Skip to main content
POST
/
agents
/
conversations
/
{id}
/
runs
Start a new run on the conversation
curl --request POST \
  --url https://api-dev.narrative.io/agents/conversations/{id}/runs \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '
{
  "client_op_id": "696b022c-e86d-4b3e-b6d7-ceb7eb1a495e",
  "expected_version": 4,
  "payload": {
    "text": "Find me a meeting time for tomorrow afternoon."
  },
  "tool_choice": {
    "kind": "auto"
  },
  "config_override": {
    "model": "anthropic.claude-opus-4.6",
    "data_plane_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
    "compute_pool_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
    "max_iterations": 2,
    "max_tokens": 2,
    "temperature": 0.5,
    "output_format_schema": {},
    "mcp_servers": [
      {
        "alias": "docs",
        "url": "https://docs.narrative.io/mcp",
        "tools": [
          {
            "name": "search_narrative_i_o_knowledge_base",
            "description": "Search the Narrative.io documentation knowledge base.",
            "input_schema": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "query"
              ],
              "properties": {
                "query": {
                  "type": "string"
                }
              }
            },
            "strict": true
          }
        ],
        "description": "Public Mintlify-hosted Narrative docs MCP server"
      }
    ],
    "client_tools": [
      {
        "alias": "docs",
        "tools": [
          {
            "name": "search_narrative_i_o_knowledge_base",
            "description": "Search the Narrative.io documentation knowledge base.",
            "input_schema": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "query"
              ],
              "properties": {
                "query": {
                  "type": "string"
                }
              }
            },
            "strict": true
          }
        ]
      }
    ]
  }
}
'
{
  "id": "82eb9cb7-619e-46bb-ac1c-8a32b0112c1c",
  "conversation_id": "bc2505b7-068d-44ed-8055-a6f6ffe54ab1",
  "company_id": 1,
  "user_id": 407,
  "client_op_id": "696b022c-e86d-4b3e-b6d7-ceb7eb1a495e",
  "effective_config": {
    "model": "anthropic.claude-opus-4.6",
    "data_plane_id": "f79cbdae-4848-47ca-95e8-69588364d185",
    "execution_cluster": "shared",
    "compute_pool_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
    "max_iterations": 8,
    "max_tokens": 2048,
    "temperature": 0,
    "output_format_schema": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "text"
      ],
      "properties": {
        "text": {
          "type": "string"
        }
      }
    },
    "mcp_servers": [],
    "client_tools": []
  },
  "submitted_inference_job_ids": [
    "3c90c3cc-0d44-4b50-8888-8dd25736052a"
  ],
  "pending_tool_calls": [
    {
      "tool_use_id": "tooluse_DWXPKZ50JDGib5GmShyUgJ",
      "name": "user-confirm_booking",
      "arguments": {
        "proposed_slot": "tomorrow afternoon"
      }
    }
  ],
  "started_at": "2026-05-18T13:21:30.355180Z",
  "tool_choice": {
    "kind": "auto"
  },
  "iterations_used": 6,
  "usage": {
    "prompt_tokens": 14842,
    "completion_tokens": 510,
    "total_tokens": 15352
  },
  "final_text": "<string>",
  "error": {
    "type": "AgentLoopMaxIterationsExceeded",
    "message": "max iterations exceeded after 5 iterations",
    "title": "Max Iterations Exceeded",
    "docs_url": "https://docs.narrative.io/reference/architecture/agent-conversations/errors/max-iterations-exceeded"
  },
  "completed_at": "2023-11-07T05:31:56Z"
}

Documentation Index

Fetch the complete documentation index at: https://docs.narrative.io/llms.txt

Use this file to discover all available pages before exploring further.

Authorizations

Authorization
string
header
required

Bearer authentication header of the form Bearer <token>, where <token> is your auth token.

Path Parameters

id
string<uuid>
required

The conversation's UUID. UUID identifying a conversation. Returned from POST /agents/conversations and used in every other agent endpoint that operates on this conversation.

Example:

"bc2505b7-068d-44ed-8055-a6f6ffe54ab1"

Body

application/json

Start a new run on the conversation.

Required fields:

  • client_op_id — your-generated UUID for idempotency.
  • expected_version — the conversation's current version, read just before this call. Triggers a 409 Version Conflict if the version moved.
  • payload — the run's input.

Optional fields:

  • tool_choice — biases tool selection on iteration 1 of this run only.
  • config_override — sparse override of the conversation's defaults for this run.
client_op_id
string<uuid>
required

A UUID you generate for each POST .../runs call. Acts as an idempotency key: re-sending the same client_op_id against the same conversation returns the original run row unchanged. This lets you retry network blips, request timeouts, etc. without accidentally starting a second workflow.

Scope: (conversation_id, client_op_id) is the uniqueness key. You can reuse the same client_op_id across different conversations; the platform doesn't deduplicate cross-conversation.

Example:

"696b022c-e86d-4b3e-b6d7-ceb7eb1a495e"

expected_version
integer
required

Monotonic per-conversation counter, bumped by 1 (or more) every time a successful run appends messages. Two roles:

  1. Delta cursor for GET .../messages?since=N — fetch only messages with sequence_no > N.
  2. Compare-and-swap token for POST .../runs — set expected_version to the conversation's current head; if it doesn't match at run-creation time, you get a Version Conflict (HTTP 409).

