# Resume chat stream

Source: https://docs.recoupable.dev/api-reference/chat/workflow-stream

Reconnects to an in-progress chat response without submitting another message. POST /api/chat starts a new turn: an existing live run returns 409, while…

## GET /api/chat/{chatId}/stream

Full OpenAPI specification: https://docs.recoupable.dev/spec/research.json

## Authentication

This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification.

[Authentication guide](https://docs.recoupable.dev/authentication)

## Operation and referenced schemas

```json
{
  "openapi": "3.1.0",
  "info": {
    "title": "Recoup API - Research",
    "description": "API documentation for the Recoup platform - an AI agent platform for the music industry",
    "license": {
      "name": "MIT"
    },
    "version": "1.0.0"
  },
  "servers": [
    {
      "url": "https://api.recoupable.dev"
    }
  ],
  "paths": {
    "/api/chat/{chatId}/stream": {
      "get": {
        "operationId": "getChatByChatIdStream",
        "summary": "Resume a chat response stream",
        "description": "Reconnects to an in-progress chat response without submitting another message. POST /api/chat starts a new turn: an existing live run returns 409, while uncertain state or a startup reservation returns retryable 503. Returns a live Server-Sent Events stream while a response is being generated, or a `200` finish-only SSE stream when no active response exists or the run is terminal. The chat must belong to the authenticated account.\n\nSend `?streamLifecycle=explicit-stop-v1` on both the initial POST and this reconnect GET to keep server work running when a reader disconnects. Use the authenticated Stop endpoint for cancellation; closing or aborting this reader alone does not confirm that the workflow stopped. Only exactly one streamLifecycle parameter with the exact value opts in; omitted, empty, unknown or repeated parameters retain legacy disconnect cancellation. Keep the existing `startIndex` cursor when reconnecting an already-read response.\n\n**Related endpoints**\n- [`POST /api/chat`](/api-reference/chat/workflow) — the interactive turn whose stream this resumes.\n- [`POST /api/chat/runs`](/api-reference/chat/runs) — start a **headless** run, then pass the returned `chatId` here to **watch its output live**.\n- [`GET /api/chat/runs/{runId}`](/api-reference/chat/runs-status) — check **whether** that run finished (status, not content).",
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "chatId",
            "in": "path",
            "required": true,
            "description": "UUID of the chat to resume.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "account_id",
            "in": "query",
            "required": false,
            "description": "Act on a chat belonging to another account. Only honoured for a key that is authorised for that account — an organization key may pass a member account's id; a personal key passing anyone else's is rejected with `403 Access denied to specified account_id`. Omit it to act as the authenticated account.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "startIndex",
            "in": "query",
            "required": false,
            "description": "Zero-based index of the first stream chunk to return, so a client that was already receiving this response resumes where it left off instead of replaying the whole turn.\n\nPass the number of chunks already received. Omit it to receive the response from the beginning — the right choice for a fresh reader such as a page load, or a client watching a headless [`POST /api/chat/runs`](/api-reference/chat/runs) run for the first time.\n\nA long turn's stream can end before the run does; reconnecting with `startIndex` is how a client keeps rendering without duplicating or skipping chunks.",
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          },
          {
            "name": "streamLifecycle",
            "in": "query",
            "required": false,
            "description": "Send streamLifecycle=explicit-stop-v1 exactly once on the initial POST and every reconnect GET to keep server work running when that reader disconnects. Only exactly one parameter with that exact value opts in. Omitting it, an empty or unknown value, or repeating the parameter retains legacy disconnect cancellation. Use authenticated POST /api/chat/{chatId}/stop for cancellation; a local reader abort is not confirmation that server work stopped. This is a query parameter, not a request-body field.",
            "schema": {
              "type": "string"
            },
            "example": "explicit-stop-v1"
          }
        ],
        "responses": {
          "200": {
            "description": "Server-Sent Events stream of UI message parts for the in-progress response, compatible with the Vercel AI SDK. The `x-workflow-run-id` response header carries the run ID for this response, and `x-workflow-stream-tail-index` carries the index of the last chunk known when this read was opened — a base for computing absolute positions, not a record of where the response ended. When there is nothing to resume, the stream contains one terminal finish chunk; workflow headers are absent on that path.",
            "headers": {
              "x-workflow-run-id": {
                "description": "Durable workflow run ID of the resumed run.",
                "schema": {
                  "type": "string"
                }
              },
              "x-workflow-stream-tail-index": {
                "description": "Zero-based index of the last chunk known to the stream **at the moment this read was opened**. Because headers are sent before the body, a read that stays open past that point will deliver chunks beyond it — so this is a base for computing absolute positions, not a record of where the response ended. A client resuming precisely should count the chunks it receives on top of this value. Omitted if the runtime cannot report it.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "text/event-stream": {
                "schema": {
                  "type": "string",
                  "description": "Server-Sent Events stream containing UI message parts"
                }
              }
            }
          },
          "400": {
            "description": "Invalid chatId UUID or malformed startIndex. When supplied, startIndex must parse to a non-negative integer; blank, nonnumeric, fractional and negative values fail validation. Authentication/ownership is checked first. Validation errors include status: error, error and missing_fields when a validation path is available.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatStreamErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid authentication.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatStreamErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "The caller does not own the chat session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatStreamErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Chat or owning session not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatStreamErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "The chat or session ownership lookup failed. This is not a missing-chat result.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatStreamErrorResponse"
                }
              }
            }
          },
          "502": {
            "description": "Workflow status lookup failed; this is not an empty or completed response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatStreamErrorResponse"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ChatStreamErrorResponse": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "error"
            ]
          },
          "error": {
            "type": "string"
          },
          "missing_fields": {
            "type": "array",
            "items": {
              "type": [
                "string",
                "integer"
              ]
            },
            "description": "Optional validation path for the first rejected field. String entries identify object/query fields; integer entries identify array positions. An empty path can represent validation of the path value itself. Lookup or workflow errors can omit this field."
          }
        }
      }
    },
    "securitySchemes": {
      "apiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key",
        "description": "Your Recoup API key. [Learn more](/quickstart#api-keys)."
      },
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer"
      }
    }
  }
}
```
