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

# Alias of POST /api/threads/{id}/messages

> Native human takeover may return a JSON liveChat acknowledgement even with stream=true…

Native human takeover may return a JSON liveChat acknowledgement even with stream=true. With clientMessageId and session identity, complete replies replay for ten minutes with X-Idempotent-Replay: true. 409 message\_outcome\_unknown requires conversation refresh and reconciliation, not automatic execution. A complete SSE replay requires a terminated \[DONE] data event and no error events. Reporting defaults to fail-open; RESPONSE\_OUTBOX\_STRICT=true makes a configured reporting enqueue failure return 503 (or an SSE error without \[DONE]).

For an existing protected thread, send its `x-widget-thread-token` capability in addition to widget identity. Widget identity is verified when signing is configured; tokenless compatibility does not establish tenant ownership. See the authentication and recovery guide for credential and retry rules. Captured responses larger than 256 KiB are not replayable; a later retry reports an unknown outcome rather than re-executing.

### Path parameters

**id** `string` *required*

Thread id (`thr_&lt;uuid>`).

### Headers

**x-widget-thread-token** `string`

Capability required for an existing thread that has an issued token. Legacy capability-less threads are denied when STATESET\_THREAD\_TOKEN\_REQUIRED=true.

### Request body

`ThreadMessageRequest`

**clientMessageId** `string`

Stable identity for one intentional send; preserve it across retries. Maximum length: `256`.

**message** `string`

Visitor text. Required unless `attachments` is non-empty.

**stream** `boolean`

`true` answers `text/event-stream`.

**provider** `Provider`

Allowed values: `'openai'`, `'anthropic'`, `'gemini'`.

**channel** `string`

Default: `'chat'`.

**meta** `ChatMeta`

Free-form request metadata forwarded to the agent (department, custom fields, page context, `sessionId`, and `agent`/`agentConfig`/`kb` identity hints). Stored on the thread. Shape is merchant-defined.

**ChatMeta**

**sessionId** `string`

**threadMessages** `RecoveryMessage[]`

**RecoveryMessage**

**id** `string`

**sender** `string` *required*

Allowed values: `'user'`, `'ai'`.

**text** `string` *required*

**timestamp** `string`

**attachments** `Attachment[]`

Maximum items: `5`.

**Attachment**

**id** `string`

**name** `string` *required*

**size** `integer` *required*

Maximum: `10485760`.

**type** `string` *required*

**dataUrl** `string`

`data:&lt;mime>;base64,&lt;payload>`.

**previewUrl** `string,null`

**url** `string`

Public URL of an already uploaded image (read for vision providers).

**sessionId** `string`

Widget session id; stored on the thread and copied into `meta.sessionId`.

**threadMessages** `RecoveryMessage[]`

Recovery history when the referenced thread is gone (see `RecoveryMessage`).

**RecoveryMessage**

**id** `string`

**sender** `string` *required*

Allowed values: `'user'`, `'ai'`.

**text** `string` *required*

**timestamp** `string`

**threadToken** `string`

Alternative to the `x-widget-thread-token` header.

**orgId** `string`

**agentId** `string`

**agent** `object`

`&#123; orgId, agentId &#125;` identity hint; must match the widget token when present.

**agent**

**orgId** `string`

**agentId** `string`

### Response

`ChatResponse | LiveChatAcknowledgement`

**ok** `boolean` *required*

Fixed value: `true`.

**threadId** `string` *required*

**messageId** `string` *required*

Id the server stored for this AI reply in the thread (`msg_&lt;base36 ms>_&lt;8 hex>`). Use it as `messageId` for `POST /api/response/feedback`.

**threadToken** `string`

Present only on the response that created (or restored) the thread.

**reply** `string` *required*

**provider** `Provider` *required*

Allowed values: `'openai'`, `'anthropic'`, `'gemini'`.

**model** `string` *required*

**sources** `SourceCitation[]`

Present only when `STATESET_SHOW_SOURCES=true` and the KB matched.

**SourceCitation**

**id** `string` *required*

**title** `string` *required*

**url** `string` *required*

**snippet** `string`

Up to 220 characters of the document.

**type** `string`

Rich message type when `content` is present. Allowed values: `'product_cards'`, `'order_cards'`, `'card'`, `'buttons'`, `'form'`.

**content** `RichContent`

Payload of a rich message (`card`, `buttons`, `form`, `product_cards`, `order_cards`); which keys are present depends on the message type. Mirrors `RichContent` in src/types/messages.ts minus client-only callbacks.

