POST/v1/conversations/:id/messages
Auth: tenant — Status: stable
Send a user message and stream the assistant's reply. Same SSE response shape as POST/v1/orchestrate, plus a conversation_id field on the done event. Billed.
Request
| Path param | Type | Description |
|---|---|---|
id | int | Conversation id. |
Body
| Field | Type | Required | Description |
|---|---|---|---|
message | string | yes | The new user turn. |
agent_def | object | no | Override the conversation's snapshotted agent for this one turn (rare — usually a follow-up should just send message). When omitted, the conversation's snapshot from create time is reused. |
curl -N \
-H "Authorization: Bearer atr_…" \
-H "Content-Type: application/json" \
-d '{"message":"and what about cache writes?"}' \
http://arbiter.example.com/v1/conversations/1/messages
Headers
Idempotency-Key is honoured here on the same terms as on POST/v1/orchestrate — retries replay the original execution instead of triggering a duplicate, the assistant turn isn't persisted twice, and the dedup is in-memory with a 24h TTL.
What happens server-side
- Conversation lookup —
404if missing or wrong tenant. Validation surfaces as a clean JSON error before the SSE stream opens. - The conversation's snapshotted
agent_defis applied to the orchestrator (unless the request body supplied its own, which wins). This is what makes follow-ups work without re-sending the agent definition. - Prior messages loaded from the DB and replayed into the agent's history (capped at the most recent 100 turns to keep request payload bounded).
- The user's
messageis persisted with therequest_idissued for this stream. - The orchestrator runs and streams events exactly as
POST/v1/orchestratewould. - On a successful
done, the assistant's full cumulative response (across every tool-call re-entry iteration) is persisted alongside the request'sinput_tokens/output_tokenstotals. - On failure (
done.ok = false), the assistant message is not persisted — only the user message remains. Retry is safe.
Response
Content-Type: text/event-stream. Identical to POST/v1/orchestrate, plus conversation_id on the terminal done event.
The request_id from this call's request_received event is the handle to pass to POST/v1/requests/:id/cancel if the user clicks Stop.
Failure modes
| Status | When | Body |
|---|---|---|
| 400 | Body isn't a JSON object; missing message; agent_def shape invalid. | {"error": "..."} |
| 401 | Missing / invalid bearer. | {"error": "..."} |
| 404 | Conversation doesn't exist or belongs to another tenant. | {"error": "conversation not found"} |
200 + done.ok = false | LLM upstream failure, billing-service denial, transient I/O. See POST/v1/orchestrate. | SSE stream |
See also
POST/v1/orchestrate— single-turn variant without history replay.GET/v1/conversations/:id/messages— read history back.POST/v1/requests/:id/cancel, SSE event catalog.