#Start a run
POST
/v1/chat/runs
Auth required
Returns 202 Accepted with the persisted run. Retrying with the same idempotency key returns that run instead of starting another: 200 OK with idempotent: true once it has left queued, or 202 with idempotent: false while it is still queued. Reconcile on data.run.runId. Fields may be sent in snake_case or camelCase; unknown fields return 400. Starting runs too quickly returns 429 with retryAfter (seconds) in the body.
#Headers
| Name | Type | In | Description |
|---|---|---|---|
| Idempotency-Key | string | header | Optional. X-Idempotency-Key is also accepted, and the body field idempotency_key works as a fallback. Keys longer than 200 characters are ignored, so the request runs as if no key was sent. |
#Body
| Name | Type | In | Description |
|---|---|---|---|
| messagesrequired | array | body | Non-empty OpenAI-style message array. |
| tools | array | body | Custom OpenAI function-tool array. |
| tool_choice | string | object | body | OpenAI tool choice. |
| model | string | body | Optional LLM model id. |
| sampling | object | body | Optional sampling parameters: max_tokens (default 4096), temperature, top_p, top_k, min_p, repetition_penalty, frequency_penalty, presence_penalty, task_profile, and think (default false). |
| media_references | array | body | Optional media references for hosted tools. Each url must be an http:// or https:// URL; inline data: URIs return 400, as do data: image URLs inside messages. Upload local files first with the media URL endpoints. |
| media_context | object | body | Optional initial media context: images[], videos[], audio[], plus uploadedImages[], uploadedVideos[], and uploadedAudio[] for caller-supplied uploads. Every entry must be an http(s) URL. |
| max_estimated_capacity_units | number | body | Recorded on the request snapshot so callers can show the ceiling alongside the run. Not enforced on chat runs. |
| confirm_cost | boolean | body | Recorded on the request snapshot. To pause chat runs for cost approval, set runtime_config.requireJobConfirmation: true; paid media tool calls then emit run_awaiting_cost_confirmation and wait for confirm-cost. |
| session_id | string | body | Optional caller session identifier. |
| client_message_id | string | body | Optional caller message identifier for client-side correlation. |
| token_type | string | body | spark, sogni, or auto. |
| billing_mode | string | body | auto (default), subscription, or tokens, as on chat completions. |
| app_source | string | body | Optional caller label. |
| runtime_config | object | body | Run-time tuning: qualityTier (fast, hq, or pro), safeContentFilter (boolean, default true; chat runs set the Sensitive Content Filter here, and a top-level safe_content_filter returns 400), and requireJobConfirmation (boolean; pause before paid media tool dispatch). Unknown keys are ignored. |
#Request
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 }
}'
const run = await sogni.chat.runs.create({
messages: [{ role: 'user', content: 'Storyboard a 5-shot neon teaser, 9:16, 15s.' }],
runtimeConfig: { requireJobConfirmation: true },
});
const api = 'https://api.sogni.ai/v1/chat/runs';
const headers = { Authorization: `Bearer ${process.env.SOGNI_API_KEY}`, 'Content-Type': 'application/json' };
for await (const event of sogni.chat.runs.streamEvents(run.runId)) {
if (event.type !== 'run_waiting_for_user' || event.payload?.reason !== 'cost_approval_required') continue;
const { toolCallId, costApprovalPreview } = event.payload.details;
// Show costApprovalPreview to the user, then confirm or cancel.
await fetch(`${api}/${run.runId}/confirm-cost`, {
method: 'POST',
headers,
body: JSON.stringify({ tool_call_id: toolCallId, decision: 'confirm', acceptedCostPreview: costApprovalPreview }),
});
}
import os, requests, uuid
resp = requests.post(
"https://api.sogni.ai/v1/chat/runs",
headers={
"Authorization": f"Bearer {os.environ['SOGNI_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"messages": [{"role": "user", "content": "Storyboard a 5-shot neon teaser, 9:16, 15s."}],
"runtime_config": {"requireJobConfirmation": True},
},
)
run = resp.json()["data"]["run"]
#Response
{
"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
}
}