---
summary: "Every ScaiBot endpoint \u2014 bots, chat, knowledge, escalation, tone, conversations,\
  \ embed tokens."
title: API reference
path: reference/api
status: published
---

# API reference

All endpoints are mounted at `/v1/modules/scaibot/` and authenticate with the standard ScaiGrid bearer token. Responses use ScaiGrid's standard envelope (`{ "data": ... }` for success, `{ "error": ... }` for failures).

## Bots

### `GET /bots`

List bots in the caller's tenant.

```bash
curl "$SCAIGRID_HOST/v1/modules/scaibot/bots" \
  -H "Authorization: Bearer $SCAIGRID_API_KEY"
```

Query parameters: `limit` (1-100, default 20), `cursor`, `status` (`active`, `draft`, `archived`).

### `POST /bots`

Create a bot.

| Field | Required | Notes |
|---|---|---|
| `name` | yes | Human-readable name. |
| `slug` | yes | URL-safe, unique within tenant. |
| `model` | yes | Frontend model id (`scailabs/poolnoodle-omni`, etc.). |
| `display_name` | no | What the bot calls itself; defaults to `name`. |
| `welcome_message` | no | First message visitor sees. |
| `placeholder_text` | no | Input field placeholder. |
| `primary_color` | no | Hex; defaults to tenant theme. |
| `max_tokens_per_response` | no | Default 500. |
| `max_context_messages` | no | History window. Default 20. |
| `knowledge_enabled` | no | Default false. |
| `knowledge_mode` | no | `managed` (default if enabled) or `linked`. |
| `knowledge_collection_id` | when linked | ScaiMatrix collection id. |
| `knowledge_settings` | no | `top_k`, `score_threshold`, `max_chunks_per_doc`, `deduplicate`. |

Returns `201 Created` with the new bot.

### `GET /bots/{bot_id}`

Fetch one bot's full config.

### `PUT /bots/{bot_id}`

Replace the bot's config. All editable fields above are settable here.

### `PATCH /bots/{bot_id}/status`

Move the bot between lifecycle states.

```json
{ "status": "draft" | "active" | "archived" }
```

### `DELETE /bots/{bot_id}`

Hard-delete the bot. Conversations are retained (anonymised). Cascades to knowledge, escalation rules, tone.

## Embed tokens

### `POST /bots/{bot_id}/embed-token`

Mint a short-lived token for the widget.

| Field | Notes |
|---|---|
| `ttl_seconds` | 60 – 86400. Default 3600. |
| `visitor_id` | Optional; appears in analytics and conversation records. |
| `visitor_email` | Optional. |
| `visitor_name` | Optional. |
| `metadata` | Arbitrary JSON; carried through to the conversation. |

```json
{
  "data": {
    "token": "sbt_...",
    "expires_at": "2026-04-22T15:00:00Z",
    "bot_id": "bot_abc123",
    "widget_url": "https://.../widget.js"
  }
}
```

### `GET /bots/{bot_id}/embed-info`

Public, unauthenticated. Returns the widget URL and basic display config — used by the widget at load time. Does **not** return a token.

### `DELETE /bots/{bot_id}/embed-token`

Revoke a token before its TTL expires. Body: `{ "token": "sbt_..." }`.

## Chat

### `POST /chat`

Send a message in a conversation. Response is a Server-Sent Event stream.

```bash
curl -N -X POST "$SCAIGRID_HOST/v1/modules/scaibot/chat" \
  -H "Authorization: Bearer $SCAIGRID_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "bot_id": "bot_abc123",
    "conversation_id": "cnv_xyz",
    "message": "What are your refund terms?"
  }'
```

Events:

| Event | Payload |
|---|---|
| `start` | `{ "conversation_id": "...", "message_id": "..." }` |
| `token` | `{ "delta": "..." }` |
| `citation` | `{ "marker": "1", "document_id": "...", "snippet": "..." }` |
| `escalation` | `{ "rule_id": "...", "action_type": "...", "message": "..." }` |
| `done` | `{ "total_tokens": 123, "latency_ms": 1450 }` |
| `error` | `{ "code": "...", "message": "..." }` |

Omit `conversation_id` to start a new conversation; the `start` event contains the assigned id.

Query parameter `include_system_prompt=true` adds a `system_prompt` event at the start (admin only — requires `scaibot:conversations:read`).

## Tone

### `GET /bots/{bot_id}/tone`

Read current tone config.

### `PUT /bots/{bot_id}/tone`

Set tone. See [Tone](../concepts/tone) for fields.

### `POST /bots/{bot_id}/tone/preview`

Generate one non-streamed response with a proposed tone.