**RichContent**

**image** `string`

**title** `string`

**description** `string`

**formId** `string`

**buttonStyle** `string`

Allowed values: `'default'`, `'pill'`.

**layout** `string`

Allowed values: `'scroll'`, `'stack'`.

**actions** `object[]`

**actions**

**label** `string` *required*

**buttons** `object[]`

**buttons**

**label** `string` *required*

**value** `string`

**href** `string`

**target** `string`

Allowed values: `'_self'`, `'_blank'`.

**fields** `object[]`

**fields**

**name** `string` *required*

**label** `string` *required*

**type** `string`

**placeholder** `string`

**required** `boolean`

**options** `object[]`

**submitLabel** `string`

**headline** `string`

**subhead** `string`

**products** `ProductCard[]`

**ProductCard**

**id** `string` *required*

**title** `string` *required*

**price** `string`

**image** `string`

**url** `string`

**description** `string`

**whyItMatches** `string`

**rating** `number`

**reviewCount** `integer`

**badges** `string[]`

**isUpsell** `boolean`

**actions** `ProductAction[]`

**variantOptions** `ProductVariantOption[]`

**variantSelectionLabel** `string`

**variantSelectionPlaceholder** `string`

**variantSelectionRequired** `boolean`

**variantDisplay** `string`

Allowed values: `'select'`, `'swatches'`.

**selectedVariantId** `string`

**selectedVariantLabel** `string`

**complementaryProducts** `ProductCard[]`

**complementaryHeadline** `string`

**complementaryText** `string`

**continueShoppingAfterAdd** `boolean`

**autoAction** `object`

**autoAction**

**id** `string` *required*

**type** `string` *required*

Fixed value: `'add_to_cart'`.

**productId** `string` *required*

**quantity** `integer` *required*

**expiresAt** `string (date-time)` *required*

**orders** `OrderCard[]`

**OrderCard**

**id** `string` *required*

**name** `string` *required*

**amount** `string`

**status** `string`

**trackingUrl** `string`

**lineItems** `object[]`

**actions** `object[]`

**escalated** `boolean`

The message triggered a live-agent escalation (Gorgias ticket). Fixed value: `true`.

### Status codes

