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

# Send a message and stream the reply as server-sent events

> The streaming form of the chat endpoint. Request body, authentication, `full` permission, `Idempotency-Key`, conversation rules, billing, and error codes are identical to `POST /v1/agents/{agent_id}/chat`; only the transport differs. The response is `text/event-stream` with named events, each carrying a JSON `data` payload:

- **response.created**: `{"request_id", "conversation_id"}` as soon as the conversation is resolved
- **response.output_text.delta**: `{"delta": "..."}` for every piece of answer text as the model produces it
- **response.completed**: the same object the synchronous endpoint returns (`request_id`, `conversation_id`, persisted `message`, `usage`); always the last event of a successful stream
- **error**: `{"code", "message", "request_id"}`, the same error object as the JSON envelope; always the last event of a failed stream

## Example Request
```bash
curl -N -X POST "/v1/agents/{agent_id}/chat/stream" \
  -H "Authorization: Bearer cht_live_{key_id}.{secret}" \
  -H "Idempotency-Key: 5b2f0d4e-1c0e-4c2b-9a5e-3d8a2f6b1c77" \
  -H "Content-Type: application/json" \
  -d '{"external_user_id": "customer-123", "message": "I want to know the price"}'
```

## Notes
- Failures before the stream is established (invalid key, validation, `idempotency_conflict`, `conversation_busy`, disabled agent, rate limit) are answered as a normal JSON error envelope with the matching HTTP status, not as a stream.
- Once headers are sent the HTTP status is already `200`; a failure after that point (for example `insufficient_balance` or `internal_error`) arrives as the `error` event.
- The reply keeps being produced and stored even if the client disconnects mid-stream, and no second reply or charge results from retrying: a retry with the same `Idempotency-Key` streams `response.created` followed directly by `response.completed` with the stored result, without replaying the deltas.
- Reasoning text and tool activity are never part of the stream; `response.completed` always carries the full final text, so a client that missed deltas can reconcile from it.



## OpenAPI

````yaml /openapi.json post /v1/agents/{agent_id}/chat/stream
openapi: 3.1.0
info:
  title: Chattler Agent API
  version: 1.0.0
  description: >-
    Public REST API for talking to a Chattler agent with an API key. Every
    request runs the agent's ordinary runtime (system prompt, knowledge base,
    tools, dialog history) and is billed to the owner's balance.
servers:
  - url: https://api.chattler.ai
security:
  - ApiKeyBearer: []
paths:
  /v1/agents/{agent_id}/chat/stream:
    post:
      tags:
        - Public API
      summary: Send a message and stream the reply as server-sent events
      description: >-
        The streaming form of the chat endpoint. Request body, authentication,
        `full` permission, `Idempotency-Key`, conversation rules, billing, and
        error codes are identical to `POST /v1/agents/{agent_id}/chat`; only the
        transport differs. The response is `text/event-stream` with named
        events, each carrying a JSON `data` payload:


        - **response.created**: `{"request_id", "conversation_id"}` as soon as
        the conversation is resolved

        - **response.output_text.delta**: `{"delta": "..."}` for every piece of
        answer text as the model produces it

        - **response.completed**: the same object the synchronous endpoint
        returns (`request_id`, `conversation_id`, persisted `message`, `usage`);
        always the last event of a successful stream

        - **error**: `{"code", "message", "request_id"}`, the same error object
        as the JSON envelope; always the last event of a failed stream


        ## Example Request

        ```bash

        curl -N -X POST "/v1/agents/{agent_id}/chat/stream" \
          -H "Authorization: Bearer cht_live_{key_id}.{secret}" \
          -H "Idempotency-Key: 5b2f0d4e-1c0e-4c2b-9a5e-3d8a2f6b1c77" \
          -H "Content-Type: application/json" \
          -d '{"external_user_id": "customer-123", "message": "I want to know the price"}'
        ```


        ## Notes

        - Failures before the stream is established (invalid key, validation,
        `idempotency_conflict`, `conversation_busy`, disabled agent, rate limit)
        are answered as a normal JSON error envelope with the matching HTTP
        status, not as a stream.

        - Once headers are sent the HTTP status is already `200`; a failure
        after that point (for example `insufficient_balance` or
        `internal_error`) arrives as the `error` event.

        - The reply keeps being produced and stored even if the client
        disconnects mid-stream, and no second reply or charge results from
        retrying: a retry with the same `Idempotency-Key` streams
        `response.created` followed directly by `response.completed` with the
        stored result, without replaying the deltas.

        - Reasoning text and tool activity are never part of the stream;
        `response.completed` always carries the full final text, so a client
        that missed deltas can reconcile from it.
      operationId: chat_stream_v1_agents__agent_id__chat_stream_post
      parameters:
        - name: agent_id
          in: path
          required: true
          schema:
            type: string
            title: Agent Id
        - name: Idempotency-Key
          in: header
          required: true
          schema:
            type: string
            minLength: 1
            maxLength: 255
            description: >-
              Client-chosen value, 1-255 characters, unique per logical request;
              a retry with the same value and body returns the stored result
            title: Idempotency-Key
          description: >-
            Client-chosen value, 1-255 characters, unique per logical request; a
            retry with the same value and body returns the stored result
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ApiChatRequest'
      responses:
        '200':
          description: >-
            SSE stream: response.created, response.output_text.delta*, then
            response.completed or error
          content:
            application/json:
              schema: {}
            text/event-stream: {}
        '401':
          description: invalid_api_key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponse'
        '402':
          description: insufficient_balance
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponse'
        '403':
          description: agent_mismatch or permission_denied
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponse'
        '404':
          description: agent_not_found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponse'
        '409':
          description: idempotency_conflict, idempotency_in_progress or conversation_busy
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponse'
        '422':
          description: validation_error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponse'
        '429':
          description: rate_limit_exceeded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponse'
        '503':
          description: api_rate_limit_unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponse'
      security:
        - ApiKeyBearer: []
components:
  schemas:
    ApiChatRequest:
      properties:
        external_user_id:
          type: string
          maxLength: 255
          minLength: 1
          title: External User Id
          description: Stable id of the end user on the caller's side
        message:
          type: string
          minLength: 1
          title: Message
      additionalProperties: false
      type: object
      required:
        - external_user_id
        - message
      title: ApiChatRequest
      description: |-
        Text-only chat input; attachments are outside this release, so any
        extra field is a validation error rather than silently ignored.
    ApiErrorResponse:
      properties:
        error:
          $ref: '#/components/schemas/ApiErrorDetail'
      type: object
      required:
        - error
      title: ApiErrorResponse
      description: The one envelope every public API failure answers with.
    ApiErrorDetail:
      properties:
        code:
          type: string
          title: Code
          description: Stable machine-readable error code
        message:
          type: string
          title: Message
          description: Human-readable explanation
        request_id:
          type: string
          title: Request Id
          description: UUID of this request, also in X-Request-ID
      type: object
      required:
        - code
        - message
        - request_id
      title: ApiErrorDetail
  securitySchemes:
    ApiKeyBearer:
      type: http
      description: 'Agent API key: Authorization: Bearer cht_live_<key_id>.<secret>'
      scheme: bearer

````