```json
{
  "tone": { "formality": "formal", "verbosity": "verbose" },
  "sample_message": "What are your refund terms?"
}
```

Returns `{ "data": { "response": "...", "tokens": 84 } }`.

## Escalation

### `GET /bots/{bot_id}/escalations`

List rules in priority order.

### `POST /bots/{bot_id}/escalations`

Create a rule. Body fields:

| Field | Notes |
|---|---|
| `name` | Human label. |
| `priority` | Higher fires first; default 50. |
| `trigger_type` | `keyword`, `intent`, `sentiment`, `confidence`, `explicit`. |
| `trigger_config` | Type-specific (see [Escalation](../concepts/escalation)). |
| `action_type` | `email`, `webhook`, `slack`, `scaiqueue`. |
| `action_config` | Type-specific. |
| `message` | What the bot says when this rule fires. |

Returns `201 Created`.

### `PUT /bots/{bot_id}/escalations/{rule_id}`

Replace a rule.

### `DELETE /bots/{bot_id}/escalations/{rule_id}`

Remove a rule. Returns `204 No Content`.

### `POST /bots/{bot_id}/escalations/reorder`

Bulk-reorder priorities.

```json
{ "order": ["rule_id_1", "rule_id_2", "rule_id_3"] }
```

## Knowledge

### `GET /bots/{bot_id}/documents`

List documents and their indexing status.

### `POST /bots/{bot_id}/documents`

Upload a document. Multipart form data: `file` (binary), `name` (string).

### `GET /bots/{bot_id}/documents/{doc_id}`

Fetch one document's metadata and status.

### `PUT /bots/{bot_id}/documents/{doc_id}`

Replace a document's file. Same multipart form as POST. Re-indexes.

### `DELETE /bots/{bot_id}/documents/{doc_id}`

Remove a document; chunks drop out of retrieval immediately.

## Conversations

### `GET /conversations`

List conversations.

Query parameters:

| Parameter | Notes |
|---|---|
| `bot_id` | Filter to one bot. |
| `visitor_id` | Filter to one visitor. |
| `status` | `active`, `closed`, `escalated`. |
| `since` | ISO 8601 timestamp. |
| `limit`, `cursor` | Pagination. |

### `GET /conversations/{conversation_id}`

Full transcript including all turns, citations, retrieved chunks, escalation events.

```json
{
  "data": {
    "id": "cnv_xyz",
    "bot_id": "bot_abc123",
    "visitor_id": "usr_42",
    "started_at": "2026-04-22T14:01:00Z",
    "status": "active",
    "messages": [
      {
        "role": "user",
        "content": "What are your refund terms?",
        "created_at": "..."
      },
      {
        "role": "assistant",
        "content": "Refunds are processed within 14 business days [^1].",
        "citations": [{"marker":"1","document_id":"doc_abc","snippet":"...","score":0.84}],
        "tokens": 67,
        "latency_ms": 1240,
        "created_at": "..."
      }
    ],
    "escalations": []
  }
}
```

### `PATCH /conversations/{conversation_id}/status`

Manually close a conversation.

```json
{ "status": "closed" }
```

## Errors

All endpoints return ScaiGrid's standard error envelope:

```json
{
  "error": {
    "code": "BOT_NOT_FOUND",
    "message": "Bot bot_abc123 not found in tenant ten_xyz",
    "details": { "bot_id": "bot_abc123" }
  },
  "meta": { "request_id": "req_..." }
}
```

ScaiBot-specific codes:

| Code | Meaning |
|---|---|
| `BOT_NOT_FOUND` | Bot id doesn't exist (or you can't see it). |
| `BOT_SLUG_TAKEN` | Slug already in use within tenant. |
| `BOT_INVALID_MODEL` | Model id not available to your tenant. |
| `CONVERSATION_NOT_FOUND` | Conversation id doesn't exist or doesn't belong to this bot. |
| `EMBED_TOKEN_EXPIRED` | Token expired; mint a fresh one. |
| `EMBED_TOKEN_INVALID` | Token signature didn't verify. |
| `DOCUMENT_TOO_LARGE` | Single document exceeded 50 MB. |
| `DOCUMENT_UNSUPPORTED` | File type not supported. |
| `KNOWLEDGE_COLLECTION_NOT_FOUND` | Linked-mode collection id missing or no access. |
| `ESCALATION_TRIGGER_INVALID` | Trigger config didn't validate (e.g. bad regex). |
| `ESCALATION_ACTION_FAILED` | Action attempted but downstream rejected (e.g. webhook 5xx). |
| `TONE_INVALID` | Tone field combination not allowed. |