| Code | Meaning |
| - | - |
| `200` | Assistant reply. `application/json` when `stream` is falsy; `text/event-stream` when |
| `stream: true` (see `SSEStream`). | |
| `400` | No message or attachments, attachments rejected, or malformed JSON |
| `401` | Widget token missing, malformed, expired, from the future, or badly signed |
| `403` | Thread capability missing/wrong, thread owner mismatch, or token identity mismatch |
| `404` | Unknown thread (or, for feedback, a message that is not an assistant message) |
| `409` | idempotency\_conflict, message\_in\_progress, message\_outcome\_unknown, or thread\_busy |
| `413` | Body over the JSON limit, or an attachment over 10 MB |
| `429` | Too many requests for this IP (scoped by verified org/agent) |
| `500` | Provider failure. The chat handler answers `&#123; error: &lt;provider error message>, type &#125;` |
| (a human-readable message, not a code) or `&#123; error: '&lt;PROVIDER>_API_KEY is not set.' &#125;` | |
| when the selected provider has no key. The request and the provider key are validated | |
| before a thread is restored or created; when a later failure happens on a thread this | |
| request created or restored, the body also carries `threadId` and the one-time | |
| `threadToken`. Errors thrown before the handler answer \`\{ error: internal\_error, | |
| requestId }`. On an SSE stream the failure is emitted as an `error\` event instead and | |
| the stream ends. | |
| `503` | Strict response-reporting queue unavailable/full, execution store or persistence failure. The default reporting mode logs/counts enqueue loss while permitting the reply. An interrupted stream can emit an error without \[DONE]. |

### Using this contract

Read the [source OpenAPI document](https://docs.stateset.com/spec/chat-widget.json) for declared schemas and alternatives.
This page also includes documented corrections from the [spec overlays](https://github.com/stateset/docs/tree/main/spec/overlays).
Example IDs and values are illustrative; replace them with records from your workspace.

```bash cURL theme={null}
curl --silent --show-error --fail-with-body --no-buffer --request POST \
  --url 'https://api.example.com/api/response/threads/{id}/messages' \
  --header 'x-widget-token: YOUR_API_KEY' \
  --header 'x-widget-thread-token: REPLACE_WITH_X_WIDGET_THREAD_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{
  "clientMessageId": "string",
  "message": "Two-person tent, green — replacement for damaged pole set.",
  "stream": true,
  "provider": "openai",
  "channel": "chat",
  "meta": {
    "sessionId": "string",
    "threadMessages": [
      {
        "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "sender": "user",
        "text": "Two-person tent, green — replacement for damaged pole set.",
        "timestamp": "2026-08-31T14:22:05Z"
      }
    ]
  },
  "attachments": [
    {
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "name": "Two-Person Tent",
      "size": 1,
      "type": "standard",
      "dataUrl": "string",
      "previewUrl": "string",
      "url": "https://example.com/webhooks/stateset"
    }
  ],
  "sessionId": "string",
  "threadMessages": [
    {
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "sender": "user",
      "text": "Two-person tent, green — replacement for damaged pole set.",
      "timestamp": "2026-08-31T14:22:05Z"
    }
  ],
  "threadToken": "YOUR_API_KEY",
  "orgId": "string",
  "agentId": "string",
  "agent": {
    "orgId": "string",
    "agentId": "string"
  }
}'
```

```json 200 theme={null}
{
  "ok": true,
  "threadId": "string",
  "messageId": "msg_mfu3x2k1_9c1e4a7b",
  "threadToken": "YOUR_API_KEY",
  "reply": "string",
  "provider": "openai",
  "model": "string",
  "sources": [
    {
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "title": "Replacement tent pole set",
      "url": "https://example.com/webhooks/stateset",
      "snippet": "string"
    }
  ],
  "type": "product_cards",
  "content": {
    "image": "https://cdn.example.com/products/tent-2p.jpg",
    "title": "Replacement tent pole set",
    "description": "Two-person tent, green — replacement for damaged pole set.",
    "formId": "string",
    "buttonStyle": "default",
    "layout": "scroll",
    "actions": [
      {
        "label": "Replacement tent pole set"
      }
    ],
    "buttons": [
      {
        "label": "Replacement tent pole set",
        "value": "49.00",
        "href": "https://example.com/webhooks/stateset",
        "target": "_self"
      }
    ],
    "fields": [
      {
        "name": "Two-Person Tent",
        "label": "Replacement tent pole set",
        "type": "standard",
        "placeholder": "string",
        "required": true,
        "options": [
          {
            "label": "Replacement tent pole set",
            "value": "49.00"
          }
        ]
      }
    ],
    "submitLabel": "string",
    "headline": "Replacement tent pole set",
    "subhead": "string",
    "products": [
      {
        "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "title": "Replacement tent pole set",
        "price": "49.00",
        "image": "https://cdn.example.com/products/tent-2p.jpg",
        "url": "https://example.com/webhooks/stateset",
        "description": "Two-person tent, green — replacement for damaged pole set.",
        "whyItMatches": "string",
        "rating": 1.5,
        "reviewCount": 1,
        "badges": [
          "string"
        ],
        "isUpsell": true,
        "actions": [
          {
            "type": "view",
            "label": "Replacement tent pole set",
            "url": "https://example.com/webhooks/stateset",
            "value": "49.00",
            "discountCode": "5.00"
          }
        ],
        "variantOptions": [
          {
            "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
            "label": "Replacement tent pole set",
            "description": "Two-person tent, green — replacement for damaged pole set.",
            "swatchColor": "string",
            "price": "49.00",
            "available": true,
            "url": "https://example.com/webhooks/stateset"
          }
        ],
        "variantSelectionLabel": "string",
        "variantSelectionPlaceholder": "string",
        "variantSelectionRequired": true,
        "variantDisplay": "select",
        "selectedVariantId": "string",
        "selectedVariantLabel": "string",
        "complementaryProducts": [
          {}
        ],
        "complementaryHeadline": "string",
        "complementaryText": "string",
        "continueShoppingAfterAdd": true
      }
    ],
    "autoAction": {
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "type": "add_to_cart",
      "productId": "string",
      "quantity": 2,
      "expiresAt": "2026-08-31T14:22:05Z"
    },
    "orders": [
      {
        "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "name": "Two-Person Tent",
        "amount": "49.00",
        "status": "pending",
        "trackingUrl": "string",
        "lineItems": [
          {
            "title": "Replacement tent pole set",
            "price": "49.00",
            "quantity": 2,
            "image": "https://cdn.example.com/products/tent-2p.jpg"
          }
        ],
        "actions": [
          {
            "label": "Replacement tent pole set",
            "value": "49.00",
            "href": "https://example.com/webhooks/stateset",
            "target": "_self"
          }
        ]
      }
    ]
  },
  "escalated": true
}
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.