← Docs API Reference
Get an API key →
API ReferenceDurable Chat Runs

Durable Chat Runs

Surface · Durable agent

#Chat Runs (durable)

A durable counterpart to POST /v1/chat/completions. Use it when a single chat turn may run long, call multiple tools, hit a safety gate, or require human cost approval before spending Spark. The server persists the run, streams typed events over SSE, and exposes cancel + cost-approval primitives. Status values: queued, running, completed, partial_failure, waiting_for_user, failed, cancelled.

POST /v1/chat/runs Auth required

Start a durable chat run. Returns 202 Accepted on first submission, 200 OK with idempotent: true when an idempotency key matches an existing run.

#Headers

NameTypeInDescription
Idempotency-KeystringheaderOptional. X-Idempotency-Key also accepted; body idempotency_key accepted as fallback. Max 200 chars.

#Body

NameTypeInDescription
messages*arraybodyNon-empty OpenAI-style message array.
toolsarraybodyCustom OpenAI function-tool array.
tool_choicestring|objectbodyOpenAI tool-choice.
modelstringbodyOptional LLM model id.
samplingobjectbodyOptional sampling parameters (temperature, top_p, etc.).
media_referencesarraybodyOptional durable media references. All durable URLs must be externally addressable.
media_contextobjectbodyOptional initial media context: images[], videos[], audio[], plus uploadedImages[] / uploadedVideos[] / uploadedAudio[] for caller-supplied uploads.
max_estimated_capacity_unitsnumberbodyRecorded on the request snapshot so callers can surface the ceiling alongside the run. Not currently enforced server-side on chat runs.
confirm_costbooleanbodyRecorded on the request snapshot. To actually pause chat runs for cost approval, set runtime_config.requireJobConfirmation: true (see below) — paid media tool calls will then emit run_awaiting_cost_confirmation SSE events and wait for confirm-cost.
session_idstringbodyOptional caller session identifier.
client_message_idstringbodyOptional caller message identifier — useful for client-side correlation.
token_typestringbodyspark, sogni, or auto.
app_sourcestringbodyOptional caller label.
runtime_configobjectbodyRun-time tuning. Fields: qualityTier (fast|hq|pro), safeContentFilter (bool), personaNames (string[]), requireJobConfirmation (bool — set true to pause before each paid media tool dispatch), jobConfirmationThresholdUsd (number — skip pause when estimate is below this).
$ curl https://api.sogni.ai/v1/chat/runs \
  -H "Authorization: Bearer $SOGNI_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "messages": [
      {"role": "user", "content": "Storyboard a 5-shot neon teaser, 9:16, 15s."}
    ],
    "runtime_config": { "requireJobConfirmation": true }
  }'

#Response (HTTP 202 Accepted on first submit, HTTP 200 OK when an idempotency key matches an existing run)

{
  "status": "success",
  "data": {
    "run": {
      "runId": "run_xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
      "ownerWalletAddress": "0x…",
      "status": "queued",
      "schemaVersion": "…",
      "backbone": { "schemaVersion": "…", "modelKnowledgeVersion": "…", "routingPolicyVersion": "…" },
      "timestamps": { "createdAt": "…", "updatedAt": "…" },
      "scope": { "ownerWalletAddress": "0x…", "appSource": "sogni-api" },
      "request": { /* request snapshot */ },
      "messages": [],
      "toolCalls": [],
      "toolResults": [],
      "mediaContext": { "images": [], "videos": [], "audio": [], "uploadedImages": [], "uploadedVideos": [], "uploadedAudio": [] },
      "events": [{ "sequence": 0, "type": "run_created", "at": "…" }],
      "createTime": 1731950400000,
      "updateTime": 1731950400000
    },
    "idempotent": false
  }
}
GET /v1/chat/runs/:id Auth required

Read the full run snapshot — current status, request, events, and (when paused) the waiting reason.

GET /v1/chat/runs/:id/events Auth required

Read the persisted event log. Use ?after=<sequence> to fetch only events past a known sequence number.

#Query parameters

NameTypeInDescription
afterintegerqueryOnly return events with sequence > after.
GET /v1/chat/runs/:id/events/stream Auth required

Server-Sent Events stream. Replays persisted events, then polls for new ones until the run reaches a terminal status (completed, failed, partial_failure, cancelled). Supports Last-Event-ID for resume and ?after=<sequence>.

#Event types

  • Lifecyclerun_created, run_resumed, run_completed, run_partial_failure, run_failed, run_cancelled
  • Roundsllm_round_started, assistant_message_delta, assistant_message_completed
  • Tool callstool_call_dispatched, tool_call_progress, tool_call_resolved
  • Media + statemedia_context_updated, media_turn_intent_classified, asset_manifest_updated
  • Billingllm_spend (authoritative per-round LLM token cost: costInToken, costInUSD, tokenType, modelName, token counts; dedupe on eventId), billing_preview_updated
  • Pause + resumerun_waiting_for_user, run_awaiting_cost_confirmation (emits toolCallId + estimate), run_cost_confirmation_resolved
  • Syntheticrun_status frames emitted by the stream on status transitions
id: 42
event: tool_call_dispatched
data: {"runId":"run_…","sequence":42,"type":"tool_call_dispatched","payload":{…}}

id: 43
event: run_awaiting_cost_confirmation
data: {"runId":"run_…","sequence":43,"type":"run_awaiting_cost_confirmation","payload":{"toolCallId":"call_…","estimatedCapacityUnits":120}}
POST /v1/chat/runs/:id/cancel Auth required

Cooperative cancel. Flips the run to cancelled, halts any in-flight tool calls owned by the run, and appends a run_cancelled event so any SSE listeners see the transition.

#Body

NameTypeInDescription
reasonstringbodyOptional cancellation reason. Defaults to user_cancelled.

#Response

{
  "status": "success",
  "data": { "run": { "runId": "run_…", "status": "cancelled" }, "aborted": true }
}
POST /v1/chat/runs/:id/confirm-cost Auth required

Resume a run that paused with waiting_for_user + cost_approval_required. Records the caller's decision, flips the run back to running, and dispatches the held tool calls.

#Body

NameTypeInDescription
tool_call_id*stringbodyID of the paused tool call (from the run_awaiting_cost_confirmation event).
decision*stringbody"confirm" or "cancel".
acceptedCostPreview*objectbodyRequired for decision: "confirm" (alias accepted_cost_preview). Echo the preview from the run_awaiting_cost_confirmation / billing_preview_updated event — { totalEstimatedCapacityUnits, tokenType, validityUntil }. totalEstimatedCapacityUnits is compared against the persisted snapshot; a stale or expired preview returns 409 (a missing or malformed object returns 400).
overridesobjectbodyOptional allowlisted edits applied to the held tool call on resume — qualityTier (fast/hq/pro), safeContentFilter, and prompt/prompts. Prompt edits are cost-neutral; cost-inflating keys are silently dropped.
reasonstringbodyOptional caller-supplied reason recorded with the decision.
idempotency_keystringbodyOptional. Idempotency-Key / X-Idempotency-Key headers also accepted.
Insufficient-credits and safety-review pauses cannot be resumed via confirm-cost. Insufficient-credits requires topping up + a fresh run; safety-review requires POST /cancel to release the run. The API returns 409 with a routing message in either case.