# Twigg: API reference

Base URL: https://api.twigg.ai. Customer API operations under `/api/v1` require `Authorization: Bearer <api key>`. Public catalogue operations under `/v1/catalogue` and the updates feed at `/api/v1/updates` require no API key.

Start at [API discovery](https://api.twigg.ai/) for links to this reference and the [OpenAPI specification](https://api.twigg.ai/openapi.json). Discovery and version information are public.

For system prompts, saved tools, context budgets and retention settings, see [Context control](https://twigg.ai/docs/context#configure-prompts-tools-and-context).

## Endpoints

## Core API

Create conversations, send turns, read history and discover models.

| Method | Path | Authentication | Purpose |
|---|---|---|---|
| GET | `/api/v1/chats` | API key | List chats |
| POST | `/api/v1/chats` | API key | Create a chat |
| DELETE | `/api/v1/chats/{chat_id}` | API key | Delete a chat |
| GET | `/api/v1/chats/{chat_id}` | API key | Get a chat |
| GET | `/api/v1/chats/{chat_id}/history` | API key | Read chat history |
| POST | `/api/v1/chats/{chat_id}/responses` | API key | Submit a turn and stream the answer |
| GET | `/v1/catalogue` | None | List public model prices |
| GET | `/v1/catalogue/{name}` | None | Get public model prices |
| GET | `/v1/catalogue/models.md` | None | Read model prices as Markdown |

## listChats

### GET /api/v1/chats

List chats newest first. Use after_id or before_id to page; they are mutually exclusive. Unknown query parameters are ignored.

Authentication: Bearer API key required.

| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
| `namespace` | query | [Namespace](#namespace) | no | Scope to a namespace **and everything beneath it**. `acme/proj-7` returns that scope's chats plus `acme/proj-7/user-99`'s and deeper. Absent lists the whole organisation. A namespace with no chats is not an error — there is no registry, so "empty" and "never used" are the same state and both return `[]`. |
| `after_id` | query | [ChatId](#chatid) | no | Page **older** than this id. Listings run newest-first, so "after" means further down the list, which is further back in time. |
| `before_id` | query | [ChatId](#chatid) | no | Page **newer** than this id. |
| `limit` | query | integer (int64) | no | 1–100. Out-of-range values are clamped. |

| Status | Content type | Body |
|---|---|---|
| 200 | application/json | [ChatPage](#chatpage) |
| 401 | application/json | [ErrorEnvelope](#errorenvelope) |
| 422 | application/json | [ErrorEnvelope](#errorenvelope) |
| 500 | application/json | [ErrorEnvelope](#errorenvelope) |
| 503 | application/json | [ErrorEnvelope](#errorenvelope) |

Example: listChats

```http
GET /api/v1/chats?namespace=acme%2Fproj-7&limit=20
Authorization: Bearer <api key>

200 →
{
  "data": [
    {
      "id": "01912d4e-9c1a-7b3f-8a21-6f1e2c3d4b5a",
      "namespace": "acme/proj-7",
      "title": "Support thread",
      "description": null,
      "user_metadata": {},
      "created_at": "2026-09-06T10:24:00Z",
      "length": 42,
      "updated_at": "2026-09-06T11:02:13Z"
    }
  ],
  "first_id": "01912d4e-9c1a-7b3f-8a21-6f1e2c3d4b5a",
  "last_id": "01912d4e-9c1a-7b3f-8a21-6f1e2c3d4b5a",
  "has_more": false
}
```

## createChat

### POST /api/v1/chats

Create an empty chat in the authenticated organisation. All request fields are optional.

Authentication: Bearer API key required.

Request body: [CreateChatRequest](#createchatrequest).

| Field | Type | Required | Description |
|---|---|---|---|
| `description` | null or [ChatDescription](#chatdescription) | no |  |
| `namespace` | null or [Namespace](#namespace) | no | The scope this chat's configuration resolves against, and the prefix it will be listed under. Immutable once set. Note this field does **not** coerce an empty string to "no namespace", where `title` and `description` below do. A namespace is load-bearing — it decides which system instructions and tools every future run on this chat receives — so a client whose interpolation produced `""` should get a 422 rather than a chat silently pinned to the org-global scope. The labels are decorative, and an untouched form field is not worth a rejection. |
| `title` | null or [ChatTitle](#chattitle) | no |  |
| `user_metadata` | [UserMetadata](#usermetadata) | no | Arbitrary customer JSON, capped at a kilobyte. Twigg never reads it — it is somewhere to hang a ticket reference or a UI flag so the customer does not have to keep a parallel table keyed by our ids. Absent means an empty object, never `null`, so "no tags" is one stored value rather than two. |

| Status | Content type | Body |
|---|---|---|
| 201 | application/json | [ChatCreated](#chatcreated) |
| 401 | application/json | [ErrorEnvelope](#errorenvelope) |
| 413 | application/json | [ErrorEnvelope](#errorenvelope) |
| 422 | application/json | [ErrorEnvelope](#errorenvelope) |
| 500 | application/json | [ErrorEnvelope](#errorenvelope) |
| 503 | application/json | [ErrorEnvelope](#errorenvelope) |

Example: createChat

```http
POST /api/v1/chats
Authorization: Bearer <api key>

{
  "namespace": "acme/proj-7",
  "title": "Support thread"
}

201 →
{
  "id": "01912d4e-9c1a-7b3f-8a21-6f1e2c3d4b5a",
  "namespace": "acme/proj-7",
  "title": "Support thread",
  "description": null,
  "user_metadata": {},
  "created_at": "2026-09-06T10:24:00Z"
}
```

## deleteChat

### DELETE /api/v1/chats/{chat_id}

Delete a chat and its ledger. Repeating the deletion returns 404.

Authentication: Bearer API key required.

| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
| `chat_id` | path | string (uuid) | yes | Resource identifier |

| Status | Content type | Body |
|---|---|---|
| 204 | — | No body |
| 400 | application/json | [ErrorEnvelope](#errorenvelope) |
| 401 | application/json | [ErrorEnvelope](#errorenvelope) |
| 404 | application/json | [ErrorEnvelope](#errorenvelope) |
| 422 | application/json | [ErrorEnvelope](#errorenvelope) |
| 500 | application/json | [ErrorEnvelope](#errorenvelope) |
| 503 | application/json | [ErrorEnvelope](#errorenvelope) |

## getChat

### GET /api/v1/chats/{chat_id}

Fetch a chat owned by the authenticated organisation. Missing chats and chats owned by another organisation both return 404.

Authentication: Bearer API key required.

| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
| `chat_id` | path | string (uuid) | yes | Resource identifier |

| Status | Content type | Body |
|---|---|---|
| 200 | application/json | [ChatSummary](#chatsummary) |
| 400 | application/json | [ErrorEnvelope](#errorenvelope) |
| 401 | application/json | [ErrorEnvelope](#errorenvelope) |
| 404 | application/json | [ErrorEnvelope](#errorenvelope) |
| 422 | application/json | [ErrorEnvelope](#errorenvelope) |
| 500 | application/json | [ErrorEnvelope](#errorenvelope) |
| 503 | application/json | [ErrorEnvelope](#errorenvelope) |

## getChatHistory

### GET /api/v1/chats/{chat_id}/history

Read the newest page by default, with rows ordered oldest first. Use before_ordinal or after_ordinal, not both. Unknown query parameters return 422.

Authentication: Bearer API key required.

| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
| `chat_id` | path | string (uuid) | yes | Resource identifier |
| `before_ordinal` | query | integer (int64) | no | Page **backwards** from here: the parts immediately older than this ordinal. What a reader scrolling up sends, using the `first_ordinal` of the page they already have. |
| `after_ordinal` | query | integer (int64) | no | Page **forwards** from here: the parts immediately newer than this ordinal. What a UI already open sends to pick up what has arrived since, using `last_ordinal`. An **ordinal**, not a part id, because ordinals are the ledger's only total order and a client paging already holds the one it saw last. They are never renumbered, so a cursor stays valid; deletes leave gaps, so a cursor is not an index and must not be incremented by hand. |
| `limit` | query | integer (int64) | no | 1–100, or 1–20 with `include=full`. Out-of-range values are clamped, not rejected. |
| `include` | query | `"stubs"` or `"none"` or `"full"` | no | How much of an attachment travels: `stubs` (the default), `none`, or `full`. |

| Status | Content type | Body |
|---|---|---|
| 200 | application/json | [HistoryPageResponse](#historypageresponse) |
| 400 | application/json | [ErrorEnvelope](#errorenvelope) |
| 401 | application/json | [ErrorEnvelope](#errorenvelope) |
| 404 | application/json | [ErrorEnvelope](#errorenvelope) |
| 422 | application/json | [ErrorEnvelope](#errorenvelope) |
| 500 | application/json | [ErrorEnvelope](#errorenvelope) |
| 503 | application/json | [ErrorEnvelope](#errorenvelope) |

Example: history

```http
GET /api/v1/chats/01912d4e-9c1a-7b3f-8a21-6f1e2c3d4b5a/history?after_ordinal=40&include=stubs
Authorization: Bearer <api key>

200 →
{
  "data": [
    {
      "id": "01912d51-4e7c-7a90-b3d2-8c5f1a0e6742",
      "ordinal": 41,
      "created_at": "2026-09-06T11:02:09Z",
      "role": "assistant",
      "part": {
        "type": "tool_call",
        "tool_use_id": "toolu_example",
        "tool_name": "read_file",
        "arguments": "{\"path\":\"src/main.rs\"}"
      },
      "user_metadata": {}
    }
  ],
  "first_ordinal": 41,
  "last_ordinal": 41,
  "has_more": false
}
```

## createResponse

### POST /api/v1/chats/{chat_id}/responses

Submit a prompt or tool results, then stream the model response as server-sent events. Failures before the first byte use HTTP errors; later failures use the error event. Dropping the connection does not cancel the turn.

Authentication: Bearer API key required.

| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
| `chat_id` | path | string (uuid) | yes | Resource identifier |

Request body: [CreateResponseRequest](#createresponserequest).

| Field | Type | Required | Description |
|---|---|---|---|
| `idempotency_key` | null or [IdempotencyKey](#idempotencykey) | no | Prevents a second run for the same key. Org-scoped and honoured for 24 hours. Reusing a key returns HTTP 409 conflict naming the original run in the message; it does not replay the SSE stream. After a dropped connection, inspect that run with GET /api/v1/runs/{run_id} and read chat history for persisted output. Do not generate a new key merely to retry an uncertain submission. |
| `input` | array of (object or object); minimum 0 items; maximum 128 items | yes |  |
| `max_tokens` | integer,null (int32); minimum 1 | no | Output ceiling. Only ever **lowered** — by the model's own ceiling, and by what the balance can pay for. |
| `model` | string | yes | The catalogue `name`, as `GET /api/v1/models` lists it — **not** the string the provider spells it with. The two are separate columns precisely so this one can stay stable: the wire name carries snapshot dates that vendors move under us, and a customer who pinned one would break the day an alias was re-pointed. Only an **active** model resolves; a withdrawn one would open a turn nothing can continue. |
| `reasoning_effort` | null or [ReasoningEffort](#reasoningeffort) | no | How hard the model should think, where it can. Coerced with a warning rather than rejected when the model cannot honour it. |
| `retry_of` | null or [RunId](#runid) | no | Retry the latest failed customer run on this chat without appending input. Send input: [] and a new idempotency key. Supply the model and any request-level tools again; the retry uses the current configuration. |
| `tools` | array,null | no | Tools for this request alone, in the same shape a published bundle uses. The **nearest** level there is, so one of these shadows a namespace or org-global tool of the same name. They are charged against the context budget like any other tool, because the provider charges for them. |
| `user_metadata` | [UserMetadata](#usermetadata) | no | Arbitrary customer JSON, attached to every part this turn writes. |

| Status | Content type | Body |
|---|---|---|
| 200 | text/event-stream | [ResponseEvent](#responseevent) |
| 400 | application/json | [ErrorEnvelope](#errorenvelope) |
| 401 | application/json | [ErrorEnvelope](#errorenvelope) |
| 402 | application/json | [ErrorEnvelope](#errorenvelope) |
| 404 | application/json | [ErrorEnvelope](#errorenvelope) |
| 409 | application/json | [ErrorEnvelope](#errorenvelope) |
| 413 | application/json | [ErrorEnvelope](#errorenvelope) |
| 422 | application/json | [ErrorEnvelope](#errorenvelope) |
| 500 | application/json | [ErrorEnvelope](#errorenvelope) |
| 502 | application/json | [ErrorEnvelope](#errorenvelope) |
| 503 | application/json | [ErrorEnvelope](#errorenvelope) |

Example: prompt

```http
POST /api/v1/chats/01912d4e-9c1a-7b3f-8a21-6f1e2c3d4b5a/responses
Authorization: Bearer <api key>

{
  "model": "claude-opus-5",
  "input": [
    {
      "type": "prompt",
      "text": "What changed in the billing module?"
    }
  ],
  "idempotency_key": "billing-turn-1"
}
```

Example: toolResult

```http
POST /api/v1/chats/01912d4e-9c1a-7b3f-8a21-6f1e2c3d4b5a/responses
Authorization: Bearer <api key>

{
  "model": "claude-opus-5",
  "input": [
    {
      "type": "tool_result",
      "tool_use_id": "toolu_example",
      "tool_name": "read_file",
      "text": "The billing module now records refunds.",
      "is_error": false,
      "trust": "customer_data"
    }
  ]
}
```

Example: landingFirst

```http
POST /api/v1/chats/01912d4e-9c1a-7b3f-8a21-6f1e2c3d4b5a/responses
Authorization: Bearer <api key>

{
  "model": "claude-opus-5",
  "input": [
    {
      "type": "prompt",
      "text": "What changed in billing?"
    }
  ]
}
```

Example: landingNext

```http
POST /api/v1/chats/01912d4e-9c1a-7b3f-8a21-6f1e2c3d4b5a/responses
Authorization: Bearer <api key>

{
  "model": "gpt-5-6-luna",
  "input": [
    {
      "type": "prompt",
      "text": "And in refunds?"
    }
  ]
}
```

## listCatalogue

### GET /v1/catalogue

No API key required. Anonymous requests use platform-default prices. A valid console session uses that organisation's markup; an invalid session is treated as anonymous.

Authentication: Public; no API key required.

| Status | Content type | Body |
|---|---|---|
| 200 | application/json | [Catalogue](#catalogue) |
| 404 | application/json | [ErrorEnvelope](#errorenvelope) |
| 500 | application/json | [ErrorEnvelope](#errorenvelope) |
| 503 | application/json | [ErrorEnvelope](#errorenvelope) |

## getCatalogueModel

### GET /v1/catalogue/{name}

No API key required. Anonymous requests use platform-default prices. A valid console session uses that organisation's markup; an invalid session is treated as anonymous.

Authentication: Public; no API key required.

| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
| `name` | path | string | yes | Public model name; may include slashes. |

| Status | Content type | Body |
|---|---|---|
| 200 | application/json | [CatalogueModelResponse](#cataloguemodelresponse) |
| 404 | application/json | [ErrorEnvelope](#errorenvelope) |
| 500 | application/json | [ErrorEnvelope](#errorenvelope) |
| 503 | application/json | [ErrorEnvelope](#errorenvelope) |

## getCatalogueMarkdown

### GET /v1/catalogue/models.md

No API key required. Anonymous requests use platform-default prices. A valid console session uses that organisation's markup; an invalid session is treated as anonymous.

Authentication: Public; no API key required.

| Status | Content type | Body |
|---|---|---|
| 200 | text/markdown | string |
| 404 | application/json | [ErrorEnvelope](#errorenvelope) |
| 500 | application/json | [ErrorEnvelope](#errorenvelope) |
| 503 | application/json | [ErrorEnvelope](#errorenvelope) |

## Supplementary API

Finer control and recall options for deeper Twigg implementations.

| Method | Path | Authentication | Purpose |
|---|---|---|---|
| GET | `/api/v1/config/budgets` | API key | List active context budgets |
| POST | `/api/v1/config/budgets` | API key | Publish context budgets |
| DELETE | `/api/v1/config/budgets/active` | API key | Withdraw context budget |
| GET | `/api/v1/config/budgets/versions` | API key | List context budget versions |
| POST | `/api/v1/config/budgets/{id}/activate` | API key | Restore Budget |
| GET | `/api/v1/config/defaults` | API key | Read default policies |
| GET | `/api/v1/config/instructions` | API key | List Instruction Versions |
| POST | `/api/v1/config/instructions` | API key | Publish system instructions |
| DELETE | `/api/v1/config/instructions/active` | API key | Withdraw Instructions |
| GET | `/api/v1/config/instructions/{id}` | API key | Read instruction version |
| POST | `/api/v1/config/instructions/{id}/activate` | API key | Restore Instructions |
| GET | `/api/v1/config/namespaces` | API key | List namespaces used by chats or configuration |
| GET | `/api/v1/config/resolved` | API key | Resolve effective configuration |
| GET | `/api/v1/config/resolved-budget` | API key | Resolve effective context budget |
| GET | `/api/v1/config/retention` | API key | List Retention Versions |
| POST | `/api/v1/config/retention` | API key | Publish retention policy |
| DELETE | `/api/v1/config/retention/active` | API key | Withdraw Retention |
| POST | `/api/v1/config/retention/{id}/activate` | API key | Restore Retention |
| GET | `/api/v1/config/tools` | API key | List Tool Versions |
| POST | `/api/v1/config/tools` | API key | Publish saved tools |
| DELETE | `/api/v1/config/tools/active` | API key | Withdraw Tools |
| GET | `/api/v1/config/tools/{id}` | API key | Read tool version |
| POST | `/api/v1/config/tools/{id}/activate` | API key | Restore Tools |
| GET | `/api/v1/models` | API key | List available models |
| GET | `/api/v1/namespaces` | API key | List namespaces in use |
| GET | `/api/v1/runs/{run_id}` | API key | Inspect a run |
| GET | `/api/v1/updates` | None | List published updates |
| GET | `/api/v1/usage` | API key | Query organisation or namespace usage |

## listActiveBudgets

### GET /api/v1/config/budgets

List active context budgets

Authentication: Bearer API key required.

| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
| `namespace` | query | [Namespace](#namespace) | no |  |

| Status | Content type | Body |
|---|---|---|
| 200 | application/json | array of ([ContextBudgetResponse](#contextbudgetresponse)) |
| 400 | application/json | [ErrorEnvelope](#errorenvelope) |
| 401 | application/json | [ErrorEnvelope](#errorenvelope) |
| 404 | application/json | [ErrorEnvelope](#errorenvelope) |
| 422 | application/json | [ErrorEnvelope](#errorenvelope) |
| 500 | application/json | [ErrorEnvelope](#errorenvelope) |
| 502 | application/json | [ErrorEnvelope](#errorenvelope) |
| 503 | application/json | [ErrorEnvelope](#errorenvelope) |

## publishBudgets

### POST /api/v1/config/budgets

Publish context budgets

Authentication: Bearer API key required.

Request body: [PublishContextBudgetRequest](#publishcontextbudgetrequest).

| Field | Type | Required | Description |
|---|---|---|---|
| `budget` | [ContextBudget](#contextbudget) | yes |  |
| `model_ids` | array of ([ModelId](#modelid)) | no |  |
| `namespace` | null or [Namespace](#namespace) | no |  |

| Status | Content type | Body |
|---|---|---|
| 200 | application/json | array of ([ContextBudgetResponse](#contextbudgetresponse)) |
| 400 | application/json | [ErrorEnvelope](#errorenvelope) |
| 401 | application/json | [ErrorEnvelope](#errorenvelope) |
| 404 | application/json | [ErrorEnvelope](#errorenvelope) |
| 422 | application/json | [ErrorEnvelope](#errorenvelope) |
| 500 | application/json | [ErrorEnvelope](#errorenvelope) |
| 502 | application/json | [ErrorEnvelope](#errorenvelope) |
| 503 | application/json | [ErrorEnvelope](#errorenvelope) |

Example: publishContextBudget

```http
POST /api/v1/config/budgets
Authorization: Bearer <api key>

{
  "namespace": "acme/proj-7",
  "model_ids": [],
  "budget": {
    "version": 3,
    "headroom": 0.1,
    "history_mode": "summarize",
    "eager_compaction": {
      "mode": "at_fill",
      "at": 0.8
    },
    "max_history_tokens": null
  }
}
```

## withdrawBudget

### DELETE /api/v1/config/budgets/active

Withdraw context budget

Authentication: Bearer API key required.

| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
| `namespace` | query | [Namespace](#namespace) | no |  |
| `model` | query | [ModelId](#modelid) | no |  |

| Status | Content type | Body |
|---|---|---|
| 200 | application/json | [DeactivateResponse](#deactivateresponse) |
| 400 | application/json | [ErrorEnvelope](#errorenvelope) |
| 401 | application/json | [ErrorEnvelope](#errorenvelope) |
| 404 | application/json | [ErrorEnvelope](#errorenvelope) |
| 422 | application/json | [ErrorEnvelope](#errorenvelope) |
| 500 | application/json | [ErrorEnvelope](#errorenvelope) |
| 502 | application/json | [ErrorEnvelope](#errorenvelope) |
| 503 | application/json | [ErrorEnvelope](#errorenvelope) |

## listBudgetVersions

### GET /api/v1/config/budgets/versions

List context budget versions

Authentication: Bearer API key required.

| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
| `namespace` | query | [Namespace](#namespace) | no |  |
| `model` | query | [ModelId](#modelid) | no |  |

| Status | Content type | Body |
|---|---|---|
| 200 | application/json | array of ([ContextBudgetResponse](#contextbudgetresponse)) |
| 400 | application/json | [ErrorEnvelope](#errorenvelope) |
| 401 | application/json | [ErrorEnvelope](#errorenvelope) |
| 404 | application/json | [ErrorEnvelope](#errorenvelope) |
| 422 | application/json | [ErrorEnvelope](#errorenvelope) |
| 500 | application/json | [ErrorEnvelope](#errorenvelope) |
| 502 | application/json | [ErrorEnvelope](#errorenvelope) |
| 503 | application/json | [ErrorEnvelope](#errorenvelope) |

## restoreBudget

### POST /api/v1/config/budgets/{id}/activate

restoreBudget

Authentication: Bearer API key required.

| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
| `id` | path | [ContextBudgetPolicyId](#contextbudgetpolicyid) | yes |  |

| Status | Content type | Body |
|---|---|---|
| 200 | application/json | [ContextBudgetResponse](#contextbudgetresponse) |
| 400 | application/json | [ErrorEnvelope](#errorenvelope) |
| 401 | application/json | [ErrorEnvelope](#errorenvelope) |
| 404 | application/json | [ErrorEnvelope](#errorenvelope) |
| 422 | application/json | [ErrorEnvelope](#errorenvelope) |
| 500 | application/json | [ErrorEnvelope](#errorenvelope) |
| 502 | application/json | [ErrorEnvelope](#errorenvelope) |
| 503 | application/json | [ErrorEnvelope](#errorenvelope) |

## getConfigurationDefaults

### GET /api/v1/config/defaults

Read default policies

Authentication: Bearer API key required.

| Status | Content type | Body |
|---|---|---|
| 200 | application/json | [DefaultPoliciesResponse](#defaultpoliciesresponse) |
| 400 | application/json | [ErrorEnvelope](#errorenvelope) |
| 401 | application/json | [ErrorEnvelope](#errorenvelope) |
| 404 | application/json | [ErrorEnvelope](#errorenvelope) |
| 422 | application/json | [ErrorEnvelope](#errorenvelope) |
| 500 | application/json | [ErrorEnvelope](#errorenvelope) |
| 502 | application/json | [ErrorEnvelope](#errorenvelope) |
| 503 | application/json | [ErrorEnvelope](#errorenvelope) |

## listInstructionVersions

### GET /api/v1/config/instructions

listInstructionVersions

Authentication: Bearer API key required.

| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
| `namespace` | query | [Namespace](#namespace) | no |  |

| Status | Content type | Body |
|---|---|---|
| 200 | application/json | array of ([ConfigVersionResponse](#configversionresponse)) |
| 400 | application/json | [ErrorEnvelope](#errorenvelope) |
| 401 | application/json | [ErrorEnvelope](#errorenvelope) |
| 404 | application/json | [ErrorEnvelope](#errorenvelope) |
| 422 | application/json | [ErrorEnvelope](#errorenvelope) |
| 500 | application/json | [ErrorEnvelope](#errorenvelope) |
| 502 | application/json | [ErrorEnvelope](#errorenvelope) |
| 503 | application/json | [ErrorEnvelope](#errorenvelope) |

## publishInstructions

### POST /api/v1/config/instructions

Publish system instructions

Authentication: Bearer API key required.

Request body: [PublishInstructionRequest](#publishinstructionrequest).

| Field | Type | Required | Description |
|---|---|---|---|
| `body` | string | yes |  |
| `mode` | [ConfigMode](#configmode) | yes |  |
| `namespace` | null or [Namespace](#namespace) | no |  |

| Status | Content type | Body |
|---|---|---|
| 200 | application/json | [ConfigVersionResponse](#configversionresponse) |
| 400 | application/json | [ErrorEnvelope](#errorenvelope) |
| 401 | application/json | [ErrorEnvelope](#errorenvelope) |
| 404 | application/json | [ErrorEnvelope](#errorenvelope) |
| 422 | application/json | [ErrorEnvelope](#errorenvelope) |
| 500 | application/json | [ErrorEnvelope](#errorenvelope) |
| 502 | application/json | [ErrorEnvelope](#errorenvelope) |
| 503 | application/json | [ErrorEnvelope](#errorenvelope) |

Example: publishInstructions

```http
POST /api/v1/config/instructions
Authorization: Bearer <api key>

{
  "namespace": "acme/proj-7",
  "mode": "append",
  "body": "You are a helpful support assistant. Ask before changing customer data."
}
```

## withdrawInstructions

### DELETE /api/v1/config/instructions/active

withdrawInstructions

Authentication: Bearer API key required.

| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
| `namespace` | query | [Namespace](#namespace) | no |  |

| Status | Content type | Body |
|---|---|---|
| 200 | application/json | [DeactivateResponse](#deactivateresponse) |
| 400 | application/json | [ErrorEnvelope](#errorenvelope) |
| 401 | application/json | [ErrorEnvelope](#errorenvelope) |
| 404 | application/json | [ErrorEnvelope](#errorenvelope) |
| 422 | application/json | [ErrorEnvelope](#errorenvelope) |
| 500 | application/json | [ErrorEnvelope](#errorenvelope) |
| 502 | application/json | [ErrorEnvelope](#errorenvelope) |
| 503 | application/json | [ErrorEnvelope](#errorenvelope) |

## getInstructions

### GET /api/v1/config/instructions/{id}

Read instruction version

Authentication: Bearer API key required.

| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
| `id` | path | [SystemInstructionId](#systeminstructionid) | yes |  |

| Status | Content type | Body |
|---|---|---|
| 200 | application/json | [InstructionResponse](#instructionresponse) |
| 400 | application/json | [ErrorEnvelope](#errorenvelope) |
| 401 | application/json | [ErrorEnvelope](#errorenvelope) |
| 404 | application/json | [ErrorEnvelope](#errorenvelope) |
| 422 | application/json | [ErrorEnvelope](#errorenvelope) |
| 500 | application/json | [ErrorEnvelope](#errorenvelope) |
| 502 | application/json | [ErrorEnvelope](#errorenvelope) |
| 503 | application/json | [ErrorEnvelope](#errorenvelope) |

## restoreInstructions

### POST /api/v1/config/instructions/{id}/activate

restoreInstructions

Authentication: Bearer API key required.

| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
| `id` | path | [SystemInstructionId](#systeminstructionid) | yes |  |

| Status | Content type | Body |
|---|---|---|
| 200 | application/json | [ConfigVersionResponse](#configversionresponse) |
| 400 | application/json | [ErrorEnvelope](#errorenvelope) |
| 401 | application/json | [ErrorEnvelope](#errorenvelope) |
| 404 | application/json | [ErrorEnvelope](#errorenvelope) |
| 422 | application/json | [ErrorEnvelope](#errorenvelope) |
| 500 | application/json | [ErrorEnvelope](#errorenvelope) |
| 502 | application/json | [ErrorEnvelope](#errorenvelope) |
| 503 | application/json | [ErrorEnvelope](#errorenvelope) |

## listConfigurationNamespaces

### GET /api/v1/config/namespaces

List namespaces used by chats or configuration

Authentication: Bearer API key required.

| Status | Content type | Body |
|---|---|---|
| 200 | application/json | array of ([Namespace](#namespace)) |
| 400 | application/json | [ErrorEnvelope](#errorenvelope) |
| 401 | application/json | [ErrorEnvelope](#errorenvelope) |
| 404 | application/json | [ErrorEnvelope](#errorenvelope) |
| 422 | application/json | [ErrorEnvelope](#errorenvelope) |
| 500 | application/json | [ErrorEnvelope](#errorenvelope) |
| 502 | application/json | [ErrorEnvelope](#errorenvelope) |
| 503 | application/json | [ErrorEnvelope](#errorenvelope) |

## resolveConfiguration

### GET /api/v1/config/resolved

Resolve effective configuration

Authentication: Bearer API key required.

| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
| `namespace` | query | [Namespace](#namespace) | no |  |

| Status | Content type | Body |
|---|---|---|
| 200 | application/json | [ResolvedConfig](#resolvedconfig) |
| 400 | application/json | [ErrorEnvelope](#errorenvelope) |
| 401 | application/json | [ErrorEnvelope](#errorenvelope) |
| 404 | application/json | [ErrorEnvelope](#errorenvelope) |
| 422 | application/json | [ErrorEnvelope](#errorenvelope) |
| 500 | application/json | [ErrorEnvelope](#errorenvelope) |
| 502 | application/json | [ErrorEnvelope](#errorenvelope) |
| 503 | application/json | [ErrorEnvelope](#errorenvelope) |

## resolveContextBudget

### GET /api/v1/config/resolved-budget

Resolve effective context budget

Authentication: Bearer API key required.

| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
| `namespace` | query | [Namespace](#namespace) | no |  |
| `model` | query | [ModelId](#modelid) | no |  |

| Status | Content type | Body |
|---|---|---|
| 200 | application/json | [ResolvedBudget](#resolvedbudget) |
| 400 | application/json | [ErrorEnvelope](#errorenvelope) |
| 401 | application/json | [ErrorEnvelope](#errorenvelope) |
| 404 | application/json | [ErrorEnvelope](#errorenvelope) |
| 422 | application/json | [ErrorEnvelope](#errorenvelope) |
| 500 | application/json | [ErrorEnvelope](#errorenvelope) |
| 502 | application/json | [ErrorEnvelope](#errorenvelope) |
| 503 | application/json | [ErrorEnvelope](#errorenvelope) |

## listRetentionVersions

### GET /api/v1/config/retention

listRetentionVersions

Authentication: Bearer API key required.

| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
| `namespace` | query | [Namespace](#namespace) | no |  |

| Status | Content type | Body |
|---|---|---|
| 200 | application/json | array of ([RetentionPolicyResponse](#retentionpolicyresponse)) |
| 400 | application/json | [ErrorEnvelope](#errorenvelope) |
| 401 | application/json | [ErrorEnvelope](#errorenvelope) |
| 404 | application/json | [ErrorEnvelope](#errorenvelope) |
| 422 | application/json | [ErrorEnvelope](#errorenvelope) |
| 500 | application/json | [ErrorEnvelope](#errorenvelope) |
| 502 | application/json | [ErrorEnvelope](#errorenvelope) |
| 503 | application/json | [ErrorEnvelope](#errorenvelope) |

## publishRetention

### POST /api/v1/config/retention

Publish retention policy

Authentication: Bearer API key required.

Request body: [PublishRetentionPolicyRequest](#publishretentionpolicyrequest).

| Field | Type | Required | Description |
|---|---|---|---|
| `namespace` | null or [Namespace](#namespace) | no |  |
| `rules` | [RetentionRules](#retentionrules) | yes | The stored document. Private fields: holding one is proof it parsed. |

| Status | Content type | Body |
|---|---|---|
| 200 | application/json | [RetentionPolicyResponse](#retentionpolicyresponse) |
| 400 | application/json | [ErrorEnvelope](#errorenvelope) |
| 401 | application/json | [ErrorEnvelope](#errorenvelope) |
| 404 | application/json | [ErrorEnvelope](#errorenvelope) |
| 422 | application/json | [ErrorEnvelope](#errorenvelope) |
| 500 | application/json | [ErrorEnvelope](#errorenvelope) |
| 502 | application/json | [ErrorEnvelope](#errorenvelope) |
| 503 | application/json | [ErrorEnvelope](#errorenvelope) |

Example: publishRetentionPolicy

```http
POST /api/v1/config/retention
Authorization: Bearer <api key>

{
  "namespace": "acme/proj-7",
  "rules": {
    "version": 3,
    "media": {
      "default": 0,
      "rules": [
        {
          "mime": "image/*",
          "retain": 0
        },
        {
          "mime": "application/pdf",
          "retain": 0
        }
      ]
    },
    "tools": {
      "default": {
        "result": {
          "mode": "keep"
        },
        "arguments": {
          "mode": "keep"
        }
      },
      "rules": [],
      "errors": null,
      "mark_truncations": true
    },
    "text": {
      "prompt": {
        "mode": "keep"
      },
      "message": {
        "mode": "keep"
      }
    },
    "reasoning": 0.3,
    "verbatim_within": 0.3,
    "anchor_parts": 0
  }
}
```

## withdrawRetention

### DELETE /api/v1/config/retention/active

withdrawRetention

Authentication: Bearer API key required.

| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
| `namespace` | query | [Namespace](#namespace) | no |  |

| Status | Content type | Body |
|---|---|---|
| 200 | application/json | [DeactivateResponse](#deactivateresponse) |
| 400 | application/json | [ErrorEnvelope](#errorenvelope) |
| 401 | application/json | [ErrorEnvelope](#errorenvelope) |
| 404 | application/json | [ErrorEnvelope](#errorenvelope) |
| 422 | application/json | [ErrorEnvelope](#errorenvelope) |
| 500 | application/json | [ErrorEnvelope](#errorenvelope) |
| 502 | application/json | [ErrorEnvelope](#errorenvelope) |
| 503 | application/json | [ErrorEnvelope](#errorenvelope) |

## restoreRetention

### POST /api/v1/config/retention/{id}/activate

restoreRetention

Authentication: Bearer API key required.

| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
| `id` | path | [RetentionPolicyId](#retentionpolicyid) | yes |  |

| Status | Content type | Body |
|---|---|---|
| 200 | application/json | [RetentionPolicyResponse](#retentionpolicyresponse) |
| 400 | application/json | [ErrorEnvelope](#errorenvelope) |
| 401 | application/json | [ErrorEnvelope](#errorenvelope) |
| 404 | application/json | [ErrorEnvelope](#errorenvelope) |
| 422 | application/json | [ErrorEnvelope](#errorenvelope) |
| 500 | application/json | [ErrorEnvelope](#errorenvelope) |
| 502 | application/json | [ErrorEnvelope](#errorenvelope) |
| 503 | application/json | [ErrorEnvelope](#errorenvelope) |

## listToolVersions

### GET /api/v1/config/tools

listToolVersions

Authentication: Bearer API key required.

| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
| `namespace` | query | [Namespace](#namespace) | no |  |

| Status | Content type | Body |
|---|---|---|
| 200 | application/json | array of ([ConfigVersionResponse](#configversionresponse)) |
| 400 | application/json | [ErrorEnvelope](#errorenvelope) |
| 401 | application/json | [ErrorEnvelope](#errorenvelope) |
| 404 | application/json | [ErrorEnvelope](#errorenvelope) |
| 422 | application/json | [ErrorEnvelope](#errorenvelope) |
| 500 | application/json | [ErrorEnvelope](#errorenvelope) |
| 502 | application/json | [ErrorEnvelope](#errorenvelope) |
| 503 | application/json | [ErrorEnvelope](#errorenvelope) |

## publishTools

### POST /api/v1/config/tools

Publish saved tools

Authentication: Bearer API key required.

Request body: [PublishToolsRequest](#publishtoolsrequest).

| Field | Type | Required | Description |
|---|---|---|---|
| `definitions` | array of ([ToolDefinition](#tooldefinition)) | yes |  |
| `mode` | [ConfigMode](#configmode) | yes |  |
| `namespace` | null or [Namespace](#namespace) | no |  |

| Status | Content type | Body |
|---|---|---|
| 200 | application/json | [ConfigVersionResponse](#configversionresponse) |
| 400 | application/json | [ErrorEnvelope](#errorenvelope) |
| 401 | application/json | [ErrorEnvelope](#errorenvelope) |
| 404 | application/json | [ErrorEnvelope](#errorenvelope) |
| 422 | application/json | [ErrorEnvelope](#errorenvelope) |
| 500 | application/json | [ErrorEnvelope](#errorenvelope) |
| 502 | application/json | [ErrorEnvelope](#errorenvelope) |
| 503 | application/json | [ErrorEnvelope](#errorenvelope) |

Example: publishSavedTools

```http
POST /api/v1/config/tools
Authorization: Bearer <api key>

{
  "namespace": "acme/proj-7",
  "mode": "append",
  "definitions": [
    {
      "name": "lookup_order",
      "description": "Find an order by its ID.",
      "input_schema": {
        "type": "object",
        "properties": {
          "order_id": {
            "type": "string"
          }
        },
        "required": [
          "order_id"
        ]
      }
    }
  ]
}
```

## withdrawTools

### DELETE /api/v1/config/tools/active

withdrawTools

Authentication: Bearer API key required.

| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
| `namespace` | query | [Namespace](#namespace) | no |  |

| Status | Content type | Body |
|---|---|---|
| 200 | application/json | [DeactivateResponse](#deactivateresponse) |
| 400 | application/json | [ErrorEnvelope](#errorenvelope) |
| 401 | application/json | [ErrorEnvelope](#errorenvelope) |
| 404 | application/json | [ErrorEnvelope](#errorenvelope) |
| 422 | application/json | [ErrorEnvelope](#errorenvelope) |
| 500 | application/json | [ErrorEnvelope](#errorenvelope) |
| 502 | application/json | [ErrorEnvelope](#errorenvelope) |
| 503 | application/json | [ErrorEnvelope](#errorenvelope) |

## getTools

### GET /api/v1/config/tools/{id}

Read tool version

Authentication: Bearer API key required.

| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
| `id` | path | [ToolSchemaId](#toolschemaid) | yes |  |

| Status | Content type | Body |
|---|---|---|
| 200 | application/json | [ToolSchemaResponse](#toolschemaresponse) |
| 400 | application/json | [ErrorEnvelope](#errorenvelope) |
| 401 | application/json | [ErrorEnvelope](#errorenvelope) |
| 404 | application/json | [ErrorEnvelope](#errorenvelope) |
| 422 | application/json | [ErrorEnvelope](#errorenvelope) |
| 500 | application/json | [ErrorEnvelope](#errorenvelope) |
| 502 | application/json | [ErrorEnvelope](#errorenvelope) |
| 503 | application/json | [ErrorEnvelope](#errorenvelope) |

## restoreTools

### POST /api/v1/config/tools/{id}/activate

restoreTools

Authentication: Bearer API key required.

| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
| `id` | path | [ToolSchemaId](#toolschemaid) | yes |  |

| Status | Content type | Body |
|---|---|---|
| 200 | application/json | [ConfigVersionResponse](#configversionresponse) |
| 400 | application/json | [ErrorEnvelope](#errorenvelope) |
| 401 | application/json | [ErrorEnvelope](#errorenvelope) |
| 404 | application/json | [ErrorEnvelope](#errorenvelope) |
| 422 | application/json | [ErrorEnvelope](#errorenvelope) |
| 500 | application/json | [ErrorEnvelope](#errorenvelope) |
| 502 | application/json | [ErrorEnvelope](#errorenvelope) |
| 503 | application/json | [ErrorEnvelope](#errorenvelope) |

## listModels

### GET /api/v1/models

Returns active models available for new requests, with the caller's prices.

Authentication: Bearer API key required.

| Status | Content type | Body |
|---|---|---|
| 200 | application/json | array of ([CatalogueModel](#cataloguemodel)) |
| 401 | application/json | [ErrorEnvelope](#errorenvelope) |
| 422 | application/json | [ErrorEnvelope](#errorenvelope) |
| 500 | application/json | [ErrorEnvelope](#errorenvelope) |
| 503 | application/json | [ErrorEnvelope](#errorenvelope) |

## listNamespaces

### GET /api/v1/namespaces

Unpaged, deliberately. A namespace count is bounded by how a customer scopes
rather than by how much they use the product, and a cursor on a list nobody
will page is API surface with no reader.

Authentication: Bearer API key required.

| Status | Content type | Body |
|---|---|---|
| 200 | application/json | [NamespacePage](#namespacepage) |
| 401 | application/json | [ErrorEnvelope](#errorenvelope) |
| 422 | application/json | [ErrorEnvelope](#errorenvelope) |
| 500 | application/json | [ErrorEnvelope](#errorenvelope) |
| 503 | application/json | [ErrorEnvelope](#errorenvelope) |

Example: namespaces

```http
GET /api/v1/namespaces
Authorization: Bearer <api key>

200 →
{
  "data": [
    "acme/proj-7",
    "acme/prdo"
  ]
}
```

## getRun

### GET /api/v1/runs/{run_id}

`404` for a run in another organisation, exactly as for one that does not
exist. The scoping is in the query rather than a check after it, so there is
no timing difference and no way to probe for someone else's run id.

Authentication: Bearer API key required.

| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
| `run_id` | path | string (uuid) | yes | Resource identifier |

| Status | Content type | Body |
|---|---|---|
| 200 | application/json | [RunInspection](#runinspection) |
| 400 | application/json | [ErrorEnvelope](#errorenvelope) |
| 401 | application/json | [ErrorEnvelope](#errorenvelope) |
| 404 | application/json | [ErrorEnvelope](#errorenvelope) |
| 422 | application/json | [ErrorEnvelope](#errorenvelope) |
| 500 | application/json | [ErrorEnvelope](#errorenvelope) |
| 503 | application/json | [ErrorEnvelope](#errorenvelope) |

## listPublicUpdates

### GET /api/v1/updates

List published updates

Authentication: Public; no API key required.

| Status | Content type | Body |
|---|---|---|
| 200 | application/json | array of ([PublicPlatformUpdate](#publicplatformupdate)) |
| 500 | application/json | [ErrorEnvelope](#errorenvelope) |

## queryUsage

### GET /api/v1/usage

Query organisation or namespace usage

Authentication: Bearer API key required.

| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
| `range` | query | `"24h"` or `"7d"` or `"30d"` or `"90d"` | yes |  |
| `bin` | query | [UsageBin](#usagebin) | no | Absent means "do not bin" — the window's totals, or one row per group. |
| `group_by` | query | [UsageDimension](#usagedimension) | no | Absent means "do not split" — the organisation as a whole. |
| `namespace` | query | [Namespace](#namespace) | no | Absent is the whole organisation. Present reports the namespace **and its subtree**, which is a different question from the namespace alone and the one a customer with per-project paths is asking. |

| Status | Content type | Body |
|---|---|---|
| 200 | application/json | [UsageReport](#usagereport) |
| 401 | application/json | [ErrorEnvelope](#errorenvelope) |
| 422 | application/json | [ErrorEnvelope](#errorenvelope) |
| 502 | application/json | [ErrorEnvelope](#errorenvelope) |
| 503 | application/json | [ErrorEnvelope](#errorenvelope) |

Example: queryNamespaceUsage

```http
GET /api/v1/usage?range=7d&namespace=acme%2Fproj-7&group_by=namespace
Authorization: Bearer <api key>
```

## Streaming events

Frames use `event: <name>` and `data: <JSON>`, separated by a blank line. Content blocks can repeat; warnings may repeat after compaction.

| Event | Payload | Meaning |
|---|---|---|
| `run` | chat_id, closed_tool_calls, run_id | Always first: identifiers and tool calls closed by this submission. |
| `config_warnings` | warnings | Configuration warnings; emitted second, and may repeat after compaction. |
| `translation_warnings` | warnings | Model compatibility warnings before content starts. |
| `compacting` | blocks, in_progress_elsewhere | Compaction work the request is waiting for. |
| `block_start` | [BlockStart](#blockstart) |  |
| `delta` | [Delta](#delta) |  |
| `block_stop` | object |  |
| `done` | compaction, cost, cost_currency, model_served, pending_tool_calls, provider_response_id, server_tools, stop_reason, usage | Terminal success. Null cost means settlement is pending; inspect the run later. |
| `error` | code, details, hint, message | Terminal failure after the stream has opened. |

## Schemas

## ApiKeyId

string (uuid)



## ApiVersion

object



| Field | Type | Required | Description |
|---|---|---|---|
| `api_version` | string | yes |  |
| `commit` | string | yes |  |
| `version` | string | yes |  |

## BalanceEntryType

`"usage"` or `"compaction"` or `"topup"` or `"refund"` or `"adjustment"` or `"promotional_credit"`



## BilledRates

object

Per-million rates as the customer pays them.

| Field | Type | Required | Description |
|---|---|---|---|
| `cache_read` | null or [RatePerMillion](#ratepermillion) | no | Absent when the provider does not meter it. |
| `cache_write` | null or [RatePerMillion](#ratepermillion) | no | Price in the stated currency per one million tokens. |
| `currency` | string | yes |  |
| `input` | [RatePerMillion](#ratepermillion) | yes | Price in the stated currency per one million tokens. |
| `output` | [RatePerMillion](#ratepermillion) | yes | Price in the stated currency per one million tokens. |

## BilledTier

object

One pricing condition, in the customer's terms. Rates here are billed too.

| Field | Type | Required | Description |
|---|---|---|---|
| `cache_read` | null or [RatePerMillion](#ratepermillion) | no | Price in the stated currency per one million tokens. |
| `cache_write` | null or [RatePerMillion](#ratepermillion) | no | Price in the stated currency per one million tokens. |
| `id` | string | yes |  |
| `input` | null or [RatePerMillion](#ratepermillion) | no | Price in the stated currency per one million tokens. |
| `max` | integer,null (int64); minimum 0 | no |  |
| `metric` | string | yes |  |
| `min` | integer (int64); minimum 0 | yes |  |
| `mode` | [TierMode](#tiermode) | yes |  |
| `output` | null or [RatePerMillion](#ratepermillion) | no | Price in the stated currency per one million tokens. |

## BlockStart

object or object or object or object



oneOf alternative 1: object



| Field | Type | Required | Description |
|---|---|---|---|
| `kind` | `"text"` | yes |  |

oneOf alternative 2: object



| Field | Type | Required | Description |
|---|---|---|---|
| `kind` | `"reasoning"` | yes |  |

oneOf alternative 3: object



| Field | Type | Required | Description |
|---|---|---|---|
| `kind` | `"refusal"` | yes |  |

oneOf alternative 4: object



| Field | Type | Required | Description |
|---|---|---|---|
| `kind` | `"tool_call"` | yes |  |
| `tool_name` | [ToolName](#toolname) | yes |  |
| `tool_use_id` | [ToolUseId](#tooluseid) | yes |  |

## BudgetFraction

number (float)



## BudgetSource

object or object

The configuration scope and model tag that supplied the resolved context budget.

oneOf alternative 1: object

No level published one. There is no row, which is why this is a variant
and not a null id.

| Field | Type | Required | Description |
|---|---|---|---|
| `kind` | `"platform_default"` | yes |  |

oneOf alternative 2: object

A published policy won, whole.

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | [ContextBudgetPolicyId](#contextbudgetpolicyid) | yes |  |
| `kind` | `"policy"` | yes |  |
| `namespace` | null or [Namespace](#namespace) | no | The level it was published at. `None` is org-global. |
| `tagged_model` | null or [ModelId](#modelid) | no | `Some` when the level's tag for this model won over its untagged policy; `None` when the level's untagged policy is what won. |

## Catalogue

object

The whole catalogue plus the margin its prices include.

| Field | Type | Required | Description |
|---|---|---|---|
| `markup_percent` | [MarkupPercent](#markuppercent) | yes | The platform-default markup the listed prices already include. Zero means the prices are provider cost. |
| `models` | array of ([CatalogueModel](#cataloguemodel)) | yes |  |
| `organisation_specific` | boolean | yes | Present when a signed-in caller's organisation has its own rate; the prices then reflect that rate rather than the default. |

## CatalogueModel

object

One catalogue entry, in full. The list and the detail share it: a detail
page is not a different fact, only a bigger frame around the same one.

| Field | Type | Required | Description |
|---|---|---|---|
| `avg_time_to_first_token_ms` | number,null (double) | no | Mean milliseconds from provider request to first nonempty output delta, over successful requests completed in the last 24 hours. Includes retry waits. Null when no timing samples exist. Refreshed every 15 minutes. |
| `context_window` | integer (int32) | yes |  |
| `display_name` | string | yes |  |
| `icon_url` | string,null | no |  |
| `id` | [ModelId](#modelid) | yes |  |
| `max_output_tokens` | integer (int32) | yes |  |
| `max_reasoning_effort` | string,null | no |  |
| `max_tool_definitions` | integer,null (int32) | no |  |
| `media` | [MediaCapabilities](#mediacapabilities) | yes |  |
| `model_family` | string | yes |  |
| `name` | string | yes | The identifier to send as `model` on a run. |
| `provider` | [Provider](#provider) | yes |  |
| `provider_label` | string | yes |  |
| `rates` | [BilledRates](#billedrates) | yes | Per-million rates as the customer pays them. |
| `supports_reasoning` | boolean | yes |  |
| `supports_tools` | boolean | yes |  |
| `thinking` | string | yes |  |
| `tiers` | array of ([BilledTier](#billedtier)) | yes |  |
| `uptime` | null or [Uptime](#uptime) | no | Observed provider request success rate over the past 24 hours, after retries, as a fraction from 0 to 1. Refreshed every 15 minutes. Null means no eligible completed requests. Local validation failures, interrupted runs and unclassified historical failures are excluded; this is not time-based uptime. |

## CatalogueModelResponse

[CatalogueModel](#cataloguemodel) and object



allOf alternative 1: [CatalogueModel](#cataloguemodel)



| Field | Type | Required | Description |
|---|---|---|---|
| `avg_time_to_first_token_ms` | number,null (double) | no | Mean milliseconds from provider request to first nonempty output delta, over successful requests completed in the last 24 hours. Includes retry waits. Null when no timing samples exist. Refreshed every 15 minutes. |
| `context_window` | integer (int32) | yes |  |
| `display_name` | string | yes |  |
| `icon_url` | string,null | no |  |
| `id` | [ModelId](#modelid) | yes |  |
| `max_output_tokens` | integer (int32) | yes |  |
| `max_reasoning_effort` | string,null | no |  |
| `max_tool_definitions` | integer,null (int32) | no |  |
| `media` | [MediaCapabilities](#mediacapabilities) | yes |  |
| `model_family` | string | yes |  |
| `name` | string | yes | The identifier to send as `model` on a run. |
| `provider` | [Provider](#provider) | yes |  |
| `provider_label` | string | yes |  |
| `rates` | [BilledRates](#billedrates) | yes | Per-million rates as the customer pays them. |
| `supports_reasoning` | boolean | yes |  |
| `supports_tools` | boolean | yes |  |
| `thinking` | string | yes |  |
| `tiers` | array of ([BilledTier](#billedtier)) | yes |  |
| `uptime` | null or [Uptime](#uptime) | no | Observed provider request success rate over the past 24 hours, after retries, as a fraction from 0 to 1. Refreshed every 15 minutes. Null means no eligible completed requests. Local validation failures, interrupted runs and unclassified historical failures are excluded; this is not time-based uptime. |

allOf alternative 2: object



| Field | Type | Required | Description |
|---|---|---|---|
| `markup_percent` | [MarkupPercent](#markuppercent) | yes |  |

## ChatCreated

object

The new chat, including its immutable namespace, labels, metadata and creation time.

| Field | Type | Required | Description |
|---|---|---|---|
| `created_at` | string (date-time) | yes |  |
| `description` | null or [ChatDescription](#chatdescription) | yes |  |
| `id` | [ChatId](#chatid) | yes |  |
| `namespace` | null or [Namespace](#namespace) | yes |  |
| `title` | null or [ChatTitle](#chattitle) | yes |  |
| `user_metadata` | [UserMetadata](#usermetadata) | yes | Echoed from the request, like the labels above — but for a different reason. This one *is* readable from the row; it is echoed so the response reflects what the customer sent rather than what `jsonb` normalisation made of it, and so all three optional fields behave the same way here. |

## ChatDescription

string



Constraints:
```json
{
  "maxLength": 200,
  "minLength": 1
}
```

## ChatId

string (uuid)



## ChatPage

object

One page of chats.

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | array of ([ChatSummary](#chatsummary)) | yes | Newest first, whichever direction was paged. Named `data` rather than `chats` so one pagination helper works across every list endpoint we add — the same reason the upstream APIs do it. |
| `first_id` | null or [ChatId](#chatid) | yes | Id of the first item in `data`. Pass as `before_id` to page backwards. `null` when the page is empty. |
| `has_more` | boolean | yes | Whether more chats exist **in the direction being paged**. |
| `last_id` | null or [ChatId](#chatid) | yes | Id of the last item in `data`. Pass as `after_id` for the next page. |

## ChatPartId

string (uuid)



## ChatSummary

object

One chat in a listing.

Everything optional here is optional in the customer's data, not in our
response: `null` means they did not set it, and the field is always present
so a client never has to distinguish "absent" from "unset".

| Field | Type | Required | Description |
|---|---|---|---|
| `created_at` | string (date-time) | yes |  |
| `description` | null or [ChatDescription](#chatdescription) | yes |  |
| `id` | [ChatId](#chatid) | yes |  |
| `length` | integer (int64) | yes | How many parts the chat's ledger holds. A count of rows, not of tokens, characters or messages — one assistant turn can be several parts (reasoning, then text, then a tool call), so this is larger than the number of exchanges a customer would count by eye. It is meant as an "is there anything in here, and roughly how much" signal, not as an addressing scheme: ordinals are never renumbered and deletes leave gaps, so the last part's ordinal is not `length - 1`. |
| `namespace` | null or [Namespace](#namespace) | yes |  |
| `title` | null or [ChatTitle](#chattitle) | yes |  |
| `updated_at` | string (date-time) | yes |  |
| `user_metadata` | [UserMetadata](#usermetadata) | yes | Customer JSON object, at most 1024 bytes when serialized compactly. Defaults to an empty object. |

## ChatTitle

string



Constraints:
```json
{
  "maxLength": 50,
  "minLength": 1
}
```

## Compaction

object



| Field | Type | Required | Description |
|---|---|---|---|
| `blocks_generated` | integer; minimum 0 | yes |  |
| `blocks_reused` | integer; minimum 0 | yes |  |
| `in_progress_elsewhere` | integer; minimum 0 | yes |  |

## ConfigMode

`"append"` or `"replace"`



## ConfigVersionResponse

object

One published version, without its payload. The body of an instruction and
the definitions of a bundle are fetched per version, not listed — a history
endpoint that returned every body would fetch and decrypt a blob per row.

| Field | Type | Required | Description |
|---|---|---|---|
| `created_at` | string (date-time) | yes |  |
| `estimated_tokens` | [TokenEstimate](#tokenestimate) | yes | A count of estimated tokens. Always non-negative; saturates rather than overflowing, because a budget calculation must never wrap into a small number and wave through a request that cannot fit. |
| `id` | string | yes |  |
| `is_active` | boolean | yes |  |
| `mode` | null or [ConfigMode](#configmode) | no | How this version combines with the org-global one — `None` for the two surfaces that do not combine at all (retention policies and context budgets), where a mode would be a field the customer could set and nothing would read. |
| `namespace` | null or [Namespace](#namespace) | no |  |
| `version` | integer (int32) | yes |  |

## ConfigWarning

object or object or object or object or object or object

Something legal but worth saying out loud.

oneOf alternative 1: object

A namespace tool replaced a global tool of the same name. The union is
keyed on name, so this is how an override is *meant* to work — but it is
also exactly what a typo in a tool name looks like from the outside.

| Field | Type | Required | Description |
|---|---|---|---|
| `code` | `"tool_shadowed"` | yes |  |
| `name` | [ToolName](#toolname) | yes |  |
| `namespace` | [Namespace](#namespace) | yes |  |

oneOf alternative 2: object

The namespace's instruction is in `replace` mode, so the org-global
instruction is not being sent at all.

| Field | Type | Required | Description |
|---|---|---|---|
| `code` | `"global_instruction_replaced"` | yes |  |
| `namespace` | [Namespace](#namespace) | yes |  |

oneOf alternative 3: object

The namespace's tool bundle is in `replace` mode, so the global bundle
is not being sent at all — including tools the customer may assume are
always present.

| Field | Type | Required | Description |
|---|---|---|---|
| `code` | `"global_tools_replaced"` | yes |  |
| `dropped` | integer; minimum 0 | yes |  |
| `namespace` | [Namespace](#namespace) | yes |  |

oneOf alternative 4: object

A nearer level's retention policy displaced a farther level's. This is how
the override is *meant* to work: a policy is a complete decision table and
is selected whole, so the farther one contributes nothing — not its rules
and not its defaults. It still warns, because the result looks identical
either way, and "my org-wide rule stopped applying" is otherwise
undiagnosable.

Both levels are named because at depth neither is guessable from the
other: "a namespace policy won" does not say which of four ancestors
stopped applying.

| Field | Type | Required | Description |
|---|---|---|---|
| `code` | `"retention_policy_overridden"` | yes |  |
| `displaced` | null or [Namespace](#namespace) | no | The nearest level whose policy is not being applied. |
| `namespace` | null or [Namespace](#namespace) | no | The level whose policy is in force. `None` is org-global. |

oneOf alternative 5: object

The budget in force did not come from the plainest place: either an
ancestor level supplied it, or a model tag at the winning level displaced
that level's untagged policy — or both.

Two facts, one warning, because either alone misleads. "The budget came
from `acme`" and "the budget came from `acme`, tagged to this model" send
an admin to different rows, and the second is the only one that explains
why a sibling model in the same namespace behaves differently.

| Field | Type | Required | Description |
|---|---|---|---|
| `code` | `"context_budget_resolved_at"` | yes |  |
| `namespace` | null or [Namespace](#namespace) | no | The level that supplied it. `None` is the org-global scope. |
| `tagged_model` | null or [ModelId](#modelid) | no | `Some` when that level's tag for this model beat its untagged policy. |

oneOf alternative 6: object

Oldest ledger parts were omitted because no complete context fitted.

| Field | Type | Required | Description |
|---|---|---|---|
| `code` | `"history_dropped"` | yes |  |
| `dropped` | integer (int64) | yes |  |
| `end_ordinal` | integer (int64) | yes |  |
| `start_ordinal` | integer (int64) | yes |  |

## ContextBudget

object



| Field | Type | Required | Description |
|---|---|---|---|
| `eager_compaction` | [EagerCompaction](#eagercompaction) | no |  |
| `headroom` | [BudgetFraction](#budgetfraction) | yes |  |
| `history_mode` | [HistoryMode](#historymode) | no |  |
| `max_history_tokens` | integer,null (int32); minimum 32000 | no |  |
| `version` | integer (int32); minimum 3; maximum 3 | yes |  |

Constraints:
```json
{
  "additionalProperties": false
}
```

## ContextBudgetPolicyId

string (uuid)



## ContextBudgetResponse

[ConfigVersionResponse](#configversionresponse) and object

One context-budget version with its document and the model it is tagged to.

allOf alternative 1: [ConfigVersionResponse](#configversionresponse)



| Field | Type | Required | Description |
|---|---|---|---|
| `created_at` | string (date-time) | yes |  |
| `estimated_tokens` | [TokenEstimate](#tokenestimate) | yes | A count of estimated tokens. Always non-negative; saturates rather than overflowing, because a budget calculation must never wrap into a small number and wave through a request that cannot fit. |
| `id` | string | yes |  |
| `is_active` | boolean | yes |  |
| `mode` | null or [ConfigMode](#configmode) | no | How this version combines with the org-global one — `None` for the two surfaces that do not combine at all (retention policies and context budgets), where a mode would be a field the customer could set and nothing would read. |
| `namespace` | null or [Namespace](#namespace) | no |  |
| `version` | integer (int32) | yes |  |

allOf alternative 2: object



| Field | Type | Required | Description |
|---|---|---|---|
| `budget` | [ContextBudget](#contextbudget) | yes |  |
| `model_id` | null or [ModelId](#modelid) | no | `None` is the scope default, not an untagged leftover. |

## CreateChatRequest

object

Body for `create`. Every field is optional — the minimum viable request is
`{}`, which opens a chat in the org's default scope with no labels.

The org is deliberately absent: it comes off the authenticated key, so a
caller cannot open a chat anywhere but their own organisation.

| Field | Type | Required | Description |
|---|---|---|---|
| `description` | null or [ChatDescription](#chatdescription) | no |  |
| `namespace` | null or [Namespace](#namespace) | no | The scope this chat's configuration resolves against, and the prefix it will be listed under. Immutable once set. Note this field does **not** coerce an empty string to "no namespace", where `title` and `description` below do. A namespace is load-bearing — it decides which system instructions and tools every future run on this chat receives — so a client whose interpolation produced `""` should get a 422 rather than a chat silently pinned to the org-global scope. The labels are decorative, and an untouched form field is not worth a rejection. |
| `title` | null or [ChatTitle](#chattitle) | no |  |
| `user_metadata` | [UserMetadata](#usermetadata) | no | Arbitrary customer JSON, capped at a kilobyte. Twigg never reads it — it is somewhere to hang a ticket reference or a UI flag so the customer does not have to keep a parallel table keyed by our ids. Absent means an empty object, never `null`, so "no tags" is one stored value rather than two. |

Constraints:
```json
{
  "additionalProperties": false
}
```

## CreateResponseRequest

object

Submit a turn or retry the latest failed run. Responses always use server-sent events.

| Field | Type | Required | Description |
|---|---|---|---|
| `idempotency_key` | null or [IdempotencyKey](#idempotencykey) | no | Prevents a second run for the same key. Org-scoped and honoured for 24 hours. Reusing a key returns HTTP 409 conflict naming the original run in the message; it does not replay the SSE stream. After a dropped connection, inspect that run with GET /api/v1/runs/{run_id} and read chat history for persisted output. Do not generate a new key merely to retry an uncertain submission. |
| `input` | array of (object or object); minimum 0 items; maximum 128 items | yes |  |
| `max_tokens` | integer,null (int32); minimum 1 | no | Output ceiling. Only ever **lowered** — by the model's own ceiling, and by what the balance can pay for. |
| `model` | string | yes | The catalogue `name`, as `GET /api/v1/models` lists it — **not** the string the provider spells it with. The two are separate columns precisely so this one can stay stable: the wire name carries snapshot dates that vendors move under us, and a customer who pinned one would break the day an alias was re-pointed. Only an **active** model resolves; a withdrawn one would open a turn nothing can continue. |
| `reasoning_effort` | null or [ReasoningEffort](#reasoningeffort) | no | How hard the model should think, where it can. Coerced with a warning rather than rejected when the model cannot honour it. |
| `retry_of` | null or [RunId](#runid) | no | Retry the latest failed customer run on this chat without appending input. Send input: [] and a new idempotency key. Supply the model and any request-level tools again; the retry uses the current configuration. |
| `tools` | array,null | no | Tools for this request alone, in the same shape a published bundle uses. The **nearest** level there is, so one of these shadows a namespace or org-global tool of the same name. They are charged against the context budget like any other tool, because the provider charges for them. |
| `user_metadata` | [UserMetadata](#usermetadata) | no | Arbitrary customer JSON, attached to every part this turn writes. |

Constraints:
```json
{
  "additionalProperties": false
}
```

## DeactivateResponse

object

Whether anything was actually withdrawn. The call is idempotent, so `false`
means "there was no override", not a failure.

| Field | Type | Required | Description |
|---|---|---|---|
| `was_active` | boolean | yes |  |

## DefaultPoliciesResponse

object

The policies in force for an org that has published nothing.

Both are the platform documents themselves, not a copy: the console seeds its
editors from this so the form a customer first sees is the policy actually
being applied to their chats. Editing the shipped documents moves both at
once, which is the point — a hard-coded copy in the client is a second
statement of the same fact, and the two only ever agree until someone edits
one of them.

| Field | Type | Required | Description |
|---|---|---|---|
| `budget` | [ContextBudget](#contextbudget) | yes |  |
| `retention` | [RetentionRules](#retentionrules) | yes | The stored document. Private fields: holding one is proof it parsed. |

## Delta

object or object or object or object



oneOf alternative 1: object



| Field | Type | Required | Description |
|---|---|---|---|
| `kind` | `"text"` | yes |  |
| `text` | string | yes |  |

oneOf alternative 2: object



| Field | Type | Required | Description |
|---|---|---|---|
| `kind` | `"reasoning"` | yes |  |
| `text` | string | yes |  |

oneOf alternative 3: object



| Field | Type | Required | Description |
|---|---|---|---|
| `kind` | `"tool_input"` | yes |  |
| `text` | string | yes |  |

oneOf alternative 4: object



| Field | Type | Required | Description |
|---|---|---|---|
| `kind` | `"refusal"` | yes |  |
| `text` | string | yes |  |

## Discovery

object



| Field | Type | Required | Description |
|---|---|---|---|
| `docs` | string | yes |  |
| `llms_txt` | string | yes |  |
| `openapi` | string | yes |  |
| `service` | string | yes |  |
| `versions` | object | yes |  |

## EagerCompaction

object or object



oneOf alternative 1: object



| Field | Type | Required | Description |
|---|---|---|---|
| `mode` | `"off"` | yes |  |

oneOf alternative 2: object



| Field | Type | Required | Description |
|---|---|---|---|
| `at` | [BudgetFraction](#budgetfraction) | yes |  |
| `mode` | `"at_fill"` | yes |  |

## ErrorBody

object

The customer-visible error payload, shared by HTTP errors and SSE failures.

| Field | Type | Required | Description |
|---|---|---|---|
| `code` | string | yes |  |
| `details` | any JSON | no |  |
| `hint` | string,null | no |  |
| `message` | string | yes |  |

## ErrorEnvelope

object



| Field | Type | Required | Description |
|---|---|---|---|
| `error` | [ErrorBody](#errorbody) | yes | The customer-visible error payload, shared by HTTP errors and SSE failures. |

## HistoryBody

object or object or object or object or object

History content: a user prompt, tool result, assistant message, reasoning or tool call.

oneOf alternative 1: object

A user prompt.

| Field | Type | Required | Description |
|---|---|---|---|
| `text` | string | yes |  |
| `type` | `"prompt"` | yes |  |

oneOf alternative 2: object

A tool result. `tombstone_reason` is present only when Twigg closed a
pending call whose result never arrived; omit it when submitting results.

| Field | Type | Required | Description |
|---|---|---|---|
| `is_error` | boolean | yes |  |
| `text` | string | yes |  |
| `tombstone_reason` | null or [TombstoneReason](#tombstonereason) | no |  |
| `tool_name` | string | yes |  |
| `tool_use_id` | [ToolUseId](#tooluseid) | yes |  |
| `trust` | [TrustLevel](#trustlevel) | yes |  |
| `type` | `"tool_result"` | yes |  |

oneOf alternative 3: object

The model's own turn.

| Field | Type | Required | Description |
|---|---|---|---|
| `refusal` | string,null | no |  |
| `text` | string | yes |  |
| `type` | `"message"` | yes |  |

oneOf alternative 4: object

Reasoning text exposed by the provider. Text may be empty when the
provider hides its reasoning. Replay signatures are not returned.

| Field | Type | Required | Description |
|---|---|---|---|
| `text` | string | yes |  |
| `type` | `"reasoning"` | yes |  |

oneOf alternative 5: object

A tool call the model made, for the customer to execute.

| Field | Type | Required | Description |
|---|---|---|---|
| `arguments` | string | yes | JSON-encoded tool arguments. Parse this string to obtain the arguments object. |
| `tool_name` | string | yes |  |
| `tool_use_id` | [ToolUseId](#tooluseid) | yes |  |
| `type` | `"tool_call"` | yes |  |

## HistoryMedia

object

An attachment, as a customer reads it back.

| Field | Type | Required | Description |
|---|---|---|---|
| `byte_len` | integer (int64); minimum 0 | yes |  |
| `data_base64` | string,null | no | Standard base64, present only under `include=full`. |
| `filename` | string,null | no |  |
| `height` | integer,null (int32); minimum 0 | no |  |
| `media_id` | [MediaId](#mediaid) | yes |  |
| `mime` | string | yes |  |
| `page_count` | integer,null (int32); minimum 0 | no |  |
| `width` | integer,null (int32); minimum 0 | no |  |

## HistoryMode

`"summarize"` or `"drop_oldest"`



## HistoryPageResponse

object

One page of a chat's parts.

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | array of ([HistoryPart](#historypart)) | yes | **Oldest first**, whichever direction was paged — a transcript is read in the order it happened, so a UI appending to the bottom never reverses a page. |
| `first_ordinal` | integer,null (int64) | yes | Ordinal of the first part in `data`. Pass as `before_ordinal` to page further back. `null` when the page is empty. |
| `has_more` | boolean | yes | Whether more parts exist **in the direction this page was paged** — older, by default and when paging back; newer, when paging forward. |
| `last_ordinal` | integer,null (int64) | yes | Ordinal of the last part in `data`. Pass as `after_ordinal` to pick up what arrives after it. `null` when the page is empty. |

## HistoryPart

object

A stored chat part with its ID, position, metadata and content. Prompt and tool-result content can be reused in a new submission; assistant-authored parts cannot be submitted.

| Field | Type | Required | Description |
|---|---|---|---|
| `created_at` | string (date-time) | yes |  |
| `id` | [ChatPartId](#chatpartid) | yes |  |
| `media` | array,null | no | Attachments, when the part has any and `include` asked for them. Returned on the history envelope, separately from `part`. To resubmit an attachment, request `include=full` and copy its MIME type, base64 bytes and optional filename into the submitted prompt or tool result's `media` list. Generated IDs, sizes and dimensions are read-only history metadata. |
| `ordinal` | integer (int64) | yes |  |
| `part` | [HistoryBody](#historybody) | yes | History content: a user prompt, tool result, assistant message, reasoning or tool call. |
| `role` | string | yes | `user` or `assistant`, derived from the kind — there is no stored role. Present because a UI's first decision is which side of the transcript a part belongs on, and deriving it from five kind names is work every client would otherwise repeat. |
| `user_metadata` | any JSON | yes |  |

## IdempotencyKey

string



Constraints:
```json
{
  "maxLength": 128,
  "minLength": 1
}
```

## InstructionResponse

[ConfigVersionResponse](#configversionresponse) and object

An instruction version with its body — what the editor loads.

allOf alternative 1: [ConfigVersionResponse](#configversionresponse)



| Field | Type | Required | Description |
|---|---|---|---|
| `created_at` | string (date-time) | yes |  |
| `estimated_tokens` | [TokenEstimate](#tokenestimate) | yes | A count of estimated tokens. Always non-negative; saturates rather than overflowing, because a budget calculation must never wrap into a small number and wave through a request that cannot fit. |
| `id` | string | yes |  |
| `is_active` | boolean | yes |  |
| `mode` | null or [ConfigMode](#configmode) | no | How this version combines with the org-global one — `None` for the two surfaces that do not combine at all (retention policies and context budgets), where a mode would be a field the customer could set and nothing would read. |
| `namespace` | null or [Namespace](#namespace) | no |  |
| `version` | integer (int32) | yes |  |

allOf alternative 2: object



| Field | Type | Required | Description |
|---|---|---|---|
| `body` | string | yes |  |

## MarkupPercent

string



## MediaCapabilities

object



| Field | Type | Required | Description |
|---|---|---|---|
| `assistant` | [MediaPolicy](#mediapolicy) | yes |  |
| `configured` | boolean | yes | False means legacy adapter defaults, not verified model-level support. |
| `max_attachments_per_part` | integer; minimum 0 | yes |  |
| `tool_result` | [MediaPolicy](#mediapolicy) | yes |  |
| `user` | [MediaPolicy](#mediapolicy) | yes |  |

## MediaCarrier

`"user_message"` or `"assistant_message"` or `"tool_result"`



## MediaId

string (uuid)



## MediaPolicy

object



| Field | Type | Required | Description |
|---|---|---|---|
| `max_attachments_per_request` | integer,null (int32); minimum 0 | no | Count across this carrier in the entire context, not per message. |
| `max_bytes_per_attachment` | integer (int64); minimum 0 | yes | Effective byte ceiling, including the platform inline attachment limit. |
| `mime_types` | array of ([MimeType](#mimetype)) | yes |  |

## MediaRetentionRule

object

One media rule.

| Field | Type | Required | Description |
|---|---|---|---|
| `mime` | [MimePattern](#mimepattern) | yes |  |
| `retain` | [RecencyThreshold](#recencythreshold) | yes | How recent a part must be for media of this type to be replayed. |

Constraints:
```json
{
  "additionalProperties": false
}
```

## MediaRules

object

The media section: which attachments are replayed.

| Field | Type | Required | Description |
|---|---|---|---|
| `default` | [RecencyThreshold](#recencythreshold) | no | Applied when no rule matches. |
| `rules` | array of ([MediaRetentionRule](#mediaretentionrule)) | no |  |

Constraints:
```json
{
  "additionalProperties": false
}
```

## MimePattern

string



## MimeType

string



## ModelId

string (uuid)



## Money

string

A signed monetary amount in the stated currency. Negative amounts debit a balance; positive amounts credit it.

## Namespace

string



Constraints:
```json
{
  "maxLength": 255,
  "minLength": 1,
  "pattern": "^[a-z0-9_-]{1,64}(/[a-z0-9_-]{1,64})*$"
}
```

## NamespacePage

object

Every namespace in use, ascending.

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | array of ([Namespace](#namespace)) | yes | Named `data` like every other listing, so one client-side helper works across the whole surface. **Chats with no namespace contribute nothing.** Absent is the org's default scope rather than a value, and returning it as one would invite a client to send it back as a namespace — which is exactly the empty-string namespace the create endpoint rejects. |

## OrganisationId

string (uuid)



## PendingCall

object



| Field | Type | Required | Description |
|---|---|---|---|
| `tool_name` | string | yes |  |
| `tool_use_id` | [ToolUseId](#tooluseid) | yes |  |

## Provider

`"anthropic"` or `"openai"` or `"google"` or `"xai"` or `"fireworks"` or `"openrouter"`



## PublicPlatformUpdate

object

Published site feed: no account-specific read receipts or draft metadata.

| Field | Type | Required | Description |
|---|---|---|---|
| `body` | [UpdateBody](#updatebody) | yes |  |
| `id` | [UpdateId](#updateid) | yes |  |
| `important` | boolean | yes |  |
| `kind` | [UpdateKind](#updatekind) | yes |  |
| `published_at` | string (date-time) | yes |  |
| `title` | [UpdateTitle](#updatetitle) | yes |  |
| `version` | null or [UpdateVersion](#updateversion) | no |  |

## PublishContextBudgetRequest

object

Body for publishing a context budget.

`model_ids` is the tag list: one document applied to each of those models,
one version row each. An **empty** list publishes the scope default instead —
the budget every model without a tag of its own uses. Publishing again for the same scope and model creates a new version and makes
it active; the previous version remains available for restoration.

| Field | Type | Required | Description |
|---|---|---|---|
| `budget` | [ContextBudget](#contextbudget) | yes |  |
| `model_ids` | array of ([ModelId](#modelid)) | no |  |
| `namespace` | null or [Namespace](#namespace) | no |  |

Constraints:
```json
{
  "additionalProperties": false
}
```

## PublishInstructionRequest

object

Body for publishing an instruction. `mode` is ignored on the global scope,
where there is nothing to combine with.

| Field | Type | Required | Description |
|---|---|---|---|
| `body` | string | yes |  |
| `mode` | [ConfigMode](#configmode) | yes |  |
| `namespace` | null or [Namespace](#namespace) | no |  |

Constraints:
```json
{
  "additionalProperties": false
}
```

## PublishRetentionPolicyRequest

object

Body for publishing a retention policy. `rules` is validated into
`RetentionRules` at the boundary, so an unreachable rule, a truncation that
could never cut anything, or an unknown schema version is a 422 before the
handler runs.

No `mode`: a policy is a complete decision table and is selected whole, so
there is nothing to combine and nothing to configure about combining.

| Field | Type | Required | Description |
|---|---|---|---|
| `namespace` | null or [Namespace](#namespace) | no |  |
| `rules` | [RetentionRules](#retentionrules) | yes | The stored document. Private fields: holding one is proof it parsed. |

Constraints:
```json
{
  "additionalProperties": false
}
```

## PublishToolsRequest

object

Body for publishing a tool bundle. `definitions` is validated into a
`ToolBundle` at the boundary, so a malformed bundle is a 422 before the
handler runs.

| Field | Type | Required | Description |
|---|---|---|---|
| `definitions` | array of ([ToolDefinition](#tooldefinition)) | yes |  |
| `mode` | [ConfigMode](#configmode) | yes |  |
| `namespace` | null or [Namespace](#namespace) | no |  |

Constraints:
```json
{
  "additionalProperties": false
}
```

## RatePerMillion

string

Price in the stated currency per one million tokens.

## ReasoningEffort

`"off"` or `"low"` or `"medium"` or `"high"` or `"x_high"` or `"max"`



## RecencyThreshold

number (float)



## ResolvedBudget

object

The budget half of a resolution.

Separate from `ResolvedConfig` because it is resolved at a different moment
and over one axis more: config resolution answers "what is configured for
this namespace", which a customer can ask without naming a model, while a
budget is resolved over namespace *and* model and only means something once a
run has picked one.

| Field | Type | Required | Description |
|---|---|---|---|
| `budget` | [ContextBudget](#contextbudget) | yes | Always present: an org that has published nothing gets the platform default rather than no budget at all. |
| `model_id` | null or [ModelId](#modelid) | no | The model this was resolved for. `None` asks what an untagged model gets. |
| `namespace` | null or [Namespace](#namespace) | no | The scope this was resolved for — what was asked, not what answered. `BudgetSource::Policy::namespace` is the level that actually supplied it, and the two differ whenever a budget is inherited. |
| `source` | [BudgetSource](#budgetsource) | yes | The configuration scope and model tag that supplied the resolved context budget. |
| `warnings` | array of ([ConfigWarning](#configwarning)) | yes |  |

## ResolvedConfig

object

The resolution result.

| Field | Type | Required | Description |
|---|---|---|---|
| `estimated_tokens` | [TokenEstimate](#tokenestimate) | yes | Instruction plus bundle, as budgeted against. Summed from the frozen per-row estimates rather than recomputed, so it matches what the packer will use. |
| `instructions` | string,null | no | `None` when neither scope has an active instruction — distinct from an empty string, which would be an instruction that says nothing. |
| `namespace` | null or [Namespace](#namespace) | no | The scope this was resolved for. `None` is org-global. |
| `retention` | [RetentionRules](#retentionrules) | yes | What a replayed part looks like: which media comes back, how tool payloads are truncated, whether reasoning is replayed, what is anchored. Always present — when no scope has published a policy this is the platform default, because "no configuration" must not mean "replay every image forever". It contributes nothing to `estimated_tokens`: a policy is never sent to a provider, it only decides what of the ledger is, and that cost is counted per part. |
| `tools` | array of ([ToolDefinition](#tooldefinition)) | yes |  |
| `versions` | [ResolvedConfigVersions](#resolvedconfigversions) | yes | Which version rows produced a resolution. Persisted on a run's manifest so "what configuration did this request use" keeps its answer after newer versions are published. |
| `warnings` | array of ([ConfigWarning](#configwarning)) | yes |  |

## ResolvedConfigVersions

object

Which version rows produced a resolution. Persisted on a run's manifest so
"what configuration did this request use" keeps its answer after newer
versions are published.

| Field | Type | Required | Description |
|---|---|---|---|
| `instructions` | array of ([SystemInstructionId](#systeminstructionid)) | yes | Every instruction version that contributes, **root first** — the order they are concatenated in. A list rather than a global/namespace pair. The pair could not express the truth at any depth past one: a chain is arbitrarily deep, every level on `append` contributes, and a `replace` truncates at whichever level issued it. Reporting two ids meant a middle level either vanished from the answer or was misreported as the namespace's. |
| `retention_policy` | null or [RetentionPolicyId](#retentionpolicyid) | no | One id, not a list: at most one level's retention policy is ever in force, because a policy is selected whole rather than combined. |
| `tools` | array of ([ToolSchemaId](#toolschemaid)) | yes | Every tool bundle version that contributes, root first. Same reasoning. |

## ResponseEvent

object or object or object or object or object or object or object or object or object

Each variant is one SSE frame: `event: <event>` followed by `data: <JSON>`.
The wrapper documents the framing; only its `data` is sent as the JSON payload.

oneOf alternative 1: object

Always first: identifiers and tool calls closed by this submission.

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | object | yes | Always first: identifiers and tool calls closed by this submission. |
| `event` | `"run"` | yes |  |

Event data:

| Field | Type | Required | Description |
|---|---|---|---|
| `chat_id` | [ChatId](#chatid) | yes |  |
| `closed_tool_calls` | array of ([ToolUseId](#tooluseid)) | yes |  |
| `run_id` | [RunId](#runid) | yes |  |

oneOf alternative 2: object

Configuration warnings; emitted second, and may repeat after compaction.

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | object | yes | Configuration warnings; emitted second, and may repeat after compaction. |
| `event` | `"config_warnings"` | yes |  |

Event data:

| Field | Type | Required | Description |
|---|---|---|---|
| `warnings` | array of ([Warning_ConfigWarning](#warning-configwarning)) | yes |  |

oneOf alternative 3: object

Model compatibility warnings before content starts.

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | object | yes | Model compatibility warnings before content starts. |
| `event` | `"translation_warnings"` | yes |  |

Event data:

| Field | Type | Required | Description |
|---|---|---|---|
| `warnings` | array of ([Warning_TranslationWarning](#warning-translationwarning)) | yes |  |

oneOf alternative 4: object

Compaction work the request is waiting for.

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | object | yes | Compaction work the request is waiting for. |
| `event` | `"compacting"` | yes |  |

Event data:

| Field | Type | Required | Description |
|---|---|---|---|
| `blocks` | integer; minimum 0 | yes |  |
| `in_progress_elsewhere` | integer; minimum 0 | yes |  |

oneOf alternative 5: object



| Field | Type | Required | Description |
|---|---|---|---|
| `data` | [BlockStart](#blockstart) | yes |  |
| `event` | `"block_start"` | yes |  |

Event data:

oneOf alternative 1: object



| Field | Type | Required | Description |
|---|---|---|---|
| `kind` | `"text"` | yes |  |

oneOf alternative 2: object



| Field | Type | Required | Description |
|---|---|---|---|
| `kind` | `"reasoning"` | yes |  |

oneOf alternative 3: object



| Field | Type | Required | Description |
|---|---|---|---|
| `kind` | `"refusal"` | yes |  |

oneOf alternative 4: object



| Field | Type | Required | Description |
|---|---|---|---|
| `kind` | `"tool_call"` | yes |  |
| `tool_name` | [ToolName](#toolname) | yes |  |
| `tool_use_id` | [ToolUseId](#tooluseid) | yes |  |

oneOf alternative 6: object



| Field | Type | Required | Description |
|---|---|---|---|
| `data` | [Delta](#delta) | yes |  |
| `event` | `"delta"` | yes |  |

Event data:

oneOf alternative 1: object



| Field | Type | Required | Description |
|---|---|---|---|
| `kind` | `"text"` | yes |  |
| `text` | string | yes |  |

oneOf alternative 2: object



| Field | Type | Required | Description |
|---|---|---|---|
| `kind` | `"reasoning"` | yes |  |
| `text` | string | yes |  |

oneOf alternative 3: object



| Field | Type | Required | Description |
|---|---|---|---|
| `kind` | `"tool_input"` | yes |  |
| `text` | string | yes |  |

oneOf alternative 4: object



| Field | Type | Required | Description |
|---|---|---|---|
| `kind` | `"refusal"` | yes |  |
| `text` | string | yes |  |

oneOf alternative 7: object



| Field | Type | Required | Description |
|---|---|---|---|
| `data` | object | yes |  |
| `event` | `"block_stop"` | yes |  |

oneOf alternative 8: object

Terminal success. Null cost means settlement is pending; inspect the run later.

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | object | yes | Terminal success. Null cost means settlement is pending; inspect the run later. |
| `event` | `"done"` | yes |  |

Event data:

| Field | Type | Required | Description |
|---|---|---|---|
| `compaction` | null or [Compaction](#compaction) | yes |  |
| `cost` | null or [Money](#money) | yes | A signed monetary amount in the stated currency. Negative amounts debit a balance; positive amounts credit it. |
| `cost_currency` | string | yes |  |
| `model_served` | string,null | yes | The model identifier reported by the upstream provider, when available. This may differ from the catalogue name used in the request (for example, gpt-5-6-luna can return gpt-5.6-luna). Do not use this as a catalogue lookup key. |
| `pending_tool_calls` | array of ([PendingCall](#pendingcall)) | yes |  |
| `provider_response_id` | string,null | yes |  |
| `server_tools` | null or [ServerToolUsage](#servertoolusage) | yes | Provider-executed operations. Counts are successful billable operations, taken from terminal usage, not inferred from streamed attempts. |
| `stop_reason` | [StopReason](#stopreason) | yes |  |
| `usage` | [Usage](#usage) | yes |  |

oneOf alternative 9: object

Terminal failure after the stream has opened.

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | [ErrorBody](#errorbody) | yes | Terminal failure after the stream has opened. |
| `event` | `"error"` | yes |  |

Event data:

| Field | Type | Required | Description |
|---|---|---|---|
| `code` | string | yes |  |
| `details` | any JSON | no |  |
| `hint` | string,null | no |  |
| `message` | string | yes |  |

## RetentionPolicyId

string (uuid)



## RetentionPolicyResponse

[ConfigVersionResponse](#configversionresponse) and object

One retention-policy version with its rules.

No `estimated_tokens`: a policy is never sent to a provider, so unlike an
instruction or a bundle it has no token cost of its own. The version envelope
is shared with the other surfaces, so the field is reported as zero rather
than given a separate response shape for one absent number.

allOf alternative 1: [ConfigVersionResponse](#configversionresponse)



| Field | Type | Required | Description |
|---|---|---|---|
| `created_at` | string (date-time) | yes |  |
| `estimated_tokens` | [TokenEstimate](#tokenestimate) | yes | A count of estimated tokens. Always non-negative; saturates rather than overflowing, because a budget calculation must never wrap into a small number and wave through a request that cannot fit. |
| `id` | string | yes |  |
| `is_active` | boolean | yes |  |
| `mode` | null or [ConfigMode](#configmode) | no | How this version combines with the org-global one — `None` for the two surfaces that do not combine at all (retention policies and context budgets), where a mode would be a field the customer could set and nothing would read. |
| `namespace` | null or [Namespace](#namespace) | no |  |
| `version` | integer (int32) | yes |  |

allOf alternative 2: object



| Field | Type | Required | Description |
|---|---|---|---|
| `rules` | [RetentionRules](#retentionrules) | yes | The stored document. Private fields: holding one is proof it parsed. |

## RetentionRules

object

The stored document. Private fields: holding one is proof it parsed.

| Field | Type | Required | Description |
|---|---|---|---|
| `anchor_parts` | integer (int32); minimum 0 | no | How many parts at the head of the chat are pinned verbatim, never compacted and never truncated. `0` disables it. Counted in **parts**, not tokens, because the value a customer wants to express is "the task spec", which is a part boundary they can see — not a token count they would have to estimate and re-tune whenever they edited the spec. |
| `media` | [MediaRules](#mediarules) | no | The media section: which attachments are replayed. |
| `reasoning` | [RecencyThreshold](#recencythreshold) | no | How recent a part must be for its reasoning to be replayed. Defaults to the same window as media: a reasoning block's replay token is worthless to a model of another family anyway, and old reasoning is the least load-bearing, most expensive thing in a long agentic history. |
| `text` | [TextRules](#textrules) | no | Truncation policies for older user prompts and assistant messages. The current exchange and configured anchor remain protected. |
| `tools` | [ToolRules](#toolrules) | no | The tool section: how tool payloads are cut down. |
| `verbatim_within` | [RecencyThreshold](#recencythreshold) | no | How recent a payload must be to escape truncation entirely — tool results, tool arguments and prose alike. One setting for the whole document, deliberately, and the direct descendant of the old `truncate_within_frontier` flag. "Don't cut the tool output I just received" is not a per-tool judgement, and giving each family its own would put three definitions of "recent" back in a document whose entire point is that there is one. Hoisted out of the tool section when prose truncation arrived: the alternative was a second identical field on `TextRules`, which is how a shared metric quietly becomes two that drift. |
| `version` | integer (int32); minimum 0 | yes |  |

Constraints:
```json
{
  "additionalProperties": false
}
```

## RunId

string (uuid)



## RunInspection

object



| Field | Type | Required | Description |
|---|---|---|---|
| `chat_id` | [ChatId](#chatid) | yes |  |
| `completed_at` | string,null (date-time) | yes |  |
| `cost` | string,null | yes | What it was charged, once settled. `None` covers both "still running" and "finished but not yet settled" — a sweep may be seconds or hours behind, and the two are the same fact to a reader: no charge has landed yet. |
| `created_at` | string (date-time) | yes |  |
| `error` | string,null | yes | The failure, when there was one. Present on `failed` runs only. |
| `id` | [RunId](#runid) | yes |  |
| `is_live` | boolean | yes | Whether a running call has an unexpired durable ownership lease. False on expiry, before recovery necessarily closes the run. A paused worker is indistinguishable from a dead one and is fenced in either case. |
| `latency_ms` | integer,null (int32) | yes |  |
| `manifest` | object | yes | The context and configuration this call actually used. Opaque JSON on purpose: it is a record, not an API — the shape follows what the planner happened to record, and freezing it into typed fields would make every planner change a breaking change here. |
| `model` | string | yes | Our catalogue `name`, never the provider's wire string — the same identifier the customer sent and `/api/v1/models` lists. |
| `namespace` | null or [Namespace](#namespace) | yes | The scope the run's configuration resolved against. `None` is org-global. |
| `origin` | [RunOrigin](#runorigin) | yes | Whether the customer asked for this call or we did. A `compaction` run is one the customer never made and is still billed for, which is exactly why it is visible here rather than folded into inference. |
| `provider_request_id` | string,null | yes | The provider's own id for the call, for quoting at us in a support conversation. Already visible on the turn's terminal SSE event. |
| `settled_at` | string,null (date-time) | yes |  |
| `status` | [RunStatus](#runstatus) | yes | **The durable answer**, from the `runs` row and never from the cache. |
| `usage` | null or [Usage](#usage) | yes | `None` while in flight, or after interruption when usage was lost. Missing counts must never be presented as confirmed zero usage. |

## RunOrigin

`"customer"` or `"compaction"`



## RunStatus

`"running"` or `"succeeded"` or `"failed"` or `"cancelled"`



## ServerToolUsage

object

Provider-executed operations. Counts are successful billable operations,
taken from terminal usage, not inferred from streamed attempts.

| Field | Type | Required | Description |
|---|---|---|---|
| `document_search_calls` | integer (int32); minimum 0 | yes |  |
| `provider_cost` | null or [Money](#money) | no | Total provider charge, including tokens, when document tools were used. |

## StopReason

`"end_turn"` or `"max_tokens"` or `"tool_use"` or `"stop_sequence"` or `"refusal"`



## SubmittedMedia

object

An inline attachment. Supply standard base64 bytes without a data-URI prefix and the matching MIME type. Model-specific format, byte and count limits apply.

| Field | Type | Required | Description |
|---|---|---|---|
| `data_base64` | string | yes | Standard base64, no data-URI prefix. Named for its encoding because the field is what a customer has to get right, and `data` alone invites a raw string or a `data:` URL — both of which would decode to something, quietly, and be sealed as an image nothing can open. |
| `filename` | string,null | no | Shown in a UI, and named in the stub that replaces this attachment once retention drops it — which is why it is worth carrying even though no provider requires it. |
| `mime` | string | yes | Explicit MIME type, such as `image/png` or `application/pdf`. Validated against the selected model's configured media capabilities before the turn is persisted. Supported formats differ by model and carrier. |

Constraints:
```json
{
  "additionalProperties": false
}
```

## SubmittedPart

object or object

One part the customer is submitting.

Only the two **user-role** kinds are representable. A customer cannot author
a `message`, a `reasoning` block or a `tool_call` — those come back from a
model — so leaving them out makes the impossible submission unconstructible
rather than merely rejected, and there is no branch anywhere below that has
to check for one.

oneOf alternative 1: object

The customer's own turn.

| Field | Type | Required | Description |
|---|---|---|---|
| `media` | array of ([SubmittedMedia](#submittedmedia)) | no |  |
| `text` | string | no |  |
| `type` | `"prompt"` | yes |  |

oneOf alternative 2: object

The answer to a tool call the model made.

| Field | Type | Required | Description |
|---|---|---|---|
| `is_error` | boolean | no | Anthropic's `tool_result.is_error`. Dropped when translating to a provider with no equivalent. |
| `media` | array of ([SubmittedMedia](#submittedmedia)) | no | What the tool returned besides text — a screenshot, most often, which is the canonical case the media tier exists for. |
| `text` | string | no |  |
| `tool_name` | string | yes | The tool's name, as it was called. Required because Google puts the function name on the response and nothing else on the row could supply it without joining back to the call. |
| `tool_use_id` | [ToolUseId](#tooluseid) | yes |  |
| `trust` | [TrustLevel](#trustlevel) | no | How far this content is trusted, driving the write-time hygiene limits. Defaults to `customer_data`, the safe assumption. |
| `type` | `"tool_result"` | yes |  |

## SystemInstructionId

string (uuid)



## TextRules

object

Truncation policies for older user prompts and assistant messages. The current exchange and configured anchor remain protected.

| Field | Type | Required | Description |
|---|---|---|---|
| `message` | [Truncation](#truncation) | no | Applied to an assistant `message`'s text. Separate from `prompt` for the same reason a tool's arguments and result are separate: they are different sizes with different value. A customer pasting logs wants their own input cut; a customer whose model writes long answers wants the replies cut. |
| `prompt` | [Truncation](#truncation) | no | Applied to a `prompt` part's text. |

Constraints:
```json
{
  "additionalProperties": false
}
```

## TierMode

`"cliff"` or `"graduated"`



## TokenEstimate

integer (int32)

A count of estimated tokens. Always non-negative; saturates rather than
overflowing, because a budget calculation must never wrap into a small
number and wave through a request that cannot fit.

## TombstoneReason

`"abandoned"` or `"cancelled"` or `"expired"`



## ToolDefinition

object

A named tool with a description and JSON Schema parameters. Parameters must be a valid schema describing an object; external schema references are not supported.

| Field | Type | Required | Description |
|---|---|---|---|
| `description` | string,null | no |  |
| `input_schema` | object | yes |  |
| `name` | [ToolName](#toolname) | yes |  |
| `strict` | boolean,null | no | Ask the provider to guarantee the tool call validates against the schema. `None` means the customer did not say, which is distinct from `false`: the providers' own defaults differ, and translating an unstated preference into an explicit `false` would silently opt a customer out of a guarantee they never declined. Dropped when targeting Google, which has no equivalent. |

## ToolName

string



## ToolNamePattern

string



## ToolRetentionRule

[ToolTreatment](#tooltreatment) and object

One tool rule.

allOf alternative 1: [ToolTreatment](#tooltreatment)



| Field | Type | Required | Description |
|---|---|---|---|
| `arguments` | [Truncation](#truncation) | no | Applied to the `tool_call` arguments. |
| `result` | [Truncation](#truncation) | no | Applied to the `tool_result` payload. |

allOf alternative 2: object



| Field | Type | Required | Description |
|---|---|---|---|
| `tool` | [ToolNamePattern](#toolnamepattern) | yes |  |

## ToolRules

object

The tool section: how tool payloads are cut down.

| Field | Type | Required | Description |
|---|---|---|---|
| `default` | [ToolTreatment](#tooltreatment) | no | Applied when no rule matches. |
| `errors` | null or [Truncation](#truncation) | no | Replaces the matched `result` treatment on a part flagged `is_error`. `None` means errors follow the ordinary rule. Worth its own knob because failures split the customer base cleanly: for one, a stack trace is the most valuable thing in the history; for the next, it is a retry loop's worth of noise. |
| `mark_truncations` | boolean | no | Whether a cut payload carries a visible marker of what was elided. Defaults **on**, and deliberately not a per-rule choice: a model that cannot tell output was truncated will confidently invent the missing middle, and that failure is not a per-tool judgement call. |
| `rules` | array of ([ToolRetentionRule](#toolretentionrule)) | no |  |

Constraints:
```json
{
  "additionalProperties": false
}
```

## ToolSchemaId

string (uuid)



## ToolSchemaResponse

[ConfigVersionResponse](#configversionresponse) and object

One tool-bundle version with its definitions — the single-version shape, the
sibling of `InstructionResponse`. The history endpoint returns bare
`ConfigVersionResponse`s, because definitions are sealed and listing them
would be a fetch and a decrypt per row.

allOf alternative 1: [ConfigVersionResponse](#configversionresponse)



| Field | Type | Required | Description |
|---|---|---|---|
| `created_at` | string (date-time) | yes |  |
| `estimated_tokens` | [TokenEstimate](#tokenestimate) | yes | A count of estimated tokens. Always non-negative; saturates rather than overflowing, because a budget calculation must never wrap into a small number and wave through a request that cannot fit. |
| `id` | string | yes |  |
| `is_active` | boolean | yes |  |
| `mode` | null or [ConfigMode](#configmode) | no | How this version combines with the org-global one — `None` for the two surfaces that do not combine at all (retention policies and context budgets), where a mode would be a field the customer could set and nothing would read. |
| `namespace` | null or [Namespace](#namespace) | no |  |
| `version` | integer (int32) | yes |  |

allOf alternative 2: object



| Field | Type | Required | Description |
|---|---|---|---|
| `definitions` | array of ([ToolDefinition](#tooldefinition)) | yes |  |

## ToolTreatment

object

How one tool's two payloads are treated. Arguments and results are separate
because they are separate parts with separate sizes — a `write_file` call's
arguments routinely dwarf its result, and a `search` call's result dwarfs its
arguments.

| Field | Type | Required | Description |
|---|---|---|---|
| `arguments` | [Truncation](#truncation) | no | Applied to the `tool_call` arguments. |
| `result` | [Truncation](#truncation) | no | Applied to the `tool_result` payload. |

Constraints:
```json
{
  "additionalProperties": false
}
```

## ToolUseId

string



## Truncation

object or object or object

Keep content unchanged, always truncate to a token target, or truncate only when it exceeds a threshold. Choose the head, tail, or both ends to retain.

oneOf alternative 1: object

Replay it whole, however large.

| Field | Type | Required | Description |
|---|---|---|---|
| `mode` | `"keep"` | yes |  |

oneOf alternative 2: object

Always cut to `to` tokens.

| Field | Type | Required | Description |
|---|---|---|---|
| `keep` | [TruncationShape](#truncationshape) | no |  |
| `mode` | `"always"` | yes |  |
| `to` | [TokenEstimate](#tokenestimate) | yes | A count of estimated tokens. Always non-negative; saturates rather than overflowing, because a budget calculation must never wrap into a small number and wave through a request that cannot fit. |

oneOf alternative 3: object

Cut to `to` tokens, but only once the payload exceeds `over`. The common
shape: small results replay verbatim, a runaway one is clipped.

| Field | Type | Required | Description |
|---|---|---|---|
| `keep` | [TruncationShape](#truncationshape) | no |  |
| `mode` | `"over"` | yes |  |
| `over` | [TokenEstimate](#tokenestimate) | yes | A count of estimated tokens. Always non-negative; saturates rather than overflowing, because a budget calculation must never wrap into a small number and wave through a request that cannot fit. |
| `to` | [TokenEstimate](#tokenestimate) | yes | A count of estimated tokens. Always non-negative; saturates rather than overflowing, because a budget calculation must never wrap into a small number and wave through a request that cannot fit. |

## TruncationShape

`"head"` or `"tail"` or `"head_and_tail"`



## TrustLevel

`"trusted"` or `"customer_data"` or `"untrusted"`



## UpdateBody

string



## UpdateId

string (uuid)



## UpdateKind

`"release"` or `"notice"` or `"maintenance"`



## UpdateTitle

string



## UpdateVersion

string



## Uptime

string

A measured ratio from 0 to 1; multiply by 100 to display a percentage.

## Usage

object



| Field | Type | Required | Description |
|---|---|---|---|
| `cache_read_tokens` | integer (int32); minimum 0 | yes | Served from the provider's prompt cache, billed below base rate. |
| `cache_write_tokens` | integer (int32); minimum 0 | yes | Written to the provider's prompt cache, billed above base rate. The Responses API does not meter this and reports zero. |
| `input_tokens` | integer (int32); minimum 0 | yes | Billable input at the base rate. Excludes both cache figures. |
| `output_tokens` | integer (int32); minimum 0 | yes | Everything the model produced, reasoning included. |
| `reasoning_tokens` | integer (int32); minimum 0 | yes | The thinking share of `Self::output_tokens`. **A subset, not an addition** — reported for visibility, never summed into the total. |

## UsageBin

`"hour"` or `"day"`



## UsageDimension

`"model"` or `"namespace"` or `"api_key"` or `"entry_type"` or `"organisation"` or `"run"`



## UsageGroup

object or object or object or object or object or object

Which slice a row is, and everything needed to label it.

A tagged union rather than a `key: String`, because the labels are not
interchangeable: a model row needs the id *and* the human name, a namespace
row's key can legitimately be absent (the org-global scope), and a run row
carries the context you would drill from.

oneOf alternative 1: object



| Field | Type | Required | Description |
|---|---|---|---|
| `dimension` | `"model"` | yes |  |
| `model_id` | null or [ModelId](#modelid) | no | Absent on a credit, which names no model. |
| `model_name` | string,null | no |  |

oneOf alternative 2: object

`None` is the org-global scope — chats created without a namespace — not
an unknown one.

| Field | Type | Required | Description |
|---|---|---|---|
| `dimension` | `"namespace"` | yes |  |
| `namespace` | null or [Namespace](#namespace) | no |  |

oneOf alternative 3: object

`None` covers events with no key: console-initiated work, and credits.

| Field | Type | Required | Description |
|---|---|---|---|
| `api_key_id` | null or [ApiKeyId](#apikeyid) | no |  |
| `dimension` | `"api_key"` | yes |  |

oneOf alternative 4: object

Usage grouped by organisation. Labels are null when unavailable.

| Field | Type | Required | Description |
|---|---|---|---|
| `dimension` | `"organisation"` | yes |  |
| `name` | string,null | no |  |
| `organisation_id` | [OrganisationId](#organisationid) | yes |  |
| `slug` | string,null | no |  |

oneOf alternative 5: object



| Field | Type | Required | Description |
|---|---|---|---|
| `dimension` | `"entry_type"` | yes |  |
| `entry_type` | [BalanceEntryType](#balanceentrytype) | yes |  |

oneOf alternative 6: object



| Field | Type | Required | Description |
|---|---|---|---|
| `api_key_id` | null or [ApiKeyId](#apikeyid) | no |  |
| `dimension` | `"run"` | yes |  |
| `entry_type` | [BalanceEntryType](#balanceentrytype) | yes |  |
| `last_occurred_at` | string,null (date-time) | no | When this run last recorded an event — what the list is ordered by, since a run has no single instant of its own here. |
| `model_name` | string,null | no |  |
| `namespace` | null or [Namespace](#namespace) | no |  |
| `run_id` | null or [RunId](#runid) | no |  |

## UsageReport

object

A report: the rows, plus the shape of the question they answer.

The window is echoed because it is not what the caller literally asked for —
a range preset is resolved against the clock and rounded to the hour — and a
chart that cannot state its own axis is a chart nobody can check.

| Field | Type | Required | Description |
|---|---|---|---|
| `bin` | null or [UsageBin](#usagebin) | no |  |
| `cached` | boolean | yes | Whether these rows came from the cache. Surfaced rather than hidden: the console tells the customer their usage lags by minutes, and this is the honest version of that sentence. |
| `group_by` | null or [UsageDimension](#usagedimension) | no |  |
| `namespace` | null or [Namespace](#namespace) | no |  |
| `rows` | array of ([UsageRow](#usagerow)) | yes |  |
| `window` | [UsageWindow](#usagewindow) | yes | A validated `[from, to)` range over `occurred_at`. Half-open, so consecutive windows tile without double-counting the instant on the boundary — an event at exactly midnight belongs to one day, not two. |

## UsageRow

[UsageTotals](#usagetotals) and object

One row of a report: an optional bucket, an optional group, and the measures.

Both are `Option` because the same row type serves all four query shapes, and
which of them are populated is a property of the query rather than of the
row — the report echoes the query shape beside the rows so a reader never has
to infer it.

allOf alternative 1: [UsageTotals](#usagetotals)



| Field | Type | Required | Description |
|---|---|---|---|
| `cache_read_tokens` | integer (int64) | yes |  |
| `cache_write_tokens` | integer (int64) | yes |  |
| `cost` | [Money](#money) | yes | Model-call spend in the requested window. Credits share the source table but are excluded from usage reports. |
| `input_tokens` | integer (int64) | yes |  |
| `output_tokens` | integer (int64) | yes |  |
| `reasoning_tokens` | integer (int64) | yes | A subset of `output_tokens`, never an addition — and priced at zero, so it explains a cost rather than adding to it. |
| `runs` | integer (int64) | yes |  |

allOf alternative 2: object



| Field | Type | Required | Description |
|---|---|---|---|
| `bucket` | string,null (date-time) | no |  |
| `group` | null or [UsageGroup](#usagegroup) | no | Which slice a row is, and everything needed to label it. A tagged union rather than a `key: String`, because the labels are not interchangeable: a model row needs the id *and* the human name, a namespace row's key can legitimately be absent (the org-global scope), and a run row carries the context you would drill from. |

## UsageTotals

object

The measures every row carries, whatever it is grouped by.

`runs` counts **distinct run ids**, not events: a run that was compacted
produces two events naming it, and counting rows would report the busy runs
twice. Credits share the analytics source table but are excluded from this
usage report.

| Field | Type | Required | Description |
|---|---|---|---|
| `cache_read_tokens` | integer (int64) | yes |  |
| `cache_write_tokens` | integer (int64) | yes |  |
| `cost` | [Money](#money) | yes | Model-call spend in the requested window. Credits share the source table but are excluded from usage reports. |
| `input_tokens` | integer (int64) | yes |  |
| `output_tokens` | integer (int64) | yes |  |
| `reasoning_tokens` | integer (int64) | yes | A subset of `output_tokens`, never an addition — and priced at zero, so it explains a cost rather than adding to it. |
| `runs` | integer (int64) | yes |  |

## UsageWindow

object

A validated `[from, to)` range over `occurred_at`.

Half-open, so consecutive windows tile without double-counting the instant
on the boundary — an event at exactly midnight belongs to one day, not two.

| Field | Type | Required | Description |
|---|---|---|---|
| `from` | string (date-time) | yes |  |
| `to` | string (date-time) | yes |  |

## UserMetadata

object

Customer JSON object, at most 1024 bytes when serialized compactly. Defaults to an empty object.

## Warning_ConfigWarning

object or object or object or object or object or object and object



allOf alternative 1: object or object or object or object or object or object

Something legal but worth saying out loud.

oneOf alternative 1: object

A namespace tool replaced a global tool of the same name. The union is
keyed on name, so this is how an override is *meant* to work — but it is
also exactly what a typo in a tool name looks like from the outside.

| Field | Type | Required | Description |
|---|---|---|---|
| `code` | `"tool_shadowed"` | yes |  |
| `name` | [ToolName](#toolname) | yes |  |
| `namespace` | [Namespace](#namespace) | yes |  |

oneOf alternative 2: object

The namespace's instruction is in `replace` mode, so the org-global
instruction is not being sent at all.

| Field | Type | Required | Description |
|---|---|---|---|
| `code` | `"global_instruction_replaced"` | yes |  |
| `namespace` | [Namespace](#namespace) | yes |  |

oneOf alternative 3: object

The namespace's tool bundle is in `replace` mode, so the global bundle
is not being sent at all — including tools the customer may assume are
always present.

| Field | Type | Required | Description |
|---|---|---|---|
| `code` | `"global_tools_replaced"` | yes |  |
| `dropped` | integer; minimum 0 | yes |  |
| `namespace` | [Namespace](#namespace) | yes |  |

oneOf alternative 4: object

A nearer level's retention policy displaced a farther level's. This is how
the override is *meant* to work: a policy is a complete decision table and
is selected whole, so the farther one contributes nothing — not its rules
and not its defaults. It still warns, because the result looks identical
either way, and "my org-wide rule stopped applying" is otherwise
undiagnosable.

Both levels are named because at depth neither is guessable from the
other: "a namespace policy won" does not say which of four ancestors
stopped applying.

| Field | Type | Required | Description |
|---|---|---|---|
| `code` | `"retention_policy_overridden"` | yes |  |
| `displaced` | null or [Namespace](#namespace) | no | The nearest level whose policy is not being applied. |
| `namespace` | null or [Namespace](#namespace) | no | The level whose policy is in force. `None` is org-global. |

oneOf alternative 5: object

The budget in force did not come from the plainest place: either an
ancestor level supplied it, or a model tag at the winning level displaced
that level's untagged policy — or both.

Two facts, one warning, because either alone misleads. "The budget came
from `acme`" and "the budget came from `acme`, tagged to this model" send
an admin to different rows, and the second is the only one that explains
why a sibling model in the same namespace behaves differently.

| Field | Type | Required | Description |
|---|---|---|---|
| `code` | `"context_budget_resolved_at"` | yes |  |
| `namespace` | null or [Namespace](#namespace) | no | The level that supplied it. `None` is the org-global scope. |
| `tagged_model` | null or [ModelId](#modelid) | no | `Some` when that level's tag for this model beat its untagged policy. |

oneOf alternative 6: object

Oldest ledger parts were omitted because no complete context fitted.

| Field | Type | Required | Description |
|---|---|---|---|
| `code` | `"history_dropped"` | yes |  |
| `dropped` | integer (int64) | yes |  |
| `end_ordinal` | integer (int64) | yes |  |
| `start_ordinal` | integer (int64) | yes |  |

allOf alternative 2: object



| Field | Type | Required | Description |
|---|---|---|---|
| `message` | string | yes |  |

## Warning_TranslationWarning

object or object or object or object or object or object or object and object



allOf alternative 1: object or object or object or object or object or object or object



oneOf alternative 1: object

Historical attachments exceeded the target model's byte or request-count
limit and were replaced with text markers. Aggregated per (MIME, carrier).

| Field | Type | Required | Description |
|---|---|---|---|
| `carrier` | [MediaCarrier](#mediacarrier) | yes |  |
| `code` | `"media_limit_exceeded"` | yes |  |
| `count` | integer; minimum 0 | yes |  |
| `mime` | [MimeType](#mimetype) | yes |  |

oneOf alternative 2: object

Media the target model cannot receive in this position was replaced with
a text marker. Aggregated per (MIME, carrier), separately from retention.

| Field | Type | Required | Description |
|---|---|---|---|
| `carrier` | [MediaCarrier](#mediacarrier) | yes |  |
| `code` | `"media_unsupported"` | yes |  |
| `count` | integer; minimum 0 | yes |  |
| `mime` | [MimeType](#mimetype) | yes |  |

oneOf alternative 3: object

Reasoning was requested but the target model does not support it, so the
request went without a thinking configuration.

| Field | Type | Required | Description |
|---|---|---|---|
| `code` | `"reasoning_not_supported"` | yes |  |
| `model` | string | yes |  |

oneOf alternative 4: object

Reasoning was switched **off** and the provider has no way to express
that, so thinking remained enabled.

The mirror of `ReasoningNotSupported`(Self::ReasoningNotSupported), and
the reason it is a warning rather than something to swallow: the customer
gets more than they asked for *and is billed for it*. The Responses API
has no "off" every vendor behind it accepts — the candidates are
model-specific and rejected elsewhere — so omitting the block is the only
universally legal shape, and omitting it means the default applies.
Silence here would make an explicit `off` look honoured while it quietly
costs money on every turn.

| Field | Type | Required | Description |
|---|---|---|---|
| `code` | `"reasoning_cannot_be_disabled"` | yes |  |
| `model` | string | yes |  |

oneOf alternative 5: object

The requested effort tier is above what this model accepts and was
lowered. The run still happened, at less depth than asked for.

| Field | Type | Required | Description |
|---|---|---|---|
| `applied` | [ReasoningEffort](#reasoningeffort) | yes |  |
| `code` | `"reasoning_effort_clamped"` | yes |  |
| `model` | string | yes |  |
| `requested` | [ReasoningEffort](#reasoningeffort) | yes |  |

oneOf alternative 6: object

A tool definition carried a field the target provider has no equivalent
for (Google has no `strict`; only Anthropic has document `title` /
`context`). Dropped on translation.

| Field | Type | Required | Description |
|---|---|---|---|
| `code` | `"tool_field_dropped"` | yes |  |
| `field` | string | yes |  |
| `tool` | [ToolName](#toolname) | yes |  |

oneOf alternative 7: object

Replay artifacts authored by a different model family were stripped,
because a signature from one family is meaningless — or a hard rejection —
to another. Expected whenever a chat hot-swaps providers, and reported so
a customer comparing two runs of the same chat can see why the second one
lost its reasoning continuity.

| Field | Type | Required | Description |
|---|---|---|---|
| `code` | `"foreign_artifacts_stripped"` | yes |  |
| `count` | integer; minimum 0 | yes |  |

allOf alternative 2: object



| Field | Type | Required | Description |
|---|---|---|---|
| `message` | string | yes |  |
