Quickstart

Your first cited interpretation.

From zero to a cited answer in minutes.

1. Mint an API key

In the PLCs.ai app, open Settings → API Keys, click Create key, and give it the ai_explain (Interpret & explain) scope. Copy the secret — it is shown exactly once.

export PLCS_API_KEY=plck_live_xxxxxxxxxxxxxxxxxxxxxxxx
export PLCS_PROJECT_ID=prj_your_project_id

2. Install an SDK (optional)

bash
pip install plcsai
bash
dotnet add package PlcsAi

3. Get a cited answer

Using an example question — "What conditions must be true for the main conveyor to start?"

bash
curl -s -X POST "https://app.plcs.ai/api/v1/projects/$PLCS_PROJECT_ID/interpret" \
  -H "Authorization: Bearer $PLCS_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "prompt": "What conditions must be true for the main conveyor to start?",
    "mode": "sync"
  }'
python
from plcsai import Client

client = Client(api_key="plck_live_…")

result = client.interpret(
    project_id="prj_…",
    prompt="What conditions must be true for the main conveyor to start?",
)
if result.status == "answer":
    print(result.answer)
    for c in result.citations:
        print(" -", c.location_kind, c.path)
elif result.status == "needs_input":
    # The assistant stopped to ask. Relay the questions; nothing is guessed.
    for q in result.questions:
        print(q.question, q.options)

print(result.request_id)
csharp
using PlcsAi;

var client = new PlcsClient("plck_live_…");

var result = await client.InterpretAsync(
    projectId: "prj_…",
    prompt: "What conditions must be true for the main conveyor to start?");

if (result is AnswerResponse answer)
{
    Console.WriteLine(answer.Answer);
    foreach (var c in answer.Citations)
        Console.WriteLine($" - {c.LocationKind} {c.Path}");
}
else if (result is NeedsInputResponse ask)
{
    // The assistant stopped to ask. Relay ask.Questions; nothing is guessed.
}

Console.WriteLine(result.RequestId);

4. The response

Branch on status. A successful call is one of three shapes, and answer is only one of them:

json
{
  "status": "answer",
  "conversation_id": "v1-interpret-9f2a…",
  "answer": "The main conveyor starts when the Start pushbutton is pressed, no E-Stop is active, and the upstream Ready interlock is set …",
  "citations": [
    { "location_kind": "rung", "path": "ConveyorControl", "rung": 12 },
    { "location_kind": "tag", "path": "Conveyor_Start_PB" }
  ],
  "usage": { "input_tokens": 1840, "output_tokens": 412 }
}

5. The assistant may ask first

The assistant is interactive, so an ambiguous question comes back as needs_input rather than a guess:

json
{
  "status": "needs_input",
  "conversation_id": "v1-interpret-9f2a…",
  "questions": [
    {
      "id": "which_line",
      "question": "Which conveyor did you mean?",
      "options": ["Line 1 infeed", "Line 2 discharge"],
      "recommended_index": 1,
      "why_it_matters": "The two are interlocked differently."
    }
  ],
  "usage": { "input_tokens": 620, "output_tokens": 84 }
}

recommended_index is a hint, not a decision — nothing is applied on your behalf. A headless caller may read it and choose for itself, but the choice is yours.

6. Answer, and continue the turn

Collect a response, then call POST /projects/{id}/interpret again with the same conversation_id and an answers array. Send prompt only on the first call — the continuation carries answers instead:

json
{
  "conversation_id": "v1-interpret-9f2a…",
  "answers": [
    { "id": "which_line", "selected_index": 1 },
    { "id": "depth", "free_text": "just the rung that gates the enable" }
  ]
}

Set exactly one of selected_index (zero-based into that question’s options) or free_text (when none of the options fit). If both are present the selection wins. An entry that answers nothing is dropped and the assistant asks again rather than guessing — so a partial batch is safe, not silently mis-answered.

python
from plcsai import Answer

if result.status == "needs_input":
    answered = client.interpret(
        project_id="prj_…",
        conversation_id=result.conversation_id,
        answers=[Answer("which_line", 1)],   # free text: Answer("depth", free_text="…")
    )
    if answered.status == "answer":
        print(answered.answer)
csharp
if (result is NeedsInputResponse ask)
{
    var answered = await client.InterpretAsync(
        projectId: "prj_…",
        conversationId: ask.ConversationId,
        answers: new[] { new Answer("which_line", 1) });   // or new Answer("depth", "free text")

    if (answered is AnswerResponse done) Console.WriteLine(done.Answer);
}

The third shape, unresolved, means the request could not be served as asked — most often it asked to change the project, and names generate instead. Generate relays questions the same way, with the same answers payload.

Want tokens as they generate? Set "mode": "stream" and read the SSE events — see Streaming.