> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tryblend.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Streaming

> Server-Sent Events format and how to consume them.

Both `POST /api/v1/responses` (with `stream: true`) and `GET /api/v1/runs/{runId}/stream` open a **Server-Sent Events** (SSE) connection. SSE is a plain HTTP response with `Content-Type: text/event-stream` — every event is a `data:` line followed by a blank line.

```
data: {"type":"response.created","response":{...}}

data: {"type":"response.output_item.added","item":{...}}

data: {"type":"response.completed","response":{...}}

data: [DONE]
```

The connection closes after `data: [DONE]`. If it drops earlier, you can resume with [`GET /runs/{runId}/stream`](/endpoint/stream-a-run) using the same `run_id`.

## Event types

Every event is a JSON object with a `type` field. Events fall into three groups: lifecycle, output, and terminal.

### Lifecycle

<ResponseField name="response.created" type="event">
  Sent immediately after `POST /responses` connects. Use its `response.id` and `response.run_id` to correlate downstream events or resume later.

  ```json theme={null}
  {
      "type": "response.created",
      "response": {
          "id": "resp_run_xyz",
          "run_id": "run_xyz",
          "created_at": 1734567890,
          "workflow_id": "pub_abc123",
          "service_tier": null
      }
  }
  ```
</ResponseField>

<ResponseField name="response.resumed" type="event">
  Sent immediately after `GET /runs/{runId}/stream` connects. Includes the current `status` so you know whether you're attaching mid-run or to something already terminal.

  ```json theme={null}
  {
      "type": "response.resumed",
      "response": {
          "id": "resp_run_xyz",
          "run_id": "run_xyz",
          "status": "running"
      }
  }
  ```
</ResponseField>

### Output

<ResponseField name="response.output_item.added" type="event">
  Progress event indicating that the response stream opened an output item. This is not the final itemized workflow output shape; read `GET /api/v1/runs/{runId}` after completion for Result fields, output items, and parts.

  ```json theme={null}
  {
      "type": "response.output_item.added",
      "output_index": 0,
      "item": {
          "type": "message",
          "id": "msg_run_xyz",
          "phase": "final_answer"
      }
  }
  ```
</ResponseField>

<ResponseField name="response.output_item.done" type="event">
  Progress event indicating that the streamed output item closed. Same shape as `added`.
</ResponseField>

### Terminal

<ResponseField name="response.completed" type="event">
  Run finished successfully. Followed by `data: [DONE]` and connection close.

  ```json theme={null}
  {
      "type": "response.completed",
      "response": {
          "usage": null,
          "service_tier": null
      }
  }
  ```

  To read the actual output, call [`GET /api/v1/runs/{runId}`](/endpoint/get-a-run) and read the `output` object. Streaming delivers progress events; the final structured output lives on the run record.
</ResponseField>

<ResponseField name="response.failed" type="event">
  Run failed. Followed by `data: [DONE]` and connection close.

  ```json theme={null}
  {
      "type": "response.failed",
      "response": {
          "error": {
              "code": null,
              "message": "Workflow failed."
          },
          "incomplete_details": {
              "reason": "error"
          },
          "usage": null,
          "service_tier": null
      }
  }
  ```
</ResponseField>

## Keepalive

The server emits SSE comment lines (`: keepalive`) every 15 seconds to prevent proxies and CDNs from closing idle connections during long-running steps. Ignore them — any line starting with `:` is a comment.

## Consuming the stream

### Browser

```ts theme={null}
const res = await fetch('https://tryblend.ai/api/v1/responses', {
    method: 'POST',
    headers: {
        'Authorization': `Bearer ${API_KEY}`,
        'Content-Type': 'application/json',
    },
    body: JSON.stringify({ workflowId: 'pub_abc123', stream: true, inputs: { prompt: 'hi' } }),
})

const reader = res.body!.getReader()
const decoder = new TextDecoder()
let buffer = ''

while (true) {
    const { value, done } = await reader.read()
    if (done) break
    buffer += decoder.decode(value, { stream: true })

    const frames = buffer.split('\n\n')
    buffer = frames.pop() ?? ''

    for (const frame of frames) {
        const line = frame.split('\n').find(l => l.startsWith('data: '))
        if (!line) continue

        const payload = line.slice(6)
        if (payload === '[DONE]') return

        const event = JSON.parse(payload)
        handleEvent(event)
    }
}
```

### Node / Python

Both have solid SSE libraries — [`eventsource`](https://www.npmjs.com/package/eventsource) for Node, [`httpx` + `sseclient`](https://pypi.org/project/sseclient-py/) for Python. The semantics are the same: parse each `data:` line as JSON and branch on `type`.

## Resuming a dropped stream

If your connection drops mid-run, don't re-POST — that starts a new run (and bills you again). Instead, reconnect with the `run_id` from `response.created`:

```bash theme={null}
curl https://tryblend.ai/api/v1/runs/run_xyz/stream \
    -H "Authorization: Bearer bai_..."
```

The first event will be `response.resumed`. If the run already finished while you were disconnected, you'll get the terminal event and `[DONE]` on the next poll tick (\~1 second).