Always start a new run cycle by reading the current version from GET /agents/conversations/{id} rather than caching a value from earlier.

Required range: x >= 0
Example:

4

payload
object
required

The run's input. Discriminated by kind:

  • user_message — fresh user turn.
  • tool_outputs — resuming a paused run with the tool answers.
tool_choice
Auto · object

Per-run policy that biases the model toward (or away from) using tools on the first iteration of the run. From iteration 2 onward the model is back on {"kind": "auto"} regardless.

Per-run, not per-conversation: every new run picks its own tool_choice. To re-force a particular tool on every follow-up run (for example, always asking the user for confirmation), set it on each POST .../runs body.

Three shapes, discriminated by kind:

Example:
{ "kind": "auto" }
config_override
object

Sparse override applied to a single run. Any field set here replaces the corresponding defaults field for that run only. Fields not set inherit from defaults.

Two list fields (mcp_servers, client_tools) replace the whole list when set, not merge — if you provide them in the override, the conversation defaults' lists are ignored entirely for that run.

system_prompt is not in this object — it's pinned at conversation creation and cannot be overridden.

Response

Idempotent retry — the same client_op_id was posted before, and the original run row is returned verbatim. May carry a non-pending status if the original run has already advanced.

Run state as returned by POST /agents/conversations/{id}/runs (Accepted, 202) and GET /agents/runs/{id} (OK, 200). Which optional fields are populated depends on status:

  • completedfinal_text, iterations_used, usage, submitted_inference_job_ids.
  • requires_actionpending_tool_calls, iterations_used, usage, submitted_inference_job_ids.
  • failederror, possibly partial iterations_used/usage/submitted_inference_job_ids.
  • pending / running → only the at-creation fields (id, conversation_id, started_at, etc.) plus submitted_inference_job_ids if any iterations have started.
id
string<uuid>
required

UUID identifying a single run. Returned by POST /agents/conversations/{id}/runs and used in GET /agents/runs/{id} to poll progress.

Example:

"82eb9cb7-619e-46bb-ac1c-8a32b0112c1c"

conversation_id
string<uuid>
required

UUID identifying a conversation. Returned from POST /agents/conversations and used in every other agent endpoint that operates on this conversation.

Example:

"bc2505b7-068d-44ed-8055-a6f6ffe54ab1"

company_id
integer
required
Example:

1

user_id
integer
required
Example:

407

client_op_id
string<uuid>
required

A UUID you generate for each POST .../runs call. Acts as an idempotency key: re-sending the same client_op_id against the same conversation returns the original run row unchanged. This lets you retry network blips, request timeouts, etc. without accidentally starting a second workflow.

Scope: (conversation_id, client_op_id) is the uniqueness key. You can reuse the same client_op_id across different conversations; the platform doesn't deduplicate cross-conversation.

Example:

"696b022c-e86d-4b3e-b6d7-ceb7eb1a495e"

status
enum<string>
required

Where the run is in its lifecycle. Three terminal states (completed, requires_action, failed) — you stop polling when you see any of them. The two in-flight states (pending, running) mean "keep polling."

  • pending — run row inserted by the API but the workflow hasn't picked it up yet. Usually < 1 second; if a run stays here for tens of seconds the workflow workers may be down.
  • running — workflow is mid-execution. One "running" you see roughly corresponds to one inference iteration; you might cycle through this state several times for a multi-iteration tool-use loop.
  • completed — model produced a final answer. final_text is populated.
  • requires_action — model called a client-side tool. pending_tool_calls is populated; resume by posting a new run with payload.kind: tool_outputs.
  • failed — non-recoverable error. error.type, error.message, error.title, error.docs_url are populated. See the error catalog for the per-type meaning.
Available options:
pending,
running,
completed,
requires_action,
failed
effective_config
object
required

The fully-merged config that was used to run this turn — conversation defaults with config_override applied. Echoed back so you can see exactly what the model saw, especially helpful when debugging unexpected behavior caused by overrides.

submitted_inference_job_ids
string<uuid>[]
required

Inference job IDs (one per iteration) for cross-system tracing. Each ID is a row in the platform's jobs table — use it to pull the raw model request/response if you need to debug a specific iteration.

pending_tool_calls
object[]
required

Tool calls the run is waiting for the caller to answer. Empty unless status is requires_action.

started_at
string<date-time>
required
Example:

"2026-05-18T13:21:30.355180Z"

tool_choice
Auto · object

Per-run policy that biases the model toward (or away from) using tools on the first iteration of the run. From iteration 2 onward the model is back on {"kind": "auto"} regardless.

Per-run, not per-conversation: every new run picks its own tool_choice. To re-force a particular tool on every follow-up run (for example, always asking the user for confirmation), set it on each POST .../runs body.

Three shapes, discriminated by kind:

Example:
{ "kind": "auto" }
iterations_used
integer | null

Number of inference iterations this run completed. Populated on terminal states. Equal to submitted_inference_job_ids.length for the same run.

Required range: x >= 0
Example:

6

usage
object

Token usage totals across every inference iteration in this run. Used for billing and capacity planning. Note that prompt_tokens grows with each iteration because each round re-sends the full conversation history to the model.

final_text
string | null

The model's final answer. The platform extracts it from the text field of the model's structured output. Populated only on status: completed.

error
object

Populated only on status: failed.

completed_at
string<date-time> | null

When the run reached a terminal status. Null while in pending or running.