> 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/attach-message-stream.md).

# Attach Message Stream

Read the conversation's in-flight agent response.

The safe counterpart to `POST` on this path: that one starts a run, this one only watches whichever run is current. So it never consumes credits, cannot be refused with `locked`, needs only read access, and any number of clients may call it at once — a second browser tab, or the same client resuming after a dropped connection.

Send `Last-Event-ID` to resume. The id names both the run and the position within it, so a resume carries on with the run it was watching — through to its `done`, even if that run has finished in the meantime and the conversation has moved on. That is what lets a client keep *appending* streamed frames after a dropped connection rather than replacing the turn wholesale. An id this API did not issue is ignored, and the live run served instead.

Omit it — a second tab, a fresh page — and the response follows whichever run is live, from its first frame.

A run's terminal frame carries an *empty* `id`, which resets the cursor. So a client that reconnects after one is served the live run rather than the finished one it was watching: the conversation is shared, and the turn that comes next may be somebody else's. That is what stops an auto-reconnecting client from pinning itself to a dead run.

Always `200`. When there is nothing to serve — no run live and no resumable cursor, or a cursor whose run has expired — the single frame `event: no_active_run` is returned and the response ends, so a client needs no status-code handling to tell that apart from any other outcome. Fall back to `GET .../messages/` there: a run's message is persisted before its `done` frame, so by then the result is already readable.

```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"}}}}},"paths":{"/api/v1/conversations/{conversation_id}/messages/stream":{"get":{"tags":["messages","public"],"summary":"Attach Message Stream","description":"Read the conversation's in-flight agent response.\n\nThe safe counterpart to ``POST`` on this path: that one starts a run, this one\nonly watches whichever run is current. So it never consumes credits, cannot be\nrefused with ``locked``, needs only read access, and any number of clients may\ncall it at once — a second browser tab, or the same client resuming after a\ndropped connection.\n\nSend ``Last-Event-ID`` to resume. The id names both the run and the position\nwithin it, so a resume carries on with the run it was watching — through to\nits ``done``, even if that run has finished in the meantime and the\nconversation has moved on. That is what lets a client keep *appending* streamed\nframes after a dropped connection rather than replacing the turn wholesale. An\nid this API did not issue is ignored, and the live run served instead.\n\nOmit it — a second tab, a fresh page — and the response follows whichever run\nis live, from its first frame.\n\nA run's terminal frame carries an *empty* ``id``, which resets the cursor. So a\nclient that reconnects after one is served the live run rather than the finished\none it was watching: the conversation is shared, and the turn that comes next\nmay be somebody else's. That is what stops an auto-reconnecting client from\npinning itself to a dead run.\n\nAlways ``200``. When there is nothing to serve — no run live and no resumable\ncursor, or a cursor whose run has expired — the single frame\n``event: no_active_run`` is returned and the response ends, so a client needs\nno status-code handling to tell that apart from any other outcome. Fall back to\n``GET .../messages/`` there: a run's message is persisted before its ``done``\nframe, so by then the result is already readable.","operationId":"messages-attach_message_stream","parameters":[{"name":"conversation_id","in":"path","required":true,"schema":{"type":"integer","title":"Conversation Id"}},{"name":"Last-Event-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last-Event-Id"}}],"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":{}}}}}}}}
```
