> For the complete documentation index, see [llms.txt](https://docs.ibexa.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.ibexa.ai/developers/rest-api/messages/send-message-stream.md).

# Send Message Stream

Send a message to a conversation and stream the agent's response as SSE.

Emits `start`, then any of `token` / `tool_call_start` / `tool_call_end` / `tool_call_error` / `mcp_auth_required` / `message` events during the run, then `done` once the message has been persisted. `mcp_auth_required` always accompanies (never replaces) a `tool_call_error` for the same call — it signals specifically that the failing tool's MCP server needs a per-user connection that doesn't exist yet, and carries `mcp_connect_url` — the frontend URL the user opens to establish it. Errors raised after the stream is opened are delivered in-band as `event: error` — the HTTP response itself stays 200. An organisation out of credits is refused that way too, with code `insufficient_credits`, before the agent runs.

The first `message` frame is **the caller's own message**, echoed back with `role: "user"` before the agent produces anything. It is there because a run is read by more than its sender — a second viewer, or the sender itself after a reconnect — and without it a run carries an answer with no visible prompt. A client that renders every `message` frame will therefore see the message it just sent: fold it into whatever was rendered optimistically rather than appending it twice. Its `message_id` is empty, unlike every other `message` frame: LangChain assigns one only when the turn is persisted, which has not happened yet, and an invented id would match nothing later. Match it on its content, which the sender knows because it just sent it.

Every frame carries an `id` naming this run and the frame's position in it, *except* the terminal `done`/`error`, which carry an empty `id` to reset the client's cursor — nothing follows them in this run, so a reconnect should ask for whatever is live rather than resume a run that has ended.

The run is published to Redis by a task that outlives this request, and this response is simply the first subscriber to it — so losing this connection no longer aborts the run, and the client resumes with a `GET` on this same path, passing that id back as `Last-Event-ID`. Any number of other viewers can watch the same run at once.

```json
{"openapi":"3.1.0","info":{"title":"Ibexa Agentic Marketing Platform","version":"0.1.0"},"security":[{"OAuth2PasswordBearer":[]}],"components":{"securitySchemes":{"OAuth2PasswordBearer":{"type":"oauth2","flows":{"password":{"scopes":{},"tokenUrl":"/api/v1/login/access-token"}}}},"schemas":{"SendMessagePayloadSchema":{"properties":{"content":{"type":"string","title":"Content","description":"Message content to add to conversation","default":""},"file_ids":{"items":{"type":"string","format":"uuid"},"type":"array","title":"File Ids","description":"IDs of files previously uploaded to this conversation to attach to the message"}},"type":"object","title":"SendMessagePayloadSchema","description":"Pydantic schema for sending a message to a conversation."}}},"paths":{"/api/v1/conversations/{conversation_id}/messages/stream":{"post":{"tags":["messages","public"],"summary":"Send Message Stream","description":"Send a message to a conversation and stream the agent's response as SSE.\n\nEmits ``start``, then any of ``token`` / ``tool_call_start`` /\n``tool_call_end`` / ``tool_call_error`` / ``mcp_auth_required`` /\n``message`` events during the run, then ``done`` once the message has\nbeen persisted. ``mcp_auth_required`` always accompanies (never replaces)\na ``tool_call_error`` for the same call — it signals specifically that the\nfailing tool's MCP server needs a per-user connection that doesn't exist\nyet, and carries ``mcp_connect_url`` — the frontend URL the user opens to\nestablish it. Errors raised after the stream is opened are delivered in-band as\n``event: error`` — the HTTP response itself stays 200. An organisation out\nof credits is refused that way too, with code ``insufficient_credits``,\nbefore the agent runs.\n\nThe first ``message`` frame is **the caller's own message**, echoed back with\n``role: \"user\"`` before the agent produces anything. It is there because a run\nis read by more than its sender — a second viewer, or the sender itself after a\nreconnect — and without it a run carries an answer with no visible prompt. A\nclient that renders every ``message`` frame will therefore see the message it\njust sent: fold it into whatever was rendered optimistically rather than\nappending it twice. Its ``message_id`` is empty, unlike every other\n``message`` frame: LangChain assigns one only when the turn is persisted, which\nhas not happened yet, and an invented id would match nothing later. Match it on\nits content, which the sender knows because it just sent it.\n\nEvery frame carries an ``id`` naming this run and the frame's position in it,\n*except* the terminal ``done``/``error``, which carry an empty ``id`` to reset\nthe client's cursor — nothing follows them in this run, so a reconnect should\nask for whatever is live rather than resume a run that has ended.\n\nThe run is published to Redis by a task that outlives this request, and this\nresponse is simply the first subscriber to it — so losing this connection no\nlonger aborts the run, and the client resumes with a ``GET`` on this same path,\npassing that id back as ``Last-Event-ID``. Any number of other viewers can\nwatch the same run at once.","operationId":"messages-send_message_stream","parameters":[{"name":"conversation_id","in":"path","required":true,"schema":{"type":"integer","title":"Conversation Id"}},{"name":"x-tenant-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendMessagePayloadSchema"}}}},"responses":{"200":{"description":"Successful Response","content":{"text/event-stream":{"itemSchema":{"type":"object","properties":{"data":{"type":"string"},"event":{"type":"string"},"id":{"type":"string"},"retry":{"type":"integer","minimum":0}}}}}},"401":{"description":"User not authenticated","content":{"application/json":{}}}}}}}}
```
