# Twigg: complete documentation Generated from the per-page files under https://twigg.ai/docs/*.md. The model catalogue with live prices is separate: https://api.twigg.ai/v1/catalogue/models.md # Twigg quickstart Twigg is a context store, assembler and model router in one. Your application keeps the agent loop and the tools. Twigg keeps the context, fits it to the model's window, translates it for the provider you pick per turn, streams the answer back, and records what it sent and what it cost. Base URL: `https://api.twigg.ai`. Every endpoint below is under `/api/v1` and takes one API key as a bearer token. Keys look like `tw_live_…` and are created in the console at https://twigg.ai/dashboard/api-keys. There is no SDK. An HTTP client that can read server-sent events is the whole integration. For system prompts, saved tools, context budgets and retention settings, see [Context control](https://twigg.ai/docs/context#configure-prompts-tools-and-context). ## 1. Create a chat, once ```bash curl -X POST 'https://api.twigg.ai/api/v1/chats' \ -H "Authorization: Bearer $TWIGG_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "namespace": "acme/proj-7", "title": "Support thread" }' ``` ```json { "id": "01912d4e-9c1a-7b3f-8a21-6f1e2c3d4b5a", "namespace": "acme/proj-7", "title": "Support thread", "description": null, "user_metadata": {}, "created_at": "2026-09-06T10:24:00Z" } ``` Keep `id`. Everything after this addresses it. `namespace` is a path you choose; it groups chats for listing and selects which instructions, tools and policies apply. It is immutable after creation. Omit it for the organisation-wide scope. This call happens once per conversation. Steps 2 to 4 below are the loop, and they repeat for its whole life: submit what just happened, read the stream, and where a tool was called, submit its result and read the stream again. Steps 2 and 4 are the same endpoint. ## 2. Send what just happened Send the prompt and the model. Never the transcript: Twigg has it. ```bash curl -N -X POST 'https://api.twigg.ai/api/v1/chats/01912d4e-9c1a-7b3f-8a21-6f1e2c3d4b5a/responses' \ -H "Authorization: Bearer $TWIGG_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "claude-opus-5", "input": [ { "type": "prompt", "text": "What changed in the billing module?" } ], "idempotency_key": "billing-turn-1" }' ``` The response is `text/event-stream`. There is no non-streaming mode. Pick any `model` from `GET /api/v1/models`; it is a per-request choice, not a property of the chat. ## 3. Read the stream Events arrive in this order. Each is `event: ` followed by `data: `. | 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. | If `done.pending_tool_calls` is non-empty, the chat is waiting for you to run those tools and send the results back. Dropping the connection cancels nothing. The turn completes, appends to the ledger and is billed. Read the answer from `/history` on reconnect. ## 4. Run the tool, send the result back Same endpoint. A `tool_result` part instead of a `prompt`. ```json { "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" } ] } ``` `trust` is `trusted`, `customer_data` (default) or `untrusted`. It sets the write-time hygiene limits on the result. It is not an injection defence. Read the stream exactly as in step 3; that is where the loop closes and begins again. Twigg never executes a tool. Every open tool call is closed on your next submission: answered ones with your result, unanswered ones with a tombstone the `run` event names in `closed_tool_calls`. ## Errors Before the first byte, an ordinary HTTP status with one envelope: ```json { "error": { "code": "payment_required", "message": "…" } } ``` | status | code | when | |---|---|---| | 400 | `bad_request` | Malformed request, including an invalid UUID in a path. | | 401 | `unauthorized` | Missing or invalid key. | | 402 | `payment_required` | The balance cannot cover the turn's input. Top up in the console. | | 404 | `not_found` | Unknown route, chat, run or model. Includes `hint: "https://api.twigg.ai/"` for discovery. | | 409 | `conflict` | A run is already open on this chat, or the idempotency key names an earlier run. | | 413 | `payload_too_large` | The body is over the size limit. | | 422 | `validation_error` | A field failed validation, including tools the model cannot take. | | 429 | `rate_limited` | Slow down. | | 500 | `internal_error` | A fault on our side. | | 502 | `external_service_error` | The upstream provider failed. | | 503 | `unavailable` | Temporarily out of service. Retry. | The `done.model_served` field is the provider-reported identifier, which can differ from the request's catalogue name: `gpt-5-6-luna` may be served as `gpt-5.6-luna`. Always select request models from the live catalogue's `name` field; do not copy `model_served` into a future request. ### Retrying after a dropped connection Provider rate limits and overloads are retried up to three times before any model stream event is forwarded, with exponential backoff and jitter. `Retry-After` is respected; requests requiring more than 30 seconds of cumulative backoff fail without retrying early. Model-provider failures include a safe category in `error.details.kind` and a `provider_request_id` when available. Failed runs retain these diagnostics in `manifest.error`, with the provider ID also available on the run; raw provider error bodies are not returned. An `idempotency_key` prevents creating another run for the same submission; it does not replay a stream. Reusing a key within its 24-hour window returns `409 conflict`, with the original run ID in the error message. If you received the initial `run` event, you already have that ID. Use `GET /api/v1/runs/{run_id}` to inspect the original run, and `GET /api/v1/chats/{chat_id}/history` to retrieve persisted output. If it is still running, check again later. Reuse the same key when the outcome of submission is uncertain; changing the key can create a second run and another charge. ### Retrying a confirmed failed run A failed run retains submitted input without appending partial assistant output; retry the latest failed run with `POST /api/v1/chats/{chat_id}/responses`, setting `input: []`, `retry_of` to its run ID, and a new `idempotency_key`. Supply the model and any request-level tools again (these can change); the retry creates a separately billed run using the existing history and current configuration. If it fails, use the new run ID for the next retry, and discard partial streamed tool arguments after an `error` event. An API key never earns a 403: a key that may not reach something is refused as a 401. The console's own routes add `forbidden` and `forbidden_requires_admin` for permission failures, which is a session's concern, not a key's. A `validation_error` may carry a `details` object beside `code` and `message`. Its `reason` can be `fixed_content_exceeds_budget` (with `fixed_tokens`, `budget_tokens`), `current_turn_exceeds_capacity` (with `current_turn_tokens`, `capacity_tokens`), or `checkpoint_exceeds_capacity` (with `required_tokens`, `capacity_tokens`). These describe protected content that cannot fit in the available input budget. After the first byte, the same `code` and `message` arrive as the terminal `error` event, since the HTTP status has already been sent. ## Reading back ``` GET /api/v1/chats/{chat_id}/history?after_ordinal=40&include=stubs GET /api/v1/runs/{run_id} GET /api/v1/chats?namespace=acme/proj-7&limit=20 GET /api/v1/namespaces ``` History is oldest first within a page and paged by `ordinal`, the ledger's total order. A call with no cursor opens at the newest end: it returns the last `limit` parts, and you page back with `before_ordinal`. Ordinals are never renumbered and deletes leave gaps: page with the `first_ordinal` and `last_ordinal` you were given, never by arithmetic. `include` is `stubs` (default: attachment descriptors), `full` (base64 bytes, page capped at 20 parts) or `none`. ## What you must not assume - No transcript is ever sent by you. Sending prior messages as new prompts duplicates them in the ledger. - A namespace is a scope, not a permission. Tenancy is your organisation behind the key; separating your own customers is your application's job. - Uppercase in a namespace is a 422, not folded. Use lowercase opaque ids for per-user leaves. - `model` is the catalogue name from `/api/v1/models`, never the provider's own wire string. ## Further reading - Overview and concepts: https://twigg.ai/docs/overview.md - Context control (budgets, retention, compaction): https://twigg.ai/docs/context.md - Full API reference: https://twigg.ai/docs/api.md - Model catalogue with prices: https://api.twigg.ai/v1/catalogue/models.md ## Sending attachments Use the selected model's `media.user` or `media.tool_result` capability from `GET /api/v1/models` to check formats and limits. Send attachments inline: ```json {"type":"prompt","text":"Describe this image","media":[{"mime":"image/png","data_base64":"","filename":"image.png"}]} ``` Place this part in the response request's `input` array. Do not prefix the base64 with a data URI. Unsupported new media on configured models returns HTTP 422 without appending the turn. When switching models, retained historical media may instead be omitted with translation warnings; see [context](context.md). --- # Twigg: overview A single API for LLM use. One API key gives you access to most major LLMs and a stateful conversation service that manages, validates and compacts context for you. You no longer need to worry about context window limits, provider API schemas, or storing and replaying context efficiently: send the next event, and name the model you want it to go to. For system prompts, saved tools, context budgets and retention settings, see [Context control](https://twigg.ai/docs/context#configure-prompts-tools-and-context). ## What it is A context store, assembler and model router in one. What it replaces: - The table you were going to design for messages, tool calls and their results. - The code that decides what to drop when the transcript outgrows the window. - The per-provider translation layer, and the replay artifacts it has to preserve. - The token accounting behind whatever you bill for. What it leaves you: - The agent loop. Twigg lets you focus on the logic of your application, not the mechanics of managing context. It never executes a tool; it tells you one was called. - The implementation of your tools, and what they do. - The choice of model you wish to use, from our catalogue. - Your chat permission management. Twigg lets you submit chats under namespaces and tag on any `user_metadata`; you decide how those namespaces are structured. A provider's stateful API stores your conversation inside the thing that generated it, so the stored thread is also the reason you cannot leave. Twigg stores it beside the models. `model` is a per-request choice, not a property of the chat, and nothing in the ledger is written in any one provider's dialect. A chat generated with one model can be continued with another simply by changing the `model` field on the next request. Scope: conversational turns with tools, across every model in the catalogue. Not embeddings, image generation, batch or fine-tuning, and never executing a tool. ## Three things to learn - **Chat** (`chat_id`). A handle to the conversation you are working with, and what the context is associated to. `POST /api/v1/chats` returns one; every submission to `/responses` carries it, and the event is appended to that chat's context. - **Namespace** (`user_83022938/project_1234`). A logical grouping of chats, so you can organise conversations in a structured way — each user in your application might have their own. API calls can be filtered by namespace, and each namespace carries its own rules and configuration. - **Run** (`run_id`). A single interaction with the responses API. It may span several parts: a reasoning block, a text block, a tool call. It records what was assembled, which model served it, and what it cost, and is the id you use for auditing requests and usage. ## Namespaces A namespace is a path you choose, such as `acme/proj-7/8f2c…a91`, set on a chat at creation and immutable after. It groups chats for listing, and it is the scope key that decides which instructions, tools, retention policy and context budget every turn on that chat receives. Resolution walks up the path. Configuration published at `acme` applies to everything beneath it. Instructions and tool bundles combine down the path, each level choosing `append` or `replace`. Retention policies and context budgets do not combine: the nearest configured level is used whole. There is nothing to provision. Creating a chat is how a namespace comes into existence; it leaves the listing when its last chat is deleted. That is why a typo is not an error: a mistyped namespace quietly resolves to the organisation-wide defaults. `GET /api/v1/namespaces` lists what your chats are actually in, so `acme/prdo` shows up next to `acme/prod`. Shape rules: - Segments of `a-z 0-9 _ -`, joined by `/`. No empty segment. - 255 bytes for the whole path, 64 for any one segment. - Uppercase is rejected with a 422, not folded. Folding would merge two of your end users into one scope. - For a per-user leaf, use an opaque lowercase id. A namespace is not a security boundary. Tenancy is enforced by the organisation behind your API key and nothing else. ## Authentication One API key, sent as `Authorization: Bearer tw_live_…`, authenticates the customer operations listed below. Discovery, version information, and the public updates feed do not require a key. The console's own routes under `/v1` run on a session cookie instead, with the public ones excepted: the catalogue linked at the foot of this page needs no credential at all. Neither credential substitutes for the other. An API key opens nothing in the console, and a session cookie opens none of the authenticated customer operations. Every error uses one envelope: ```json { "error": { "code": "conflict", "message": "A run is already open on this chat." } } ``` A failure after the response has opened arrives as a terminal `error` event carrying the same `code`. ## The loop Once: 1. `POST /api/v1/chats` creates the chat and returns its id. Then, every turn. Steps 2 to 4 repeat for the life of the conversation, and 2 and 4 are the same endpoint: 2. `POST /api/v1/chats/{chat_id}/responses` sends what just happened: a prompt and the model. Not the transcript. 3. Read the `text/event-stream`. If `done.pending_tool_calls` is non-empty, the chat is waiting on you. 4. `POST /api/v1/chats/{chat_id}/responses` again, the same endpoint, with a `tool_result` instead of a prompt. Name a different model here if you want one. If a process dies mid-conversation, the next one picks it up from the chat id alone. ## Billing Twigg is prepaid. Your organisation holds a credit balance in USD; every run is checked against it before it opens and charged against it once it closes. There is no end-of-month invoice to reconcile. - **Before the run.** Pre-flight checks the balance covers the input plus the most the output could cost, at your billed rates, and caps `max_tokens` against what is left. A 402 means the balance cannot cover the input alone. - **After the run.** Settlement charges what was actually used, at the rates frozen when the run opened, so a catalogue price change is never retroactive. Compaction is charged as its own line, because it is its own model call. Concurrent runs can drive a balance slightly negative. If a provider reports cache-write tokens but the run has no configured cache-write rate, those tokens are charged at the applicable input rate. This applies to both chat and compaction runs. Usage still records them as cache writes; an explicit cache-write rate, including zero, takes precedence. Add funds from Console → Billing, from $5 up. The charge goes to the card on file and the credit is applied when the payment settles, a moment later rather than immediately. Every attempt, manual or automatic, is listed with its outcome. Cards are held by Stripe and managed through their portal; Twigg never sees a card number. **Auto top-up.** Set a threshold and an amount: when the balance falls below the threshold, the card on file is charged that amount, behind whichever request crossed the line, so a conversation does not stop while it runs. The amount must be larger than the threshold, or a top-up would land the balance still under the line and fire again on the next turn. The rule can be saved before a card is on file; it simply will not fire until there is one. **You may have to approve the first charge.** Auto top-ups are charged off-session, with nobody at a keyboard, and a bank can refuse to authorise a card that way until it has been charged once with the cardholder present. If your first automatic top-up is declined for authentication, add funds manually once from the console, approving it with your bank if asked. Later top-ups then run unattended on the same card. ## Endpoints | 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 | `/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 | | GET | `/v1/catalogue` | None | List public model prices | | GET | `/v1/catalogue/models.md` | None | Read model prices as Markdown | | GET | `/v1/catalogue/{name}` | None | Get public model prices | Prices for every model, including any applicable markup, are at https://api.twigg.ai/v1/catalogue/models.md. The live catalogue states the current environment’s margin; JSON `markup_percent: "0"` means provider cost. Prices already include that markup, so do not add it again. ## Catalogue performance The catalogue's `uptime` field reports the observed success rate of completed provider requests over the last 24 hours, after automatic retries. It is refreshed every 15 minutes and ranges from `0` to `1`; `null` means there are no eligible results. Both chat and compaction requests contribute. Local validation errors, interrupted runs and historical failures without a provider classification are excluded. This is a traffic-based measure, not continuous monitoring or an uptime SLA; a lightly used model may have very few observations. `avg_time_to_first_token_ms` is the mean time in milliseconds from starting a provider request to its first nonempty output delta, across successful requests completed in the last 24 hours. It includes automatic retry waits and counts streamed text, reasoning, refusals and tool arguments; headers and empty block starts do not count. Hidden reasoning cannot be measured until output arrives. Historical runs without timing and responses with no output deltas are excluded; `null` means no timing samples are available. Context preparation and compaction before a customer provider call are not part of that call's timing. Both measurements are refreshed in the background every 15 minutes. Catalogue requests read the stored results without scanning request history. Figures depend on the workload (including prompt size and reasoning settings), and may remain at their last measured values if a refresh fails. --- # Twigg: context control Two documents govern what a turn carries to the model. Both are published per namespace from the dashboard or customer API and versioned. Neither travels on a request: a run resolves them for its own scope. Publishing makes a new version; old versions stay listed and any can be made active again. ## Context budget A budget decides how much history a turn may carry and how hard the service works to make it fit. Set it with `POST /api/v1/config/budgets` or in Dashboard → Configuration → Context window, per namespace and per model; it resolves server-side, so it never travels on a request. A budget caps total estimated input, including instructions, tools, and history. Text uses a local tokenizer with a 20% safety margin across providers, without a provider token-count request. This is an estimate for context fitting; billing uses the provider's reported usage. It is `(context_window - reserved_output) × (1 - headroom)`, lowered by an optional token ceiling. Output defaults to the smaller of 16,384 tokens and the model's output limit; a request can specify its own output limit. | option | meaning | |---|---| | `version` | Required schema version: `3`. | | `headroom` | Required fraction left unused after output reservation; default policy uses 0.10. | | `max_history_tokens` | Optional ceiling of at least 32,000 tokens. `null` uses the available model budget. | | `history_mode` | `summarize` (default) keeps one saved prefix summary plus recent original history. `drop_oldest` omits the oldest history between the anchor and recent exchange, without summary calls. | | `eager_compaction` | `{"mode":"off"}` or `{"mode":"at_fill","at":0.8}` (default). Prepare summaries after a run reaches the selected fill. Must be off in drop-oldest mode. | Summarization rolls the earlier checkpoint and newly aged history into a new checkpoint when needed. Older checkpoints and original messages remain saved. Switching to a smaller window may generate missing summaries; a larger window can use an earlier checkpoint or restore all originals. Summaries are lossy: older details may lose fidelity as a conversation continues. Tool arguments, results, names, errors and call identifiers are available to the summarizer. Media contributes attachment descriptions, and reasoning signatures are excluded. Your retention options still control original parts replayed to the model, including tool truncation, media, reasoning and anchors. Changing retention does not rewrite saved summaries. Drop-oldest reports `history_dropped` with the omitted ordinal range. Neither mode deletes stored history. Instructions, tools, the anchor and the current exchange must themselves fit; otherwise the request reports a capacity error. The service supports chat models with context windows of at least 128,000 tokens. Checkpoint size, cadence, source chunk size and compression ratios are internal platform policy, not customer options. Read current defaults with `GET /api/v1/config/defaults` using your API key. A budget can be the scope's default for every model, or tagged to specific models. A tag at a level beats that level's untagged budget. The stream says which answered with a `context_budget_resolved_at` warning. ## Retention policy A retention policy says what a part looks like when it is replayed to the model at a given point in the context. It is what stops large images, files and tool results being replayed verbatim once they are far down the history. Author it per namespace with `POST /api/v1/config/retention` or in the dashboard; it applies to every run in that scope. It never changes what is stored: a pruned attachment is still in your ledger and still comes back from `/history`. It stops being sent, and stops being billed, on every subsequent turn. What you can express: - **Attachments**: a boundary for everything, per MIME family (`image/*`), or for one exact type. - **Reasoning**: one boundary. - **Tool payloads**: a rule for all tools, a name prefix (`gh_*`), or one tool, with results and arguments treated separately. - **Failed calls**: an override that replaces the matched rule when a tool returned an error. - **Prose**: separate treatments for what you sent and what the model replied. Thresholds keep, they do not cut. A threshold is a share of the assembled window measured back from the newest part, and higher keeps more: `1` replays however old, `0` never replays, `0.25` replays within the newest quarter. The current turn is exempt from retention pruning. New attachments must still pass that model's format, carrier and size/count checks before the turn is accepted. The platform default for media is `0`: sent once, never replayed. Rules match first-hit, in list order. A broad rule above a narrow one makes the narrow one dead, and the backend refuses to publish that arrangement. Truncation is `keep` (never cut), `always` (cut to a size) or `over` (cut only past a threshold), keeping `head`, `tail` or `head_and_tail`. `mark_truncations` is on by default; a model that cannot tell output was cut will invent the missing middle. `anchor_parts` holds the first N parts verbatim, never compacted and never truncated. ## Compaction When history no longer fits, Twigg summarizes an older prefix and keeps recent history verbatim. There is no fixed 40,000-token guarantee: the retained amount depends on the model window, output reservation, budget and retention settings. The current exchange and any configured anchor remain protected; if they cannot fit with the required summary, the request returns a capacity error. Saved summaries can be reused, and original history remains available. Increasing the budget can restore original messages without rewriting existing summaries. Compaction currently uses OpenAI's `gpt-5-6-luna`, regardless of the model selected for the chat turn. History selected for summarisation is sent to OpenAI. The compaction model is platform-selected and cannot currently be configured by customers. Compaction is a real model call and is billed as its own `compaction` line, at the same rates as inference, so you can see what context management costs. It is allowed to run at zero balance. A `compacting` event precedes the wait it describes, and `done.compaction` reports what was generated. ## Where a policy lives | scope | handle | meaning | |---|---|---| | Organisation | `namespace: null` | The default every chat resolves against when nothing nearer is published. | | Namespace | `acme/proj-7` | Set on the chat at creation. Resolution walks up from here. | | Model tag | budget only | A budget tagged to specific models. Beats the level's untagged budget. | A request-level tool with the same name as a saved tool takes precedence for that request. This replacement does not emit a `tool_shadowed` warning; that warning covers shadowing while resolving saved tool bundles. Policies do not merge. Instructions and tool bundles do, with an explicit `append` or `replace` mode. Resolution that was legal but worth knowing about arrives as `config_warnings` at the head of the stream: `retention_policy_overridden` names the displaced level, `context_budget_resolved_at` names the level and model tag that answered. ## Model attachment capabilities Read `media` from the model catalogue before sending attachments. `user`, `tool_result` and `assistant` have separate MIME allowlists and byte/count limits. An empty MIME list means unsupported; `configured: false` means model-specific support has not been configured, and legacy adapter behaviour applies. For configured models, unsupported new attachments return HTTP 422 before the turn is persisted. Prompt text is limited to 500,000 Unicode characters per part, and a published system-instruction body is limited to 131,072 UTF-8 bytes. The deployment-wide HTTP body limit can reject a large request earlier with HTTP 413; base64 encoding contributes to that size. Retention runs first when preparing history. Retained attachments that the chosen model cannot accept become text markers with `media_unsupported` or `media_limit_exceeded` translation warnings. Count limits include history, with newest attachments retained first. Stored bytes remain intact, so switching back to a compatible model can replay them when your retention policy keeps them. This also applies to provider calls used for compaction. The live [model catalogue](https://api.twigg.ai/v1/catalogue/models.md) lists the current formats and limits; provider and model names alone do not imply support. ## Using an agent or want app-level control? You can also configure these using the API. Set system prompts, saved tools, context budgets and retention policies directly from your application or agent, using the same validation and versioning as the dashboard. - **System prompts** — `POST /api/v1/config/instructions` publishes and activates an instruction version. - **Saved tools** — `POST /api/v1/config/tools` publishes reusable tool definitions. - **Retention and truncation** — `POST /api/v1/config/retention` controls how history is replayed. - **Context budgets** — `POST /api/v1/config/budgets` configures history and compaction settings, with optional model-specific budgets. ### Start with a namespace Authenticate with `Authorization: Bearer `. Every read and change is restricted to the organisation associated with that key; another organisation's configuration version IDs return `404 not_found`. Set `namespace` in the publish body, or in the query string for listing and resolution. Omit it for organisation-wide scope. A chat inherits settings from the namespace chosen at creation. For example, publish instructions for one namespace: ```bash curl -X POST 'https://api.twigg.ai/api/v1/config/instructions' \ -H "Authorization: Bearer $TWIGG_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "namespace": "acme/proj-7", "mode": "append", "body": "You are a helpful support assistant. Ask before changing customer data." }' ``` ### Inspect, restore and track usage Read starting policies with `GET /api/v1/config/defaults`. Use `GET /api/v1/config/resolved` and `GET /api/v1/config/resolved-budget` to inspect the effective settings and their sources before changing anything. Configuration lists provide version history. The `/{id}/activate` operations restore a previous version; DELETE operations withdraw an override without deleting its history. Query namespace usage with `GET /api/v1/usage?range=7d&namespace=acme/proj-7`. This includes descendants within your organisation. Add `group_by=namespace` for a breakdown, or omit `namespace` for organisation-wide usage. Reports lag ingestion and may be cached for three minutes. ### Scope and request settings For budgets, an empty `model_ids` list selects the scope default; otherwise supply model UUIDs from the catalogue. Multi-model publishing applies each model separately, so a later failure can leave earlier models updated. Budgets control how much of a model's window is used, not its maximum window size. Per-request `tools` still apply only to that response request and override saved tools with the same name. `max_tokens` controls that request's output limit, not a persistent policy. Your application executes tool calls and returns the results. See the [API reference](https://twigg.ai/docs/api) for every endpoint and schema. You can also manage these settings in [Dashboard → Configuration](https://twigg.ai/dashboard/configuration). API-key clients use `/api/v1/config`; dashboard routes under `/v1/config` remain session-authenticated. --- # Twigg: API reference Base URL: https://api.twigg.ai. Customer API operations under `/api/v1` require `Authorization: Bearer `. 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 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 { "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 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 { "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 { "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 { "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 { "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 { "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 { "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 { "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 { "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 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 ``` ## Streaming events Frames use `event: ` and `data: `, 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: ` followed by `data: `. 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 | |