openapi: 3.2.0 info: title: Tale Platform Threads API version: 3.27.0 contact: name: Tale url: https://tale.dev description: 'REST access to a Tale deployment: knowledge resources, automations and their runs, chat threads, agents, and skills — plus an MCP endpoint exposing the same platform to MCP clients.' servers: - url: '{origin}' description: The deployment this document describes — `GET /openapi.json` on a running instance fills it in variables: origin: default: https://your-host.example.com description: Scheme and host of the Tale deployment security: - bearerAuth: [] tags: - name: Threads description: Chat threads of the key holder. paths: /api/v1/models: get: tags: - Threads summary: List available chat models description: 'The configured chat models available to the key holder in this organization, filtered by model-access policy and direct API credentials. Subscription models that require a sandbox are excluded. An empty list means none are available; the list is a complete set, not paginated. Use id as model when sending a message, and providerSlug when the same id is listed under more than one provider. Each entry carries what a client needs to choose — context window, output cap, capabilities, price when the catalog publishes one, tags — and `default: true` marks the organization’s default pick for this key holder. The list is the organization’s configured catalog, not a promise from the provider’s account: a plan that excludes a listed model fails the turn with errorCode `model_not_entitled`, a spent balance with `credit_exhausted`.' operationId: listChatModels security: - bearerAuth: [] responses: '200': description: Available models content: application/json: schema: type: object description: 'The complete set under `models` — never paginated: no `isDone`, no cursor to follow' x-tale-pagination: none required: - models - harnesses properties: models: type: array items: $ref: '#/components/schemas/ChatModel' harnesses: type: array description: The harnesses a project agent may run on — the managed lane’s, those that run unattended on the platform’s own credentials; `harness` is the value `POST /api/v1/projects/{id}/agents` takes, and a value outside this list answers 400 `PROJECT_AGENT_HARNESS_INVALID` naming the list in `data.harnesses` items: type: object required: - harness - label properties: harness: type: string description: The slug the agent input takes label: type: string description: The harness’s display name headers: ETag: $ref: '#/components/headers/ETag' Cache-Control: $ref: '#/components/headers/CacheControl' X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '304': description: Not Modified — `If-None-Match` named the current `ETag`, so the body is not sent headers: ETag: $ref: '#/components/headers/ETag' Cache-Control: $ref: '#/components/headers/CacheControl' X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '400': description: 'Invalid request: a body the schema refuses — a wrong type, an unknown key, a missing field, a NUL or an unpaired surrogate, a whole number beyond 2^53 − 1 (`INVALID_BODY`, every problem under `data.issues`); a query parameter the operation does not take, given twice or left blank (`INVALID_QUERY`); on a list, a `limit` that is not a whole number (`INVALID_LIMIT`) or a `cursor` this list did not answer (`INVALID_CURSOR`)' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '401': description: Missing or invalid API key (`UNAUTHORIZED`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' WWW-Authenticate: $ref: '#/components/headers/WWWAuthenticate' '403': description: '`X-Organization-Slug` names an organization the key holder is no member of (`ORG_FORBIDDEN`, with the slugs the key holder may send under `data.organizations`)' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '404': description: '`X-Organization-Slug` names no organization (`ORG_SLUG_INVALID`, with the slugs the key holder may send under `data.organizations`)' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '405': description: The path exists, but not for this method (`METHOD_NOT_ALLOWED`); `Allow` names the methods it serves content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Allow: $ref: '#/components/headers/Allow' '429': description: 'The key holder’s request budget is spent (`RATE_LIMITED`): `Retry-After` names the wait in whole seconds and `data.retryAfterMs` in milliseconds; the envelope carries a `requestId`' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Retry-After: $ref: '#/components/headers/RetryAfter' '500': description: Internal error (`INTERNAL_ERROR`); the envelope carries a `requestId` to quote when reporting it content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '503': description: 'The platform’s database is restarting or cannot be reached (`DATABASE_UNAVAILABLE`): `Retry-After` names the wait in whole seconds — retry with backoff; the envelope carries a `requestId`' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Retry-After: $ref: '#/components/headers/RetryAfter' parameters: - name: X-Organization-Slug in: header required: false schema: type: string description: The organization to operate on. STRICTLY REQUIRED on every call, reads included, when the key holder belongs to more than one organization — without it such requests are refused with 400 `ORG_SLUG_REQUIRED` (the dashboard’s last-active organization never steers a machine call). A slug that names no organization answers 404 `ORG_SLUG_INVALID`, one the key holder is no member of 403 `ORG_FORBIDDEN`. Single-organization keys may omit it; a blank or whitespace-only value reads as the header absent. Each of the three refusals lists the slugs the key holder may send under `data.organizations`; `GET /api/v1/me` answers them too. - name: X-Request-Id in: header required: false description: 'Your own correlation id, echoed back on the response and written to the platform’s log: up to 255 characters of letters, digits, `_`, `-` and `=`. Any other value is replaced by a fresh UUID (the response carries the id that was used), and uniqueness of an id you choose is yours to keep.' schema: type: string pattern: ^[A-Za-z0-9_=-]{1,255}$ /api/v1/threads: get: tags: - Threads summary: List your personal threads description: Only the key holder’s threads without a project. Project threads are available through their project URL. Includes archived threads, newest activity first. operationId: listThreads security: - bearerAuth: [] parameters: - name: X-Organization-Slug in: header required: false schema: type: string description: The organization to operate on. STRICTLY REQUIRED on every call, reads included, when the key holder belongs to more than one organization — without it such requests are refused with 400 `ORG_SLUG_REQUIRED` (the dashboard’s last-active organization never steers a machine call). A slug that names no organization answers 404 `ORG_SLUG_INVALID`, one the key holder is no member of 403 `ORG_FORBIDDEN`. Single-organization keys may omit it; a blank or whitespace-only value reads as the header absent. Each of the three refusals lists the slugs the key holder may send under `data.organizations`; `GET /api/v1/me` answers them too. - name: cursor in: query required: false schema: type: string description: The previous page’s `continueCursor`, unchanged — opaque; omit for the first page. A value this list did not answer is refused with 400 `INVALID_CURSOR` rather than read as the first page - name: limit in: query required: false schema: type: integer minimum: 1 maximum: 100 default: 25 description: Page size, 1..100 (default 25; out-of-range numbers are clamped, a value that is not a number answers 400 `INVALID_LIMIT`) - name: X-Request-Id in: header required: false description: 'Your own correlation id, echoed back on the response and written to the platform’s log: up to 255 characters of letters, digits, `_`, `-` and `=`. Any other value is replaced by a fresh UUID (the response carries the id that was used), and uniqueness of an id you choose is yours to keep.' schema: type: string pattern: ^[A-Za-z0-9_=-]{1,255}$ responses: '200': description: Paginated threads content: application/json: schema: type: object x-tale-pagination: keyset required: - page - isDone - continueCursor properties: page: type: array items: $ref: '#/components/schemas/Thread' isDone: type: boolean continueCursor: type: string description: Pass back as `cursor` for the next page; empty when `isDone` headers: ETag: $ref: '#/components/headers/ETag' Cache-Control: $ref: '#/components/headers/CacheControl' X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '304': description: Not Modified — `If-None-Match` named the current `ETag`, so the body is not sent headers: ETag: $ref: '#/components/headers/ETag' Cache-Control: $ref: '#/components/headers/CacheControl' X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '400': description: 'Invalid request: a body the schema refuses — a wrong type, an unknown key, a missing field, a NUL or an unpaired surrogate, a whole number beyond 2^53 − 1 (`INVALID_BODY`, every problem under `data.issues`); a query parameter the operation does not take, given twice or left blank (`INVALID_QUERY`); on a list, a `limit` that is not a whole number (`INVALID_LIMIT`) or a `cursor` this list did not answer (`INVALID_CURSOR`)' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '401': description: Missing or invalid API key (`UNAUTHORIZED`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' WWW-Authenticate: $ref: '#/components/headers/WWWAuthenticate' '403': description: '`X-Organization-Slug` names an organization the key holder is no member of (`ORG_FORBIDDEN`, with the slugs the key holder may send under `data.organizations`)' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '404': description: '`X-Organization-Slug` names no organization (`ORG_SLUG_INVALID`, with the slugs the key holder may send under `data.organizations`)' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '405': description: The path exists, but not for this method (`METHOD_NOT_ALLOWED`); `Allow` names the methods it serves content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Allow: $ref: '#/components/headers/Allow' '429': description: 'The key holder’s request budget is spent (`RATE_LIMITED`): `Retry-After` names the wait in whole seconds and `data.retryAfterMs` in milliseconds; the envelope carries a `requestId`' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Retry-After: $ref: '#/components/headers/RetryAfter' '500': description: Internal error (`INTERNAL_ERROR`); the envelope carries a `requestId` to quote when reporting it content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '503': description: 'The platform’s database is restarting or cannot be reached (`DATABASE_UNAVAILABLE`): `Retry-After` names the wait in whole seconds — retry with backoff; the envelope carries a `requestId`' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Retry-After: $ref: '#/components/headers/RetryAfter' post: tags: - Threads summary: Create a personal chat description: A direct chat with the built-in assistant. The thread has no project context. This operation accepts only an optional title (trimmed, 1–120 characters — the bound `PATCH` renames within); agent selectors and projectId are refused. Project agents execute through project tasks. A thread created without a title is named by the assistant after its first message (read `title` back from the thread); `PATCH` renames it. operationId: createThread security: - bearerAuth: [] parameters: - name: X-Organization-Slug in: header required: false schema: type: string description: The organization to operate on. STRICTLY REQUIRED on every call, reads included, when the key holder belongs to more than one organization — without it such requests are refused with 400 `ORG_SLUG_REQUIRED` (the dashboard’s last-active organization never steers a machine call). A slug that names no organization answers 404 `ORG_SLUG_INVALID`, one the key holder is no member of 403 `ORG_FORBIDDEN`. Single-organization keys may omit it; a blank or whitespace-only value reads as the header absent. Each of the three refusals lists the slugs the key holder may send under `data.organizations`; `GET /api/v1/me` answers them too. - name: X-Request-Id in: header required: false description: 'Your own correlation id, echoed back on the response and written to the platform’s log: up to 255 characters of letters, digits, `_`, `-` and `=`. Any other value is replaced by a fresh UUID (the response carries the id that was used), and uniqueness of an id you choose is yours to keep.' schema: type: string pattern: ^[A-Za-z0-9_=-]{1,255}$ requestBody: required: false content: application/json: schema: type: object additionalProperties: false properties: title: type: string minLength: 1 maxLength: 120 description: The thread’s name, trimmed (1–120 characters) — the same bound PATCH renames within responses: '201': description: Created thread ID content: application/json: schema: type: object required: - id properties: id: type: string headers: Location: $ref: '#/components/headers/Location' X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '400': description: 'Invalid request: a body the schema refuses — a wrong type, an unknown key, a missing field, a NUL or an unpaired surrogate, a whole number beyond 2^53 − 1 (`INVALID_BODY`, every problem under `data.issues`); a query parameter the operation does not take, given twice or left blank (`INVALID_QUERY`); on a list, a `limit` that is not a whole number (`INVALID_LIMIT`) or a `cursor` this list did not answer (`INVALID_CURSOR`)' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '401': description: Missing or invalid API key (`UNAUTHORIZED`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' WWW-Authenticate: $ref: '#/components/headers/WWWAuthenticate' '403': description: '`X-Organization-Slug` names an organization the key holder is no member of (`ORG_FORBIDDEN`, with the slugs the key holder may send under `data.organizations`)' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '404': description: '`X-Organization-Slug` names no organization (`ORG_SLUG_INVALID`, with the slugs the key holder may send under `data.organizations`)' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '405': description: The path exists, but not for this method (`METHOD_NOT_ALLOWED`); `Allow` names the methods it serves content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Allow: $ref: '#/components/headers/Allow' '413': description: The body exceeds this operation’s byte cap — 1 MiB unless the description says otherwise (`BODY_TOO_LARGE`); the envelope carries a `requestId` content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '429': description: 'The key holder’s request budget is spent (`RATE_LIMITED`): `Retry-After` names the wait in whole seconds and `data.retryAfterMs` in milliseconds; the envelope carries a `requestId`' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Retry-After: $ref: '#/components/headers/RetryAfter' '500': description: Internal error (`INTERNAL_ERROR`); the envelope carries a `requestId` to quote when reporting it content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '503': description: 'The platform’s database is restarting or cannot be reached (`DATABASE_UNAVAILABLE`): `Retry-After` names the wait in whole seconds — retry with backoff; the envelope carries a `requestId`' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Retry-After: $ref: '#/components/headers/RetryAfter' /api/v1/threads/{id}: get: tags: - Threads summary: Read a thread description: Only the key holder’s threads without a project. Project threads are available through their project URL. operationId: getThread security: - bearerAuth: [] parameters: - name: X-Organization-Slug in: header required: false schema: type: string description: The organization to operate on. STRICTLY REQUIRED on every call, reads included, when the key holder belongs to more than one organization — without it such requests are refused with 400 `ORG_SLUG_REQUIRED` (the dashboard’s last-active organization never steers a machine call). A slug that names no organization answers 404 `ORG_SLUG_INVALID`, one the key holder is no member of 403 `ORG_FORBIDDEN`. Single-organization keys may omit it; a blank or whitespace-only value reads as the header absent. Each of the three refusals lists the slugs the key holder may send under `data.organizations`; `GET /api/v1/me` answers them too. - name: id in: path required: true schema: type: string description: Thread ID - name: X-Request-Id in: header required: false description: 'Your own correlation id, echoed back on the response and written to the platform’s log: up to 255 characters of letters, digits, `_`, `-` and `=`. Any other value is replaced by a fresh UUID (the response carries the id that was used), and uniqueness of an id you choose is yours to keep.' schema: type: string pattern: ^[A-Za-z0-9_=-]{1,255}$ responses: '200': description: The thread content: application/json: schema: $ref: '#/components/schemas/Thread' headers: ETag: $ref: '#/components/headers/ETag' Cache-Control: $ref: '#/components/headers/CacheControl' X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '304': description: Not Modified — `If-None-Match` named the current `ETag`, so the body is not sent headers: ETag: $ref: '#/components/headers/ETag' Cache-Control: $ref: '#/components/headers/CacheControl' X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '400': description: 'Invalid request: a body the schema refuses — a wrong type, an unknown key, a missing field, a NUL or an unpaired surrogate, a whole number beyond 2^53 − 1 (`INVALID_BODY`, every problem under `data.issues`); a query parameter the operation does not take, given twice or left blank (`INVALID_QUERY`); on a list, a `limit` that is not a whole number (`INVALID_LIMIT`) or a `cursor` this list did not answer (`INVALID_CURSOR`)' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '401': description: Missing or invalid API key (`UNAUTHORIZED`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' WWW-Authenticate: $ref: '#/components/headers/WWWAuthenticate' '403': description: '`X-Organization-Slug` names an organization the key holder is no member of (`ORG_FORBIDDEN`, with the slugs the key holder may send under `data.organizations`)' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '404': description: Thread missing, owned by another user, or outside the visible URL scope; or `X-Organization-Slug` names no organization (`ORG_SLUG_INVALID`, with the slugs the key holder may send under `data.organizations`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '405': description: The path exists, but not for this method (`METHOD_NOT_ALLOWED`); `Allow` names the methods it serves content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Allow: $ref: '#/components/headers/Allow' '429': description: 'The key holder’s request budget is spent (`RATE_LIMITED`): `Retry-After` names the wait in whole seconds and `data.retryAfterMs` in milliseconds; the envelope carries a `requestId`' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Retry-After: $ref: '#/components/headers/RetryAfter' '500': description: Internal error (`INTERNAL_ERROR`); the envelope carries a `requestId` to quote when reporting it content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '503': description: 'The platform’s database is restarting or cannot be reached (`DATABASE_UNAVAILABLE`): `Retry-After` names the wait in whole seconds — retry with backoff; the envelope carries a `requestId`' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Retry-After: $ref: '#/components/headers/RetryAfter' patch: tags: - Threads summary: Archive, restore or rename a thread description: 'Only the key holder’s threads without a project. Project threads are available through their project URL. Archiving is the app’s own toggle, audited the same way: an archived thread stays readable and refuses messages with 409 `CHAT_THREAD_ARCHIVED`; `archived: false` restores it. A thread whose turn is still running — or whose accepted send is still queued — refuses the archive with 409 `CHAT_TURN_IN_PROGRESS`, as the delete does: cancel the turn first (restoring and renaming stay open mid-turn). Archiving stamps `archivedAt` and leaves `updatedAt` — the last message activity — untouched. `title` renames the thread (trimmed, 1–120 characters). Send at least one of the two.' operationId: updateThread security: - bearerAuth: [] parameters: - name: X-Organization-Slug in: header required: false schema: type: string description: The organization to operate on. STRICTLY REQUIRED on every call, reads included, when the key holder belongs to more than one organization — without it such requests are refused with 400 `ORG_SLUG_REQUIRED` (the dashboard’s last-active organization never steers a machine call). A slug that names no organization answers 404 `ORG_SLUG_INVALID`, one the key holder is no member of 403 `ORG_FORBIDDEN`. Single-organization keys may omit it; a blank or whitespace-only value reads as the header absent. Each of the three refusals lists the slugs the key holder may send under `data.organizations`; `GET /api/v1/me` answers them too. - name: id in: path required: true schema: type: string description: Thread ID - name: X-Request-Id in: header required: false description: 'Your own correlation id, echoed back on the response and written to the platform’s log: up to 255 characters of letters, digits, `_`, `-` and `=`. Any other value is replaced by a fresh UUID (the response carries the id that was used), and uniqueness of an id you choose is yours to keep.' schema: type: string pattern: ^[A-Za-z0-9_=-]{1,255}$ requestBody: required: true content: application/json: schema: type: object additionalProperties: false minProperties: 1 properties: archived: type: boolean title: type: string minLength: 1 maxLength: 120 description: The new name, trimmed responses: '200': description: The thread after the change content: application/json: schema: $ref: '#/components/schemas/Thread' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '400': description: 'Invalid request: a body the schema refuses — a wrong type, an unknown key, a missing field, a NUL or an unpaired surrogate, a whole number beyond 2^53 − 1 (`INVALID_BODY`, every problem under `data.issues`); a query parameter the operation does not take, given twice or left blank (`INVALID_QUERY`); on a list, a `limit` that is not a whole number (`INVALID_LIMIT`) or a `cursor` this list did not answer (`INVALID_CURSOR`)' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '401': description: Missing or invalid API key (`UNAUTHORIZED`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' WWW-Authenticate: $ref: '#/components/headers/WWWAuthenticate' '403': description: '`X-Organization-Slug` names an organization the key holder is no member of (`ORG_FORBIDDEN`, with the slugs the key holder may send under `data.organizations`)' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '404': description: Thread missing, owned by another user, or outside the visible URL scope; or `X-Organization-Slug` names no organization (`ORG_SLUG_INVALID`, with the slugs the key holder may send under `data.organizations`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '405': description: The path exists, but not for this method (`METHOD_NOT_ALLOWED`); `Allow` names the methods it serves content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Allow: $ref: '#/components/headers/Allow' '409': description: '`archived: true` while a turn is running on the thread, or an accepted send is still queued (`CHAT_TURN_IN_PROGRESS`)' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '413': description: The body exceeds this operation’s byte cap — 1 MiB unless the description says otherwise (`BODY_TOO_LARGE`); the envelope carries a `requestId` content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '429': description: 'The key holder’s request budget is spent (`RATE_LIMITED`): `Retry-After` names the wait in whole seconds and `data.retryAfterMs` in milliseconds; the envelope carries a `requestId`' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Retry-After: $ref: '#/components/headers/RetryAfter' '500': description: Internal error (`INTERNAL_ERROR`); the envelope carries a `requestId` to quote when reporting it content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '503': description: 'The platform’s database is restarting or cannot be reached (`DATABASE_UNAVAILABLE`): `Retry-After` names the wait in whole seconds — retry with backoff; the envelope carries a `requestId`' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Retry-After: $ref: '#/components/headers/RetryAfter' delete: tags: - Threads summary: Delete a thread description: Only the key holder’s threads without a project. Project threads are available through their project URL. Moves the thread to the app’s trash (its grace window and legal-hold check apply). A thread whose turn is still running — or whose accepted send is still queued — answers 409 `CHAT_TURN_IN_PROGRESS`; cancel the turn first. operationId: deleteThread security: - bearerAuth: [] parameters: - name: X-Organization-Slug in: header required: false schema: type: string description: The organization to operate on. STRICTLY REQUIRED on every call, reads included, when the key holder belongs to more than one organization — without it such requests are refused with 400 `ORG_SLUG_REQUIRED` (the dashboard’s last-active organization never steers a machine call). A slug that names no organization answers 404 `ORG_SLUG_INVALID`, one the key holder is no member of 403 `ORG_FORBIDDEN`. Single-organization keys may omit it; a blank or whitespace-only value reads as the header absent. Each of the three refusals lists the slugs the key holder may send under `data.organizations`; `GET /api/v1/me` answers them too. - name: id in: path required: true schema: type: string description: Thread ID - name: X-Request-Id in: header required: false description: 'Your own correlation id, echoed back on the response and written to the platform’s log: up to 255 characters of letters, digits, `_`, `-` and `=`. Any other value is replaced by a fresh UUID (the response carries the id that was used), and uniqueness of an id you choose is yours to keep.' schema: type: string pattern: ^[A-Za-z0-9_=-]{1,255}$ responses: '204': description: Deleted headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '400': description: 'Invalid request: a body the schema refuses — a wrong type, an unknown key, a missing field, a NUL or an unpaired surrogate, a whole number beyond 2^53 − 1 (`INVALID_BODY`, every problem under `data.issues`); a query parameter the operation does not take, given twice or left blank (`INVALID_QUERY`); on a list, a `limit` that is not a whole number (`INVALID_LIMIT`) or a `cursor` this list did not answer (`INVALID_CURSOR`)' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '401': description: Missing or invalid API key (`UNAUTHORIZED`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' WWW-Authenticate: $ref: '#/components/headers/WWWAuthenticate' '403': description: '`X-Organization-Slug` names an organization the key holder is no member of (`ORG_FORBIDDEN`, with the slugs the key holder may send under `data.organizations`)' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '404': description: Thread missing, owned by another user, or outside the visible URL scope; or `X-Organization-Slug` names no organization (`ORG_SLUG_INVALID`, with the slugs the key holder may send under `data.organizations`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '405': description: The path exists, but not for this method (`METHOD_NOT_ALLOWED`); `Allow` names the methods it serves content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Allow: $ref: '#/components/headers/Allow' '409': description: A turn is running on the thread, or an accepted send is still queued (`CHAT_TURN_IN_PROGRESS`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '429': description: 'The key holder’s request budget is spent (`RATE_LIMITED`): `Retry-After` names the wait in whole seconds and `data.retryAfterMs` in milliseconds; the envelope carries a `requestId`' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Retry-After: $ref: '#/components/headers/RetryAfter' '500': description: Internal error (`INTERNAL_ERROR`); the envelope carries a `requestId` to quote when reporting it content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '503': description: 'The platform’s database is restarting or cannot be reached (`DATABASE_UNAVAILABLE`): `Retry-After` names the wait in whole seconds — retry with backoff; the envelope carries a `requestId`' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Retry-After: $ref: '#/components/headers/RetryAfter' /api/v1/threads/{id}/messages: get: tags: - Threads summary: Read a thread’s messages description: Only the key holder’s threads without a project. Project threads are available through their project URL. Messages are in sequence order — oldest first, or newest first with `order=desc`, so the reply a turn just produced is on the first page of a long thread; use the previous continueCursor as cursor for the next page (a cursor is bound to the direction it was minted in). While a turn runs, its assistant row is already on the page with status `pending` and empty parts — the row GET /api/v1/threads/{id}/generation names as messageId — and is filled in when the turn settles; the page is complete without that row being final. One message is also readable by id at GET /api/v1/threads/{id}/messages/{messageId}. operationId: listMessages security: - bearerAuth: [] parameters: - name: X-Organization-Slug in: header required: false schema: type: string description: The organization to operate on. STRICTLY REQUIRED on every call, reads included, when the key holder belongs to more than one organization — without it such requests are refused with 400 `ORG_SLUG_REQUIRED` (the dashboard’s last-active organization never steers a machine call). A slug that names no organization answers 404 `ORG_SLUG_INVALID`, one the key holder is no member of 403 `ORG_FORBIDDEN`. Single-organization keys may omit it; a blank or whitespace-only value reads as the header absent. Each of the three refusals lists the slugs the key holder may send under `data.organizations`; `GET /api/v1/me` answers them too. - name: id in: path required: true schema: type: string description: Thread ID - name: cursor in: query required: false schema: type: string description: The previous page’s `continueCursor`, unchanged — opaque; omit for the first page. A value this list did not answer is refused with 400 `INVALID_CURSOR` rather than read as the first page - name: limit in: query required: false schema: type: integer minimum: 1 maximum: 100 default: 25 description: Page size, 1..100 (default 25; out-of-range numbers are clamped, a value that is not a number answers 400 `INVALID_LIMIT`) - name: order in: query required: false schema: type: string enum: - asc - desc default: asc description: 'The walk’s direction: `asc` (the default) pages oldest first from sequence 0, `desc` pages newest first — the settled reply of the latest turn leads the first page. A `cursor` minted walking one direction is refused with 400 `INVALID_CURSOR` on the other' - name: X-Request-Id in: header required: false description: 'Your own correlation id, echoed back on the response and written to the platform’s log: up to 255 characters of letters, digits, `_`, `-` and `=`. Any other value is replaced by a fresh UUID (the response carries the id that was used), and uniqueness of an id you choose is yours to keep.' schema: type: string pattern: ^[A-Za-z0-9_=-]{1,255}$ responses: '200': description: Paginated messages content: application/json: schema: type: object x-tale-pagination: keyset required: - page - isDone - continueCursor properties: page: type: array items: $ref: '#/components/schemas/Message' isDone: type: boolean continueCursor: type: string description: Pass back as `cursor` for the next page; empty when `isDone` headers: ETag: $ref: '#/components/headers/ETag' Cache-Control: $ref: '#/components/headers/CacheControl' X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '304': description: Not Modified — `If-None-Match` named the current `ETag`, so the body is not sent headers: ETag: $ref: '#/components/headers/ETag' Cache-Control: $ref: '#/components/headers/CacheControl' X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '400': description: 'Invalid request: a body the schema refuses — a wrong type, an unknown key, a missing field, a NUL or an unpaired surrogate, a whole number beyond 2^53 − 1 (`INVALID_BODY`, every problem under `data.issues`); a query parameter the operation does not take, given twice or left blank (`INVALID_QUERY`); on a list, a `limit` that is not a whole number (`INVALID_LIMIT`) or a `cursor` this list did not answer (`INVALID_CURSOR`)' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '401': description: Missing or invalid API key (`UNAUTHORIZED`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' WWW-Authenticate: $ref: '#/components/headers/WWWAuthenticate' '403': description: '`X-Organization-Slug` names an organization the key holder is no member of (`ORG_FORBIDDEN`, with the slugs the key holder may send under `data.organizations`)' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '404': description: Thread missing, owned by another user, or outside the visible URL scope; or `X-Organization-Slug` names no organization (`ORG_SLUG_INVALID`, with the slugs the key holder may send under `data.organizations`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '405': description: The path exists, but not for this method (`METHOD_NOT_ALLOWED`); `Allow` names the methods it serves content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Allow: $ref: '#/components/headers/Allow' '429': description: 'The key holder’s request budget is spent (`RATE_LIMITED`): `Retry-After` names the wait in whole seconds and `data.retryAfterMs` in milliseconds; the envelope carries a `requestId`' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Retry-After: $ref: '#/components/headers/RetryAfter' '500': description: Internal error (`INTERNAL_ERROR`); the envelope carries a `requestId` to quote when reporting it content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '503': description: 'The platform’s database is restarting or cannot be reached (`DATABASE_UNAVAILABLE`): `Retry-After` names the wait in whole seconds — retry with backoff; the envelope carries a `requestId`' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Retry-After: $ref: '#/components/headers/RetryAfter' post: tags: - Threads summary: Send a message and start a turn description: 'Only the key holder’s threads without a project. Project threads are available through their project URL. Answers 202 while the turn runs in the background; the 202 names the assistant message the reply lands in (`messageId`). Poll GET /api/v1/threads/{id}/generation until status is idle, then read the messages. Send `Idempotency-Key` to make the send safe to retry: a repeat within 24 hours answers what the first attempt answered — the same `messageId` — with `duplicate: true` and queues nothing, and a repeat with a different body answers 409 `IDEMPOTENCY_KEY_REUSED`; a refused send remembers nothing. Every turn runs the built-in workspace assistant: its instructions, safety rules and three retrieval tools ride every request (about 3,000 prompt tokens per model round, counted in `usage.inputTokens` — a turn that calls a tool runs up to five rounds, each billing its full prompt again), and a request for a deliverable is redirected to Tasks by design — this is a conversation with the workspace, not a bare model call. A budget cap that binds the key holder — their own, one of their teams’, the conversation’s project’s, the organization’s or this API key’s — refuses the send with 429 `BUDGET_EXCEEDED` before anything is queued; a cap reached while an accepted send waited settles its `messageId` as failed with errorCode `budget_exceeded`. A turn failure appears as an assistant error message. Charges the execute bucket on top of the general REST bucket.' operationId: postMessage security: - bearerAuth: [] parameters: - name: X-Organization-Slug in: header required: false schema: type: string description: The organization to operate on. STRICTLY REQUIRED on every call, reads included, when the key holder belongs to more than one organization — without it such requests are refused with 400 `ORG_SLUG_REQUIRED` (the dashboard’s last-active organization never steers a machine call). A slug that names no organization answers 404 `ORG_SLUG_INVALID`, one the key holder is no member of 403 `ORG_FORBIDDEN`. Single-organization keys may omit it; a blank or whitespace-only value reads as the header absent. Each of the three refusals lists the slugs the key holder may send under `data.organizations`; `GET /api/v1/me` answers them too. - name: id in: path required: true schema: type: string description: Thread ID - name: Idempotency-Key in: header required: false schema: type: string pattern: ^[ -~]*[!-~][ -~]*$ maxLength: 255 description: 'A stable key of your choosing that names this send — printable ASCII (a UUID, a job id), at most 255 characters: any other character answers 400 `INVALID_HEADER`, naming the header under `data.issues`, and no send happens — keys are compared byte for byte once surrounding whitespace is removed, so the pattern is what keeps two spellings of one key from naming two sends; a header value carrying a control character never reaches the platform, the edge refuses the request before any envelope. A repeat with the same key, thread and scope within 24 hours answers 202 with what the first attempt answered — the same `messageId` to poll for, nothing new queued and `duplicate: true`; a repeat with a different body answers 409 `IDEMPOTENCY_KEY_REUSED`. A refused send is not remembered, so the same key runs once the refusal is fixed. A header sent but left blank (spaces only), or one over 255 characters, answers 400 `INVALID_HEADER` and starts nothing — omit the header to proceed without a key; it is never silently read as "no key".' - name: X-Request-Id in: header required: false description: 'Your own correlation id, echoed back on the response and written to the platform’s log: up to 255 characters of letters, digits, `_`, `-` and `=`. Any other value is replaced by a fresh UUID (the response carries the id that was used), and uniqueness of an id you choose is yours to keep.' schema: type: string pattern: ^[A-Za-z0-9_=-]{1,255}$ requestBody: required: true content: application/json: schema: type: object additionalProperties: false required: - content - model properties: content: type: string minLength: 1 maxLength: 100000 description: 'The prompt, trimmed before the length check — a blank prompt (nothing but whitespace and invisible format characters: zero-width spaces, joiners, bidi marks) is 400 `INVALID_BODY`, not a turn; markdown that renders as nothing, an empty code fence say, is still a prompt. The 100,000 cap counts UTF-16 code units, so an astral character — an emoji — costs two, and a body of 50,001 of them is refused. Text only: this surface takes no image or file input, on a `vision` model too — a data URI or base64 pasted here reaches the model as text and is answered as text; image attachments are the app’s composer.' model: type: string minLength: 1 maxLength: 200 description: 'Model ID from GET /api/v1/models; never auto-selected. Checked at the door: without `providerSlug`, an id the list does not carry answers 400 `CHAT_MODEL_UNKNOWN` (and an id listed under several providers `CHAT_MODEL_AMBIGUOUS`); with a `providerSlug` the pair is judged instead — an unknown provider answers `CHAT_PROVIDER_UNKNOWN`, a listed provider that does not serve the id `CHAT_MODEL_NOT_ON_PROVIDER`.' providerSlug: type: string minLength: 1 maxLength: 200 description: 'Provider slug returned with the model. Checked against GET /api/v1/models: a slug the list does not carry answers 400 `CHAT_PROVIDER_UNKNOWN`, one that does not serve the model 400 `CHAT_MODEL_NOT_ON_PROVIDER` — never a silent fallback to another provider. Optional when exactly one listed provider serves the model; a model listed under several providers answers 400 `CHAT_MODEL_AMBIGUOUS` with `data.providers` until one is named. The 202 names the provider the turn runs on either way.' reasoningEffort: type: string enum: - low - medium - high - extra - max description: The reasoning depth, on the same five-step scale the app offers; omitted samples the model’s default. Ignored by a model whose `capabilities.reasoning` is false. A higher step spends more of the cap and of the bill — reasoning tokens count inside `maxOutputTokens` and inside `usage.outputTokens` at the output price — and at `high` or above a slower model can think for a minute or more before any answer; a turn has no fixed deadline, so bound your poll loop. maxOutputTokens: type: integer minimum: 1 description: 'The largest reply this turn may produce across every model round, in tokens — a cap under the model’s own `maxOutputTokens` from GET /api/v1/models; a value above it answers 400 `INVALID_BODY`. Omitted, the model’s own ceiling applies. A tool-calling turn spends the cap round by round: a later round gets what the earlier ones left, and a round that would start with nothing left is not run. A reasoning model spends its reasoning inside the cap too, and the catalog does not say how much: a cap of a few thousand tokens can be consumed by the reasoning alone and settle an EMPTY reply — `complete`, `finishReason: "length"`, no text part, billed, the reasoning tokens counted inside `usage.outputTokens` at the output price — so give a reasoning model several thousand tokens or lower `reasoningEffort`. The one floor is the thinking-budget dialect (Anthropic-style extended thinking): there a cap under 2,048 is raised to 2,048; a model whose reasoning is set by an effort level (the GLM and DeepSeek families) has no floor and the cap bites exactly. A reply that runs into the cap still settles `complete`, cut short, and says so with `finishReason: "length"` on the message — a cut that fell on an earlier round’s tool calls included: every call of that round is withheld — it does not run and its tool-result reads `status: "invalid_args"`, the message saying whether its arguments were cut or complete.' locale: type: string minLength: 2 maxLength: 20 pattern: ^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$ description: A BCP 47 language tag (`de`, `en-GB`) — the language the assistant is asked to answer in, whatever language the prompt is written in. The instruction rides the prompt as a directive, on the system prompt and on the message itself; a model may still slip (most often a reasoning model on a short prompt), and nothing on the wire marks a slip — a client that must have the language checks the reply and resends. An organization’s mandatory instructions win over `locale`. Omitted, the assistant answers in the prompt’s language. responses: '202': description: Turn accepted, or the send an earlier attempt under the same `Idempotency-Key` accepted content: application/json: schema: type: object required: - threadId - status - model - providerSlug - messageId - poll properties: duplicate: type: boolean enum: - true description: 'Present when the `Idempotency-Key` had already accepted this send: nothing new was queued — poll for the `messageId` it names' threadId: type: string status: type: string enum: - accepted model: type: string providerSlug: type: string description: The provider the turn runs on — named in the request, or resolved from the model list messageId: type: string description: 'The assistant message the reply lands in — the row GET …/messages carries as `pending` until the turn settles. Keep it: a caller that loses this response finds its reply by this id.' poll: type: string description: Poll URL under /api/v1/threads/{id}/generation headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '400': description: Invalid body (`INVALID_BODY`), a model the list does not carry (`CHAT_MODEL_UNKNOWN`), a model listed under several providers with none named (`CHAT_MODEL_AMBIGUOUS`), an unknown providerSlug (`CHAT_PROVIDER_UNKNOWN`), or a provider that does not serve the model (`CHAT_MODEL_NOT_ON_PROVIDER`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '401': description: Missing or invalid API key (`UNAUTHORIZED`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' WWW-Authenticate: $ref: '#/components/headers/WWWAuthenticate' '403': description: '`X-Organization-Slug` names an organization the key holder is no member of (`ORG_FORBIDDEN`, with the slugs the key holder may send under `data.organizations`)' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '404': description: Thread missing, owned by another user, or outside the visible URL scope; or `X-Organization-Slug` names no organization (`ORG_SLUG_INVALID`, with the slugs the key holder may send under `data.organizations`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '405': description: The path exists, but not for this method (`METHOD_NOT_ALLOWED`); `Allow` names the methods it serves content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Allow: $ref: '#/components/headers/Allow' '409': description: The thread is archived (`CHAT_THREAD_ARCHIVED`) or not a direct chat (`CHAT_THREAD_NOT_DIRECT`), a turn is already running or still queued (`CHAT_TURN_IN_PROGRESS`) — nothing is queued and the running turn keeps its `messageId` — or the `Idempotency-Key` was already used for a different request (`IDEMPOTENCY_KEY_REUSED`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '413': description: The body exceeds this operation’s byte cap — 1 MiB unless the description says otherwise (`BODY_TOO_LARGE`); the envelope carries a `requestId` content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '429': description: 'The key holder’s request budget is spent (`RATE_LIMITED`): `Retry-After` names the wait in whole seconds and `data.retryAfterMs` in milliseconds; the envelope carries a `requestId`; or a budget cap that binds the key holder is reached (`BUDGET_EXCEEDED`): nothing is queued, `data` names the cap — `scope`, `period`, `limitCode`, `used`, `limit`, `resetsAt` — and `Retry-After` the wait in whole seconds until its period resets' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Retry-After: $ref: '#/components/headers/RetryAfter' '500': description: Internal error (`INTERNAL_ERROR`); the envelope carries a `requestId` to quote when reporting it content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '503': description: 'The platform’s database is restarting or cannot be reached (`DATABASE_UNAVAILABLE`): `Retry-After` names the wait in whole seconds — retry with backoff; the envelope carries a `requestId`' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Retry-After: $ref: '#/components/headers/RetryAfter' /api/v1/threads/{id}/messages/{messageId}: get: tags: - Threads summary: Read one message description: 'Only the key holder’s threads without a project. Project threads are available through their project URL. One message of the thread by id — the reply the 202 named (`messageId`), read without paging the transcript. The same shape as a row of GET /api/v1/threads/{id}/messages: `pending` while its turn still runs, settled with `finishReason` and `usage` once the poll says idle. A message id the thread does not carry answers 404 `MESSAGE_NOT_FOUND`.' operationId: getMessage security: - bearerAuth: [] parameters: - name: X-Organization-Slug in: header required: false schema: type: string description: The organization to operate on. STRICTLY REQUIRED on every call, reads included, when the key holder belongs to more than one organization — without it such requests are refused with 400 `ORG_SLUG_REQUIRED` (the dashboard’s last-active organization never steers a machine call). A slug that names no organization answers 404 `ORG_SLUG_INVALID`, one the key holder is no member of 403 `ORG_FORBIDDEN`. Single-organization keys may omit it; a blank or whitespace-only value reads as the header absent. Each of the three refusals lists the slugs the key holder may send under `data.organizations`; `GET /api/v1/me` answers them too. - name: id in: path required: true schema: type: string description: Thread ID - name: messageId in: path required: true schema: type: string description: Message ID - name: X-Request-Id in: header required: false description: 'Your own correlation id, echoed back on the response and written to the platform’s log: up to 255 characters of letters, digits, `_`, `-` and `=`. Any other value is replaced by a fresh UUID (the response carries the id that was used), and uniqueness of an id you choose is yours to keep.' schema: type: string pattern: ^[A-Za-z0-9_=-]{1,255}$ responses: '200': description: The message content: application/json: schema: $ref: '#/components/schemas/Message' headers: ETag: $ref: '#/components/headers/ETag' Cache-Control: $ref: '#/components/headers/CacheControl' X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '304': description: Not Modified — `If-None-Match` named the current `ETag`, so the body is not sent headers: ETag: $ref: '#/components/headers/ETag' Cache-Control: $ref: '#/components/headers/CacheControl' X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '400': description: 'Invalid request: a body the schema refuses — a wrong type, an unknown key, a missing field, a NUL or an unpaired surrogate, a whole number beyond 2^53 − 1 (`INVALID_BODY`, every problem under `data.issues`); a query parameter the operation does not take, given twice or left blank (`INVALID_QUERY`); on a list, a `limit` that is not a whole number (`INVALID_LIMIT`) or a `cursor` this list did not answer (`INVALID_CURSOR`)' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '401': description: Missing or invalid API key (`UNAUTHORIZED`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' WWW-Authenticate: $ref: '#/components/headers/WWWAuthenticate' '403': description: '`X-Organization-Slug` names an organization the key holder is no member of (`ORG_FORBIDDEN`, with the slugs the key holder may send under `data.organizations`)' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '404': description: Thread missing, owned by another user, or outside the visible URL scope (`THREAD_NOT_FOUND`); or no such message on the thread (`MESSAGE_NOT_FOUND`); or `X-Organization-Slug` names no organization (`ORG_SLUG_INVALID`, with the slugs the key holder may send under `data.organizations`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '405': description: The path exists, but not for this method (`METHOD_NOT_ALLOWED`); `Allow` names the methods it serves content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Allow: $ref: '#/components/headers/Allow' '429': description: 'The key holder’s request budget is spent (`RATE_LIMITED`): `Retry-After` names the wait in whole seconds and `data.retryAfterMs` in milliseconds; the envelope carries a `requestId`' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Retry-After: $ref: '#/components/headers/RetryAfter' '500': description: Internal error (`INTERNAL_ERROR`); the envelope carries a `requestId` to quote when reporting it content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '503': description: 'The platform’s database is restarting or cannot be reached (`DATABASE_UNAVAILABLE`): `Retry-After` names the wait in whole seconds — retry with backoff; the envelope carries a `requestId`' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Retry-After: $ref: '#/components/headers/RetryAfter' /api/v1/threads/{id}/generation: get: tags: - Threads summary: Poll the running turn description: 'Only the key holder’s threads without a project. Project threads are available through their project URL. `queued`: the accepted send is waiting for a worker (the 202 was answered, the turn has not opened) — accepted sends form one oldest-first queue shared by the whole deployment, worked in batches of up to five turns with the next batch starting only once the running one has settled, so a burst of sends completes in waves, not in parallel. `streaming`: the model is streaming its reply to the server — `text` and `reasoning` carry what has arrived so far, so a poller sees progress; send `since` (the UTF-16 code units of `text` you already hold — JavaScript `String.length`, an emoji counting two; not code points) and `reasoningSince` (the same for `reasoning`) to receive only what arrived after them, with `textOffset`/`textLength` and `reasoningOffset`/`reasoningLength` beside them — reassemble by one rule, `held = held.slice(0, textOffset) + text`, and send `textLength` back as `since`; there is no push channel on this surface. A turn has no fixed deadline — the server abandons one only after 180 seconds of provider silence — so bound your own loop and stop a turn you no longer want with DELETE /api/v1/threads/{id}/generation. `idle`: no turn is running — `lastMessageId` and `lastStatus` name the newest assistant message and how it settled. The recipe after a send (or a lost 202): poll until `idle`, then compare `lastMessageId` with the `messageId` the 202 named — equal means your turn settled (read that message at GET /api/v1/threads/{id}/messages/{messageId}, or page the messages with `order=desc`), a different id means yours has not started yet.' operationId: getGeneration security: - bearerAuth: [] parameters: - name: X-Organization-Slug in: header required: false schema: type: string description: The organization to operate on. STRICTLY REQUIRED on every call, reads included, when the key holder belongs to more than one organization — without it such requests are refused with 400 `ORG_SLUG_REQUIRED` (the dashboard’s last-active organization never steers a machine call). A slug that names no organization answers 404 `ORG_SLUG_INVALID`, one the key holder is no member of 403 `ORG_FORBIDDEN`. Single-organization keys may omit it; a blank or whitespace-only value reads as the header absent. Each of the three refusals lists the slugs the key holder may send under `data.organizations`; `GET /api/v1/me` answers them too. - name: id in: path required: true schema: type: string description: Thread ID - name: since in: query required: false schema: type: integer minimum: 0 description: 'The number of UTF-16 code units (JavaScript `String.length` — an emoji counts two; not code points, not grapheme clusters) of `text` you already hold from an earlier poll: the answer carries only what arrived after them, `textOffset` says where the slice starts — the value you sent, one lower when that would have split a surrogate pair (an astral character is two units, and a slice opening on its second half would put an unpaired surrogate on the wire), or 0 when a tool round reset the reply — and `textLength` how long the whole reply is so far. Reassemble by one rule, `held = held.slice(0, textOffset) + text`, and send `textLength` back as `since`: it never falls inside a pair. Ignored unless the turn is streaming' - name: reasoningSince in: query required: false schema: type: integer minimum: 0 description: 'The number of UTF-16 code units (JavaScript `String.length`) of `reasoning` you already hold from an earlier poll — the `since` of the reasoning stream: the answer carries only what arrived after them, `reasoningOffset` says where the slice starts — the value you sent, one lower when that would have split a surrogate pair, or 0 when a tool round reset the reasoning with the text — and `reasoningLength` how long the whole reasoning is so far. Reassemble as `held = held.slice(0, reasoningOffset) + reasoning` and send `reasoningLength` back as `reasoningSince`. Ignored unless the turn is streaming' - name: X-Request-Id in: header required: false description: 'Your own correlation id, echoed back on the response and written to the platform’s log: up to 255 characters of letters, digits, `_`, `-` and `=`. Any other value is replaced by a fresh UUID (the response carries the id that was used), and uniqueness of an id you choose is yours to keep.' schema: type: string pattern: ^[A-Za-z0-9_=-]{1,255}$ responses: '200': description: Idle when no turn is running; otherwise the live status content: application/json: schema: type: object required: - status properties: status: type: string enum: - idle - queued - streaming messageId: type: string description: The assistant message the turn streams into (present while queued or streaming) text: type: string description: The reply text streamed so far — from `textOffset` on when `since` was sent, always whole characters (present while streaming) textOffset: type: integer minimum: 0 description: 'Where `text` starts, in UTF-16 code units: the `since` you sent, one lower when that would have split a surrogate pair, or 0 when the reply was reset since — reassemble as `held = held.slice(0, textOffset) + text` (present while streaming)' textLength: type: integer minimum: 0 description: The length of the whole reply streamed so far, in UTF-16 code units (JavaScript `String.length`) — send it back as `since` on the next poll; it never falls inside a surrogate pair (present while streaming) lastMessageId: type: string description: The newest assistant message on the thread (present while idle, once the thread has one) — compare with the `messageId` a 202 named to know whether that turn settled lastStatus: type: string enum: - pending - complete - failed - cancelled description: How `lastMessageId` settled (present with it) reasoning: type: string description: The model’s reasoning streamed so far — from `reasoningOffset` on when `reasoningSince` was sent, always whole characters; display-only, may be empty (present while streaming) reasoningOffset: type: integer minimum: 0 description: 'Where `reasoning` starts, in UTF-16 code units: the `reasoningSince` you sent, one lower when that would have split a surrogate pair, or 0 when the reasoning was reset since (present while streaming)' reasoningLength: type: integer minimum: 0 description: The length of the whole reasoning streamed so far, in UTF-16 code units — send it back as `reasoningSince` on the next poll; it never falls inside a surrogate pair (present while streaming) cancelRequested: type: boolean description: A stop was asked for and the turn has not settled yet (present while streaming) updatedAt: type: integer minimum: 0 description: Epoch ms of the last progress write (present while streaming) headers: ETag: $ref: '#/components/headers/ETag' Cache-Control: $ref: '#/components/headers/CacheControl' X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '304': description: Not Modified — `If-None-Match` named the current `ETag`, so the body is not sent headers: ETag: $ref: '#/components/headers/ETag' Cache-Control: $ref: '#/components/headers/CacheControl' X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '400': description: 'Invalid request: a body the schema refuses — a wrong type, an unknown key, a missing field, a NUL or an unpaired surrogate, a whole number beyond 2^53 − 1 (`INVALID_BODY`, every problem under `data.issues`); a query parameter the operation does not take, given twice or left blank (`INVALID_QUERY`); on a list, a `limit` that is not a whole number (`INVALID_LIMIT`) or a `cursor` this list did not answer (`INVALID_CURSOR`)' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '401': description: Missing or invalid API key (`UNAUTHORIZED`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' WWW-Authenticate: $ref: '#/components/headers/WWWAuthenticate' '403': description: '`X-Organization-Slug` names an organization the key holder is no member of (`ORG_FORBIDDEN`, with the slugs the key holder may send under `data.organizations`)' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '404': description: Thread missing, owned by another user, or outside the visible URL scope; or `X-Organization-Slug` names no organization (`ORG_SLUG_INVALID`, with the slugs the key holder may send under `data.organizations`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '405': description: The path exists, but not for this method (`METHOD_NOT_ALLOWED`); `Allow` names the methods it serves content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Allow: $ref: '#/components/headers/Allow' '429': description: 'The key holder’s request budget is spent (`RATE_LIMITED`): `Retry-After` names the wait in whole seconds and `data.retryAfterMs` in milliseconds; the envelope carries a `requestId`' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Retry-After: $ref: '#/components/headers/RetryAfter' '500': description: Internal error (`INTERNAL_ERROR`); the envelope carries a `requestId` to quote when reporting it content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '503': description: 'The platform’s database is restarting or cannot be reached (`DATABASE_UNAVAILABLE`): `Retry-After` names the wait in whole seconds — retry with backoff; the envelope carries a `requestId`' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Retry-After: $ref: '#/components/headers/RetryAfter' delete: tags: - Threads summary: Cancel the running turn description: 'Only the key holder’s threads without a project. Project threads are available through their project URL. Asks the running turn to stop — the app’s own stop: the turn reads the flag at its next progress write and settles what it has. 202 means the stop is requested, not done; poll until idle. A send still queued (no worker has opened it) is stopped the same way: 202 naming the reply the send’s 202 promised, the model is never called, and that reply settles as `cancelled` with empty `parts`. Nothing running and nothing queued answers 404 `CHAT_TURN_NOT_RUNNING` with `data.lastMessageId` and `data.lastStatus` — the idle poll’s answer, so a stop that lost the race against a fast model needs no second call.' operationId: cancelGeneration security: - bearerAuth: [] parameters: - name: X-Organization-Slug in: header required: false schema: type: string description: The organization to operate on. STRICTLY REQUIRED on every call, reads included, when the key holder belongs to more than one organization — without it such requests are refused with 400 `ORG_SLUG_REQUIRED` (the dashboard’s last-active organization never steers a machine call). A slug that names no organization answers 404 `ORG_SLUG_INVALID`, one the key holder is no member of 403 `ORG_FORBIDDEN`. Single-organization keys may omit it; a blank or whitespace-only value reads as the header absent. Each of the three refusals lists the slugs the key holder may send under `data.organizations`; `GET /api/v1/me` answers them too. - name: id in: path required: true schema: type: string description: Thread ID - name: X-Request-Id in: header required: false description: 'Your own correlation id, echoed back on the response and written to the platform’s log: up to 255 characters of letters, digits, `_`, `-` and `=`. Any other value is replaced by a fresh UUID (the response carries the id that was used), and uniqueness of an id you choose is yours to keep.' schema: type: string pattern: ^[A-Za-z0-9_=-]{1,255}$ responses: '202': description: Cancellation requested — of the running turn, or of the queued send content: application/json: schema: type: object required: - status properties: status: type: string enum: - cancelling messageId: type: string description: The assistant message the turn was writing, or the one the queued send was to write headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '400': description: 'Invalid request: a body the schema refuses — a wrong type, an unknown key, a missing field, a NUL or an unpaired surrogate, a whole number beyond 2^53 − 1 (`INVALID_BODY`, every problem under `data.issues`); a query parameter the operation does not take, given twice or left blank (`INVALID_QUERY`); on a list, a `limit` that is not a whole number (`INVALID_LIMIT`) or a `cursor` this list did not answer (`INVALID_CURSOR`)' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '401': description: Missing or invalid API key (`UNAUTHORIZED`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' WWW-Authenticate: $ref: '#/components/headers/WWWAuthenticate' '403': description: '`X-Organization-Slug` names an organization the key holder is no member of (`ORG_FORBIDDEN`, with the slugs the key holder may send under `data.organizations`)' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '404': description: Thread missing, owned by another user, or outside the visible URL scope (`THREAD_NOT_FOUND`); or no turn is running and no send is queued (`CHAT_TURN_NOT_RUNNING` — `data.lastMessageId` and `data.lastStatus` name the newest assistant message, as the idle poll does); or `X-Organization-Slug` names no organization (`ORG_SLUG_INVALID`, with the slugs the key holder may send under `data.organizations`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '405': description: The path exists, but not for this method (`METHOD_NOT_ALLOWED`); `Allow` names the methods it serves content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Allow: $ref: '#/components/headers/Allow' '429': description: 'The key holder’s request budget is spent (`RATE_LIMITED`): `Retry-After` names the wait in whole seconds and `data.retryAfterMs` in milliseconds; the envelope carries a `requestId`' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Retry-After: $ref: '#/components/headers/RetryAfter' '500': description: Internal error (`INTERNAL_ERROR`); the envelope carries a `requestId` to quote when reporting it content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '503': description: 'The platform’s database is restarting or cannot be reached (`DATABASE_UNAVAILABLE`): `Retry-After` names the wait in whole seconds — retry with backoff; the envelope carries a `requestId`' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Retry-After: $ref: '#/components/headers/RetryAfter' /api/v1/projects/{id}/threads: get: tags: - Threads summary: List your threads in a project description: Only the key holder’s threads belonging to the URL project; requires project read access. Includes archived threads, newest activity first. operationId: listProjectThreads security: - bearerAuth: [] parameters: - name: X-Organization-Slug in: header required: false schema: type: string description: The organization to operate on. STRICTLY REQUIRED on every call, reads included, when the key holder belongs to more than one organization — without it such requests are refused with 400 `ORG_SLUG_REQUIRED` (the dashboard’s last-active organization never steers a machine call). A slug that names no organization answers 404 `ORG_SLUG_INVALID`, one the key holder is no member of 403 `ORG_FORBIDDEN`. Single-organization keys may omit it; a blank or whitespace-only value reads as the header absent. Each of the three refusals lists the slugs the key holder may send under `data.organizations`; `GET /api/v1/me` answers them too. - name: id in: path required: true schema: type: string description: Project ID - name: cursor in: query required: false schema: type: string description: The previous page’s `continueCursor`, unchanged — opaque; omit for the first page. A value this list did not answer is refused with 400 `INVALID_CURSOR` rather than read as the first page - name: limit in: query required: false schema: type: integer minimum: 1 maximum: 100 default: 25 description: Page size, 1..100 (default 25; out-of-range numbers are clamped, a value that is not a number answers 400 `INVALID_LIMIT`) - name: X-Request-Id in: header required: false description: 'Your own correlation id, echoed back on the response and written to the platform’s log: up to 255 characters of letters, digits, `_`, `-` and `=`. Any other value is replaced by a fresh UUID (the response carries the id that was used), and uniqueness of an id you choose is yours to keep.' schema: type: string pattern: ^[A-Za-z0-9_=-]{1,255}$ responses: '200': description: Paginated threads content: application/json: schema: type: object x-tale-pagination: keyset required: - page - isDone - continueCursor properties: page: type: array items: $ref: '#/components/schemas/Thread' isDone: type: boolean continueCursor: type: string description: Pass back as `cursor` for the next page; empty when `isDone` headers: ETag: $ref: '#/components/headers/ETag' Cache-Control: $ref: '#/components/headers/CacheControl' X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '304': description: Not Modified — `If-None-Match` named the current `ETag`, so the body is not sent headers: ETag: $ref: '#/components/headers/ETag' Cache-Control: $ref: '#/components/headers/CacheControl' X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '400': description: 'Invalid request: a body the schema refuses — a wrong type, an unknown key, a missing field, a NUL or an unpaired surrogate, a whole number beyond 2^53 − 1 (`INVALID_BODY`, every problem under `data.issues`); a query parameter the operation does not take, given twice or left blank (`INVALID_QUERY`); on a list, a `limit` that is not a whole number (`INVALID_LIMIT`) or a `cursor` this list did not answer (`INVALID_CURSOR`)' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '401': description: Missing or invalid API key (`UNAUTHORIZED`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' WWW-Authenticate: $ref: '#/components/headers/WWWAuthenticate' '403': description: '`X-Organization-Slug` names an organization the key holder is no member of (`ORG_FORBIDDEN`, with the slugs the key holder may send under `data.organizations`)' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '404': description: Thread missing, owned by another user, or outside the visible URL scope; or `X-Organization-Slug` names no organization (`ORG_SLUG_INVALID`, with the slugs the key holder may send under `data.organizations`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '405': description: The path exists, but not for this method (`METHOD_NOT_ALLOWED`); `Allow` names the methods it serves content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Allow: $ref: '#/components/headers/Allow' '429': description: 'The key holder’s request budget is spent (`RATE_LIMITED`): `Retry-After` names the wait in whole seconds and `data.retryAfterMs` in milliseconds; the envelope carries a `requestId`' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Retry-After: $ref: '#/components/headers/RetryAfter' '500': description: Internal error (`INTERNAL_ERROR`); the envelope carries a `requestId` to quote when reporting it content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '503': description: 'The platform’s database is restarting or cannot be reached (`DATABASE_UNAVAILABLE`): `Retry-After` names the wait in whole seconds — retry with backoff; the envelope carries a `requestId`' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Retry-After: $ref: '#/components/headers/RetryAfter' post: tags: - Threads summary: Create a chat in a project description: A direct chat with the built-in assistant. The URL supplies its project context. Any member with project read access may create a chat while the project is active. This operation accepts only an optional title (trimmed, 1–120 characters — the bound `PATCH` renames within); agent selectors and projectId are refused. Project agents execute through project tasks. A thread created without a title is named by the assistant after its first message (read `title` back from the thread); `PATCH` renames it. operationId: createProjectThread security: - bearerAuth: [] parameters: - name: X-Organization-Slug in: header required: false schema: type: string description: The organization to operate on. STRICTLY REQUIRED on every call, reads included, when the key holder belongs to more than one organization — without it such requests are refused with 400 `ORG_SLUG_REQUIRED` (the dashboard’s last-active organization never steers a machine call). A slug that names no organization answers 404 `ORG_SLUG_INVALID`, one the key holder is no member of 403 `ORG_FORBIDDEN`. Single-organization keys may omit it; a blank or whitespace-only value reads as the header absent. Each of the three refusals lists the slugs the key holder may send under `data.organizations`; `GET /api/v1/me` answers them too. - name: id in: path required: true schema: type: string description: Project ID - name: X-Request-Id in: header required: false description: 'Your own correlation id, echoed back on the response and written to the platform’s log: up to 255 characters of letters, digits, `_`, `-` and `=`. Any other value is replaced by a fresh UUID (the response carries the id that was used), and uniqueness of an id you choose is yours to keep.' schema: type: string pattern: ^[A-Za-z0-9_=-]{1,255}$ requestBody: required: false content: application/json: schema: type: object additionalProperties: false properties: title: type: string minLength: 1 maxLength: 120 description: The thread’s name, trimmed (1–120 characters) — the same bound PATCH renames within responses: '201': description: Created thread ID content: application/json: schema: type: object required: - id properties: id: type: string headers: Location: $ref: '#/components/headers/Location' X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '400': description: 'Invalid request: a body the schema refuses — a wrong type, an unknown key, a missing field, a NUL or an unpaired surrogate, a whole number beyond 2^53 − 1 (`INVALID_BODY`, every problem under `data.issues`); a query parameter the operation does not take, given twice or left blank (`INVALID_QUERY`); on a list, a `limit` that is not a whole number (`INVALID_LIMIT`) or a `cursor` this list did not answer (`INVALID_CURSOR`)' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '401': description: Missing or invalid API key (`UNAUTHORIZED`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' WWW-Authenticate: $ref: '#/components/headers/WWWAuthenticate' '403': description: The project is archived; or `X-Organization-Slug` names an organization the key holder is no member of (`ORG_FORBIDDEN`, with the slugs the key holder may send under `data.organizations`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '404': description: Thread missing, owned by another user, or outside the visible URL scope; or `X-Organization-Slug` names no organization (`ORG_SLUG_INVALID`, with the slugs the key holder may send under `data.organizations`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '405': description: The path exists, but not for this method (`METHOD_NOT_ALLOWED`); `Allow` names the methods it serves content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Allow: $ref: '#/components/headers/Allow' '413': description: The body exceeds this operation’s byte cap — 1 MiB unless the description says otherwise (`BODY_TOO_LARGE`); the envelope carries a `requestId` content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '429': description: 'The key holder’s request budget is spent (`RATE_LIMITED`): `Retry-After` names the wait in whole seconds and `data.retryAfterMs` in milliseconds; the envelope carries a `requestId`' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Retry-After: $ref: '#/components/headers/RetryAfter' '500': description: Internal error (`INTERNAL_ERROR`); the envelope carries a `requestId` to quote when reporting it content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '503': description: 'The platform’s database is restarting or cannot be reached (`DATABASE_UNAVAILABLE`): `Retry-After` names the wait in whole seconds — retry with backoff; the envelope carries a `requestId`' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Retry-After: $ref: '#/components/headers/RetryAfter' /api/v1/projects/{id}/threads/{threadId}: get: tags: - Threads summary: Read a thread description: Only the key holder’s threads belonging to the URL project; requires project read access. operationId: getProjectThread security: - bearerAuth: [] parameters: - name: X-Organization-Slug in: header required: false schema: type: string description: The organization to operate on. STRICTLY REQUIRED on every call, reads included, when the key holder belongs to more than one organization — without it such requests are refused with 400 `ORG_SLUG_REQUIRED` (the dashboard’s last-active organization never steers a machine call). A slug that names no organization answers 404 `ORG_SLUG_INVALID`, one the key holder is no member of 403 `ORG_FORBIDDEN`. Single-organization keys may omit it; a blank or whitespace-only value reads as the header absent. Each of the three refusals lists the slugs the key holder may send under `data.organizations`; `GET /api/v1/me` answers them too. - name: id in: path required: true schema: type: string description: Project ID - name: threadId in: path required: true schema: type: string description: Thread ID - name: X-Request-Id in: header required: false description: 'Your own correlation id, echoed back on the response and written to the platform’s log: up to 255 characters of letters, digits, `_`, `-` and `=`. Any other value is replaced by a fresh UUID (the response carries the id that was used), and uniqueness of an id you choose is yours to keep.' schema: type: string pattern: ^[A-Za-z0-9_=-]{1,255}$ responses: '200': description: The thread content: application/json: schema: $ref: '#/components/schemas/Thread' headers: ETag: $ref: '#/components/headers/ETag' Cache-Control: $ref: '#/components/headers/CacheControl' X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '304': description: Not Modified — `If-None-Match` named the current `ETag`, so the body is not sent headers: ETag: $ref: '#/components/headers/ETag' Cache-Control: $ref: '#/components/headers/CacheControl' X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '400': description: 'Invalid request: a body the schema refuses — a wrong type, an unknown key, a missing field, a NUL or an unpaired surrogate, a whole number beyond 2^53 − 1 (`INVALID_BODY`, every problem under `data.issues`); a query parameter the operation does not take, given twice or left blank (`INVALID_QUERY`); on a list, a `limit` that is not a whole number (`INVALID_LIMIT`) or a `cursor` this list did not answer (`INVALID_CURSOR`)' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '401': description: Missing or invalid API key (`UNAUTHORIZED`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' WWW-Authenticate: $ref: '#/components/headers/WWWAuthenticate' '403': description: '`X-Organization-Slug` names an organization the key holder is no member of (`ORG_FORBIDDEN`, with the slugs the key holder may send under `data.organizations`)' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '404': description: Thread missing, owned by another user, or outside the visible URL scope; or `X-Organization-Slug` names no organization (`ORG_SLUG_INVALID`, with the slugs the key holder may send under `data.organizations`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '405': description: The path exists, but not for this method (`METHOD_NOT_ALLOWED`); `Allow` names the methods it serves content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Allow: $ref: '#/components/headers/Allow' '429': description: 'The key holder’s request budget is spent (`RATE_LIMITED`): `Retry-After` names the wait in whole seconds and `data.retryAfterMs` in milliseconds; the envelope carries a `requestId`' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Retry-After: $ref: '#/components/headers/RetryAfter' '500': description: Internal error (`INTERNAL_ERROR`); the envelope carries a `requestId` to quote when reporting it content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '503': description: 'The platform’s database is restarting or cannot be reached (`DATABASE_UNAVAILABLE`): `Retry-After` names the wait in whole seconds — retry with backoff; the envelope carries a `requestId`' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Retry-After: $ref: '#/components/headers/RetryAfter' patch: tags: - Threads summary: Archive, restore or rename a thread description: 'Only the key holder’s threads belonging to the URL project; requires project read access. Archiving is the app’s own toggle, audited the same way: an archived thread stays readable and refuses messages with 409 `CHAT_THREAD_ARCHIVED`; `archived: false` restores it. A thread whose turn is still running — or whose accepted send is still queued — refuses the archive with 409 `CHAT_TURN_IN_PROGRESS`, as the delete does: cancel the turn first (restoring and renaming stay open mid-turn). Archiving stamps `archivedAt` and leaves `updatedAt` — the last message activity — untouched. `title` renames the thread (trimmed, 1–120 characters). Send at least one of the two.' operationId: updateProjectThread security: - bearerAuth: [] parameters: - name: X-Organization-Slug in: header required: false schema: type: string description: The organization to operate on. STRICTLY REQUIRED on every call, reads included, when the key holder belongs to more than one organization — without it such requests are refused with 400 `ORG_SLUG_REQUIRED` (the dashboard’s last-active organization never steers a machine call). A slug that names no organization answers 404 `ORG_SLUG_INVALID`, one the key holder is no member of 403 `ORG_FORBIDDEN`. Single-organization keys may omit it; a blank or whitespace-only value reads as the header absent. Each of the three refusals lists the slugs the key holder may send under `data.organizations`; `GET /api/v1/me` answers them too. - name: id in: path required: true schema: type: string description: Project ID - name: threadId in: path required: true schema: type: string description: Thread ID - name: X-Request-Id in: header required: false description: 'Your own correlation id, echoed back on the response and written to the platform’s log: up to 255 characters of letters, digits, `_`, `-` and `=`. Any other value is replaced by a fresh UUID (the response carries the id that was used), and uniqueness of an id you choose is yours to keep.' schema: type: string pattern: ^[A-Za-z0-9_=-]{1,255}$ requestBody: required: true content: application/json: schema: type: object additionalProperties: false minProperties: 1 properties: archived: type: boolean title: type: string minLength: 1 maxLength: 120 description: The new name, trimmed responses: '200': description: The thread after the change content: application/json: schema: $ref: '#/components/schemas/Thread' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '400': description: 'Invalid request: a body the schema refuses — a wrong type, an unknown key, a missing field, a NUL or an unpaired surrogate, a whole number beyond 2^53 − 1 (`INVALID_BODY`, every problem under `data.issues`); a query parameter the operation does not take, given twice or left blank (`INVALID_QUERY`); on a list, a `limit` that is not a whole number (`INVALID_LIMIT`) or a `cursor` this list did not answer (`INVALID_CURSOR`)' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '401': description: Missing or invalid API key (`UNAUTHORIZED`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' WWW-Authenticate: $ref: '#/components/headers/WWWAuthenticate' '403': description: The project is archived; or `X-Organization-Slug` names an organization the key holder is no member of (`ORG_FORBIDDEN`, with the slugs the key holder may send under `data.organizations`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '404': description: Thread missing, owned by another user, or outside the visible URL scope; or `X-Organization-Slug` names no organization (`ORG_SLUG_INVALID`, with the slugs the key holder may send under `data.organizations`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '405': description: The path exists, but not for this method (`METHOD_NOT_ALLOWED`); `Allow` names the methods it serves content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Allow: $ref: '#/components/headers/Allow' '409': description: '`archived: true` while a turn is running on the thread, or an accepted send is still queued (`CHAT_TURN_IN_PROGRESS`)' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '413': description: The body exceeds this operation’s byte cap — 1 MiB unless the description says otherwise (`BODY_TOO_LARGE`); the envelope carries a `requestId` content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '429': description: 'The key holder’s request budget is spent (`RATE_LIMITED`): `Retry-After` names the wait in whole seconds and `data.retryAfterMs` in milliseconds; the envelope carries a `requestId`' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Retry-After: $ref: '#/components/headers/RetryAfter' '500': description: Internal error (`INTERNAL_ERROR`); the envelope carries a `requestId` to quote when reporting it content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '503': description: 'The platform’s database is restarting or cannot be reached (`DATABASE_UNAVAILABLE`): `Retry-After` names the wait in whole seconds — retry with backoff; the envelope carries a `requestId`' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Retry-After: $ref: '#/components/headers/RetryAfter' delete: tags: - Threads summary: Delete a thread description: Only the key holder’s threads belonging to the URL project; requires project read access. Moves the thread to the app’s trash (its grace window and legal-hold check apply). A thread whose turn is still running — or whose accepted send is still queued — answers 409 `CHAT_TURN_IN_PROGRESS`; cancel the turn first. operationId: deleteProjectThread security: - bearerAuth: [] parameters: - name: X-Organization-Slug in: header required: false schema: type: string description: The organization to operate on. STRICTLY REQUIRED on every call, reads included, when the key holder belongs to more than one organization — without it such requests are refused with 400 `ORG_SLUG_REQUIRED` (the dashboard’s last-active organization never steers a machine call). A slug that names no organization answers 404 `ORG_SLUG_INVALID`, one the key holder is no member of 403 `ORG_FORBIDDEN`. Single-organization keys may omit it; a blank or whitespace-only value reads as the header absent. Each of the three refusals lists the slugs the key holder may send under `data.organizations`; `GET /api/v1/me` answers them too. - name: id in: path required: true schema: type: string description: Project ID - name: threadId in: path required: true schema: type: string description: Thread ID - name: X-Request-Id in: header required: false description: 'Your own correlation id, echoed back on the response and written to the platform’s log: up to 255 characters of letters, digits, `_`, `-` and `=`. Any other value is replaced by a fresh UUID (the response carries the id that was used), and uniqueness of an id you choose is yours to keep.' schema: type: string pattern: ^[A-Za-z0-9_=-]{1,255}$ responses: '204': description: Deleted headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '400': description: 'Invalid request: a body the schema refuses — a wrong type, an unknown key, a missing field, a NUL or an unpaired surrogate, a whole number beyond 2^53 − 1 (`INVALID_BODY`, every problem under `data.issues`); a query parameter the operation does not take, given twice or left blank (`INVALID_QUERY`); on a list, a `limit` that is not a whole number (`INVALID_LIMIT`) or a `cursor` this list did not answer (`INVALID_CURSOR`)' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '401': description: Missing or invalid API key (`UNAUTHORIZED`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' WWW-Authenticate: $ref: '#/components/headers/WWWAuthenticate' '403': description: The project is archived; or `X-Organization-Slug` names an organization the key holder is no member of (`ORG_FORBIDDEN`, with the slugs the key holder may send under `data.organizations`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '404': description: Thread missing, owned by another user, or outside the visible URL scope; or `X-Organization-Slug` names no organization (`ORG_SLUG_INVALID`, with the slugs the key holder may send under `data.organizations`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '405': description: The path exists, but not for this method (`METHOD_NOT_ALLOWED`); `Allow` names the methods it serves content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Allow: $ref: '#/components/headers/Allow' '409': description: A turn is running on the thread, or an accepted send is still queued (`CHAT_TURN_IN_PROGRESS`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '429': description: 'The key holder’s request budget is spent (`RATE_LIMITED`): `Retry-After` names the wait in whole seconds and `data.retryAfterMs` in milliseconds; the envelope carries a `requestId`' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Retry-After: $ref: '#/components/headers/RetryAfter' '500': description: Internal error (`INTERNAL_ERROR`); the envelope carries a `requestId` to quote when reporting it content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '503': description: 'The platform’s database is restarting or cannot be reached (`DATABASE_UNAVAILABLE`): `Retry-After` names the wait in whole seconds — retry with backoff; the envelope carries a `requestId`' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Retry-After: $ref: '#/components/headers/RetryAfter' /api/v1/projects/{id}/threads/{threadId}/messages: get: tags: - Threads summary: Read a thread’s messages description: Only the key holder’s threads belonging to the URL project; requires project read access. Messages are in sequence order — oldest first, or newest first with `order=desc`, so the reply a turn just produced is on the first page of a long thread; use the previous continueCursor as cursor for the next page (a cursor is bound to the direction it was minted in). While a turn runs, its assistant row is already on the page with status `pending` and empty parts — the row GET /api/v1/projects/{id}/threads/{threadId}/generation names as messageId — and is filled in when the turn settles; the page is complete without that row being final. One message is also readable by id at GET /api/v1/projects/{id}/threads/{threadId}/messages/{messageId}. operationId: listProjectThreadMessages security: - bearerAuth: [] parameters: - name: X-Organization-Slug in: header required: false schema: type: string description: The organization to operate on. STRICTLY REQUIRED on every call, reads included, when the key holder belongs to more than one organization — without it such requests are refused with 400 `ORG_SLUG_REQUIRED` (the dashboard’s last-active organization never steers a machine call). A slug that names no organization answers 404 `ORG_SLUG_INVALID`, one the key holder is no member of 403 `ORG_FORBIDDEN`. Single-organization keys may omit it; a blank or whitespace-only value reads as the header absent. Each of the three refusals lists the slugs the key holder may send under `data.organizations`; `GET /api/v1/me` answers them too. - name: id in: path required: true schema: type: string description: Project ID - name: threadId in: path required: true schema: type: string description: Thread ID - name: cursor in: query required: false schema: type: string description: The previous page’s `continueCursor`, unchanged — opaque; omit for the first page. A value this list did not answer is refused with 400 `INVALID_CURSOR` rather than read as the first page - name: limit in: query required: false schema: type: integer minimum: 1 maximum: 100 default: 25 description: Page size, 1..100 (default 25; out-of-range numbers are clamped, a value that is not a number answers 400 `INVALID_LIMIT`) - name: order in: query required: false schema: type: string enum: - asc - desc default: asc description: 'The walk’s direction: `asc` (the default) pages oldest first from sequence 0, `desc` pages newest first — the settled reply of the latest turn leads the first page. A `cursor` minted walking one direction is refused with 400 `INVALID_CURSOR` on the other' - name: X-Request-Id in: header required: false description: 'Your own correlation id, echoed back on the response and written to the platform’s log: up to 255 characters of letters, digits, `_`, `-` and `=`. Any other value is replaced by a fresh UUID (the response carries the id that was used), and uniqueness of an id you choose is yours to keep.' schema: type: string pattern: ^[A-Za-z0-9_=-]{1,255}$ responses: '200': description: Paginated messages content: application/json: schema: type: object x-tale-pagination: keyset required: - page - isDone - continueCursor properties: page: type: array items: $ref: '#/components/schemas/Message' isDone: type: boolean continueCursor: type: string description: Pass back as `cursor` for the next page; empty when `isDone` headers: ETag: $ref: '#/components/headers/ETag' Cache-Control: $ref: '#/components/headers/CacheControl' X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '304': description: Not Modified — `If-None-Match` named the current `ETag`, so the body is not sent headers: ETag: $ref: '#/components/headers/ETag' Cache-Control: $ref: '#/components/headers/CacheControl' X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '400': description: 'Invalid request: a body the schema refuses — a wrong type, an unknown key, a missing field, a NUL or an unpaired surrogate, a whole number beyond 2^53 − 1 (`INVALID_BODY`, every problem under `data.issues`); a query parameter the operation does not take, given twice or left blank (`INVALID_QUERY`); on a list, a `limit` that is not a whole number (`INVALID_LIMIT`) or a `cursor` this list did not answer (`INVALID_CURSOR`)' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '401': description: Missing or invalid API key (`UNAUTHORIZED`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' WWW-Authenticate: $ref: '#/components/headers/WWWAuthenticate' '403': description: '`X-Organization-Slug` names an organization the key holder is no member of (`ORG_FORBIDDEN`, with the slugs the key holder may send under `data.organizations`)' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '404': description: Thread missing, owned by another user, or outside the visible URL scope; or `X-Organization-Slug` names no organization (`ORG_SLUG_INVALID`, with the slugs the key holder may send under `data.organizations`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '405': description: The path exists, but not for this method (`METHOD_NOT_ALLOWED`); `Allow` names the methods it serves content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Allow: $ref: '#/components/headers/Allow' '429': description: 'The key holder’s request budget is spent (`RATE_LIMITED`): `Retry-After` names the wait in whole seconds and `data.retryAfterMs` in milliseconds; the envelope carries a `requestId`' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Retry-After: $ref: '#/components/headers/RetryAfter' '500': description: Internal error (`INTERNAL_ERROR`); the envelope carries a `requestId` to quote when reporting it content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '503': description: 'The platform’s database is restarting or cannot be reached (`DATABASE_UNAVAILABLE`): `Retry-After` names the wait in whole seconds — retry with backoff; the envelope carries a `requestId`' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Retry-After: $ref: '#/components/headers/RetryAfter' post: tags: - Threads summary: Send a message and start a turn description: 'Only the key holder’s threads belonging to the URL project; requires project read access. The project must be active; members can send without an editor seat. Answers 202 while the turn runs in the background; the 202 names the assistant message the reply lands in (`messageId`). Poll GET /api/v1/projects/{id}/threads/{threadId}/generation until status is idle, then read the messages. Send `Idempotency-Key` to make the send safe to retry: a repeat within 24 hours answers what the first attempt answered — the same `messageId` — with `duplicate: true` and queues nothing, and a repeat with a different body answers 409 `IDEMPOTENCY_KEY_REUSED`; a refused send remembers nothing. Every turn runs the built-in workspace assistant: its instructions, safety rules and three retrieval tools ride every request (about 3,000 prompt tokens per model round, counted in `usage.inputTokens` — a turn that calls a tool runs up to five rounds, each billing its full prompt again), and a request for a deliverable is redirected to Tasks by design — this is a conversation with the workspace, not a bare model call. A budget cap that binds the key holder — their own, one of their teams’, the conversation’s project’s, the organization’s or this API key’s — refuses the send with 429 `BUDGET_EXCEEDED` before anything is queued; a cap reached while an accepted send waited settles its `messageId` as failed with errorCode `budget_exceeded`. A turn failure appears as an assistant error message. Charges the execute bucket on top of the general REST bucket.' operationId: postProjectThreadMessage security: - bearerAuth: [] parameters: - name: X-Organization-Slug in: header required: false schema: type: string description: The organization to operate on. STRICTLY REQUIRED on every call, reads included, when the key holder belongs to more than one organization — without it such requests are refused with 400 `ORG_SLUG_REQUIRED` (the dashboard’s last-active organization never steers a machine call). A slug that names no organization answers 404 `ORG_SLUG_INVALID`, one the key holder is no member of 403 `ORG_FORBIDDEN`. Single-organization keys may omit it; a blank or whitespace-only value reads as the header absent. Each of the three refusals lists the slugs the key holder may send under `data.organizations`; `GET /api/v1/me` answers them too. - name: id in: path required: true schema: type: string description: Project ID - name: threadId in: path required: true schema: type: string description: Thread ID - name: Idempotency-Key in: header required: false schema: type: string pattern: ^[ -~]*[!-~][ -~]*$ maxLength: 255 description: 'A stable key of your choosing that names this send — printable ASCII (a UUID, a job id), at most 255 characters: any other character answers 400 `INVALID_HEADER`, naming the header under `data.issues`, and no send happens — keys are compared byte for byte once surrounding whitespace is removed, so the pattern is what keeps two spellings of one key from naming two sends; a header value carrying a control character never reaches the platform, the edge refuses the request before any envelope. A repeat with the same key, thread and scope within 24 hours answers 202 with what the first attempt answered — the same `messageId` to poll for, nothing new queued and `duplicate: true`; a repeat with a different body answers 409 `IDEMPOTENCY_KEY_REUSED`. A refused send is not remembered, so the same key runs once the refusal is fixed. A header sent but left blank (spaces only), or one over 255 characters, answers 400 `INVALID_HEADER` and starts nothing — omit the header to proceed without a key; it is never silently read as "no key".' - name: X-Request-Id in: header required: false description: 'Your own correlation id, echoed back on the response and written to the platform’s log: up to 255 characters of letters, digits, `_`, `-` and `=`. Any other value is replaced by a fresh UUID (the response carries the id that was used), and uniqueness of an id you choose is yours to keep.' schema: type: string pattern: ^[A-Za-z0-9_=-]{1,255}$ requestBody: required: true content: application/json: schema: type: object additionalProperties: false required: - content - model properties: content: type: string minLength: 1 maxLength: 100000 description: 'The prompt, trimmed before the length check — a blank prompt (nothing but whitespace and invisible format characters: zero-width spaces, joiners, bidi marks) is 400 `INVALID_BODY`, not a turn; markdown that renders as nothing, an empty code fence say, is still a prompt. The 100,000 cap counts UTF-16 code units, so an astral character — an emoji — costs two, and a body of 50,001 of them is refused. Text only: this surface takes no image or file input, on a `vision` model too — a data URI or base64 pasted here reaches the model as text and is answered as text; image attachments are the app’s composer.' model: type: string minLength: 1 maxLength: 200 description: 'Model ID from GET /api/v1/models; never auto-selected. Checked at the door: without `providerSlug`, an id the list does not carry answers 400 `CHAT_MODEL_UNKNOWN` (and an id listed under several providers `CHAT_MODEL_AMBIGUOUS`); with a `providerSlug` the pair is judged instead — an unknown provider answers `CHAT_PROVIDER_UNKNOWN`, a listed provider that does not serve the id `CHAT_MODEL_NOT_ON_PROVIDER`.' providerSlug: type: string minLength: 1 maxLength: 200 description: 'Provider slug returned with the model. Checked against GET /api/v1/models: a slug the list does not carry answers 400 `CHAT_PROVIDER_UNKNOWN`, one that does not serve the model 400 `CHAT_MODEL_NOT_ON_PROVIDER` — never a silent fallback to another provider. Optional when exactly one listed provider serves the model; a model listed under several providers answers 400 `CHAT_MODEL_AMBIGUOUS` with `data.providers` until one is named. The 202 names the provider the turn runs on either way.' reasoningEffort: type: string enum: - low - medium - high - extra - max description: The reasoning depth, on the same five-step scale the app offers; omitted samples the model’s default. Ignored by a model whose `capabilities.reasoning` is false. A higher step spends more of the cap and of the bill — reasoning tokens count inside `maxOutputTokens` and inside `usage.outputTokens` at the output price — and at `high` or above a slower model can think for a minute or more before any answer; a turn has no fixed deadline, so bound your poll loop. maxOutputTokens: type: integer minimum: 1 description: 'The largest reply this turn may produce across every model round, in tokens — a cap under the model’s own `maxOutputTokens` from GET /api/v1/models; a value above it answers 400 `INVALID_BODY`. Omitted, the model’s own ceiling applies. A tool-calling turn spends the cap round by round: a later round gets what the earlier ones left, and a round that would start with nothing left is not run. A reasoning model spends its reasoning inside the cap too, and the catalog does not say how much: a cap of a few thousand tokens can be consumed by the reasoning alone and settle an EMPTY reply — `complete`, `finishReason: "length"`, no text part, billed, the reasoning tokens counted inside `usage.outputTokens` at the output price — so give a reasoning model several thousand tokens or lower `reasoningEffort`. The one floor is the thinking-budget dialect (Anthropic-style extended thinking): there a cap under 2,048 is raised to 2,048; a model whose reasoning is set by an effort level (the GLM and DeepSeek families) has no floor and the cap bites exactly. A reply that runs into the cap still settles `complete`, cut short, and says so with `finishReason: "length"` on the message — a cut that fell on an earlier round’s tool calls included: every call of that round is withheld — it does not run and its tool-result reads `status: "invalid_args"`, the message saying whether its arguments were cut or complete.' locale: type: string minLength: 2 maxLength: 20 pattern: ^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$ description: A BCP 47 language tag (`de`, `en-GB`) — the language the assistant is asked to answer in, whatever language the prompt is written in. The instruction rides the prompt as a directive, on the system prompt and on the message itself; a model may still slip (most often a reasoning model on a short prompt), and nothing on the wire marks a slip — a client that must have the language checks the reply and resends. An organization’s mandatory instructions win over `locale`. Omitted, the assistant answers in the prompt’s language. responses: '202': description: Turn accepted, or the send an earlier attempt under the same `Idempotency-Key` accepted content: application/json: schema: type: object required: - threadId - status - model - providerSlug - messageId - poll properties: duplicate: type: boolean enum: - true description: 'Present when the `Idempotency-Key` had already accepted this send: nothing new was queued — poll for the `messageId` it names' threadId: type: string status: type: string enum: - accepted model: type: string providerSlug: type: string description: The provider the turn runs on — named in the request, or resolved from the model list messageId: type: string description: 'The assistant message the reply lands in — the row GET …/messages carries as `pending` until the turn settles. Keep it: a caller that loses this response finds its reply by this id.' poll: type: string description: Poll URL under /api/v1/projects/{id}/threads/{threadId}/generation headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '400': description: Invalid body (`INVALID_BODY`), a model the list does not carry (`CHAT_MODEL_UNKNOWN`), a model listed under several providers with none named (`CHAT_MODEL_AMBIGUOUS`), an unknown providerSlug (`CHAT_PROVIDER_UNKNOWN`), or a provider that does not serve the model (`CHAT_MODEL_NOT_ON_PROVIDER`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '401': description: Missing or invalid API key (`UNAUTHORIZED`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' WWW-Authenticate: $ref: '#/components/headers/WWWAuthenticate' '403': description: The project is archived; or `X-Organization-Slug` names an organization the key holder is no member of (`ORG_FORBIDDEN`, with the slugs the key holder may send under `data.organizations`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '404': description: Thread missing, owned by another user, or outside the visible URL scope; or `X-Organization-Slug` names no organization (`ORG_SLUG_INVALID`, with the slugs the key holder may send under `data.organizations`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '405': description: The path exists, but not for this method (`METHOD_NOT_ALLOWED`); `Allow` names the methods it serves content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Allow: $ref: '#/components/headers/Allow' '409': description: The thread is archived (`CHAT_THREAD_ARCHIVED`) or not a direct chat (`CHAT_THREAD_NOT_DIRECT`), a turn is already running or still queued (`CHAT_TURN_IN_PROGRESS`) — nothing is queued and the running turn keeps its `messageId` — or the `Idempotency-Key` was already used for a different request (`IDEMPOTENCY_KEY_REUSED`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '413': description: The body exceeds this operation’s byte cap — 1 MiB unless the description says otherwise (`BODY_TOO_LARGE`); the envelope carries a `requestId` content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '429': description: 'The key holder’s request budget is spent (`RATE_LIMITED`): `Retry-After` names the wait in whole seconds and `data.retryAfterMs` in milliseconds; the envelope carries a `requestId`; or a budget cap that binds the key holder is reached (`BUDGET_EXCEEDED`): nothing is queued, `data` names the cap — `scope`, `period`, `limitCode`, `used`, `limit`, `resetsAt` — and `Retry-After` the wait in whole seconds until its period resets' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Retry-After: $ref: '#/components/headers/RetryAfter' '500': description: Internal error (`INTERNAL_ERROR`); the envelope carries a `requestId` to quote when reporting it content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '503': description: 'The platform’s database is restarting or cannot be reached (`DATABASE_UNAVAILABLE`): `Retry-After` names the wait in whole seconds — retry with backoff; the envelope carries a `requestId`' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Retry-After: $ref: '#/components/headers/RetryAfter' /api/v1/projects/{id}/threads/{threadId}/messages/{messageId}: get: tags: - Threads summary: Read one message description: 'Only the key holder’s threads belonging to the URL project; requires project read access. One message of the thread by id — the reply the 202 named (`messageId`), read without paging the transcript. The same shape as a row of GET /api/v1/projects/{id}/threads/{threadId}/messages: `pending` while its turn still runs, settled with `finishReason` and `usage` once the poll says idle. A message id the thread does not carry answers 404 `MESSAGE_NOT_FOUND`.' operationId: getProjectThreadMessage security: - bearerAuth: [] parameters: - name: X-Organization-Slug in: header required: false schema: type: string description: The organization to operate on. STRICTLY REQUIRED on every call, reads included, when the key holder belongs to more than one organization — without it such requests are refused with 400 `ORG_SLUG_REQUIRED` (the dashboard’s last-active organization never steers a machine call). A slug that names no organization answers 404 `ORG_SLUG_INVALID`, one the key holder is no member of 403 `ORG_FORBIDDEN`. Single-organization keys may omit it; a blank or whitespace-only value reads as the header absent. Each of the three refusals lists the slugs the key holder may send under `data.organizations`; `GET /api/v1/me` answers them too. - name: id in: path required: true schema: type: string description: Project ID - name: threadId in: path required: true schema: type: string description: Thread ID - name: messageId in: path required: true schema: type: string description: Message ID - name: X-Request-Id in: header required: false description: 'Your own correlation id, echoed back on the response and written to the platform’s log: up to 255 characters of letters, digits, `_`, `-` and `=`. Any other value is replaced by a fresh UUID (the response carries the id that was used), and uniqueness of an id you choose is yours to keep.' schema: type: string pattern: ^[A-Za-z0-9_=-]{1,255}$ responses: '200': description: The message content: application/json: schema: $ref: '#/components/schemas/Message' headers: ETag: $ref: '#/components/headers/ETag' Cache-Control: $ref: '#/components/headers/CacheControl' X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '304': description: Not Modified — `If-None-Match` named the current `ETag`, so the body is not sent headers: ETag: $ref: '#/components/headers/ETag' Cache-Control: $ref: '#/components/headers/CacheControl' X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '400': description: 'Invalid request: a body the schema refuses — a wrong type, an unknown key, a missing field, a NUL or an unpaired surrogate, a whole number beyond 2^53 − 1 (`INVALID_BODY`, every problem under `data.issues`); a query parameter the operation does not take, given twice or left blank (`INVALID_QUERY`); on a list, a `limit` that is not a whole number (`INVALID_LIMIT`) or a `cursor` this list did not answer (`INVALID_CURSOR`)' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '401': description: Missing or invalid API key (`UNAUTHORIZED`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' WWW-Authenticate: $ref: '#/components/headers/WWWAuthenticate' '403': description: '`X-Organization-Slug` names an organization the key holder is no member of (`ORG_FORBIDDEN`, with the slugs the key holder may send under `data.organizations`)' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '404': description: Thread missing, owned by another user, or outside the visible URL scope (`THREAD_NOT_FOUND`); or no such message on the thread (`MESSAGE_NOT_FOUND`); or `X-Organization-Slug` names no organization (`ORG_SLUG_INVALID`, with the slugs the key holder may send under `data.organizations`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '405': description: The path exists, but not for this method (`METHOD_NOT_ALLOWED`); `Allow` names the methods it serves content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Allow: $ref: '#/components/headers/Allow' '429': description: 'The key holder’s request budget is spent (`RATE_LIMITED`): `Retry-After` names the wait in whole seconds and `data.retryAfterMs` in milliseconds; the envelope carries a `requestId`' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Retry-After: $ref: '#/components/headers/RetryAfter' '500': description: Internal error (`INTERNAL_ERROR`); the envelope carries a `requestId` to quote when reporting it content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '503': description: 'The platform’s database is restarting or cannot be reached (`DATABASE_UNAVAILABLE`): `Retry-After` names the wait in whole seconds — retry with backoff; the envelope carries a `requestId`' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Retry-After: $ref: '#/components/headers/RetryAfter' /api/v1/projects/{id}/threads/{threadId}/generation: get: tags: - Threads summary: Poll the running turn description: 'Only the key holder’s threads belonging to the URL project; requires project read access. `queued`: the accepted send is waiting for a worker (the 202 was answered, the turn has not opened) — accepted sends form one oldest-first queue shared by the whole deployment, worked in batches of up to five turns with the next batch starting only once the running one has settled, so a burst of sends completes in waves, not in parallel. `streaming`: the model is streaming its reply to the server — `text` and `reasoning` carry what has arrived so far, so a poller sees progress; send `since` (the UTF-16 code units of `text` you already hold — JavaScript `String.length`, an emoji counting two; not code points) and `reasoningSince` (the same for `reasoning`) to receive only what arrived after them, with `textOffset`/`textLength` and `reasoningOffset`/`reasoningLength` beside them — reassemble by one rule, `held = held.slice(0, textOffset) + text`, and send `textLength` back as `since`; there is no push channel on this surface. A turn has no fixed deadline — the server abandons one only after 180 seconds of provider silence — so bound your own loop and stop a turn you no longer want with DELETE /api/v1/projects/{id}/threads/{threadId}/generation. `idle`: no turn is running — `lastMessageId` and `lastStatus` name the newest assistant message and how it settled. The recipe after a send (or a lost 202): poll until `idle`, then compare `lastMessageId` with the `messageId` the 202 named — equal means your turn settled (read that message at GET /api/v1/projects/{id}/threads/{threadId}/messages/{messageId}, or page the messages with `order=desc`), a different id means yours has not started yet.' operationId: getProjectThreadGeneration security: - bearerAuth: [] parameters: - name: X-Organization-Slug in: header required: false schema: type: string description: The organization to operate on. STRICTLY REQUIRED on every call, reads included, when the key holder belongs to more than one organization — without it such requests are refused with 400 `ORG_SLUG_REQUIRED` (the dashboard’s last-active organization never steers a machine call). A slug that names no organization answers 404 `ORG_SLUG_INVALID`, one the key holder is no member of 403 `ORG_FORBIDDEN`. Single-organization keys may omit it; a blank or whitespace-only value reads as the header absent. Each of the three refusals lists the slugs the key holder may send under `data.organizations`; `GET /api/v1/me` answers them too. - name: id in: path required: true schema: type: string description: Project ID - name: threadId in: path required: true schema: type: string description: Thread ID - name: since in: query required: false schema: type: integer minimum: 0 description: 'The number of UTF-16 code units (JavaScript `String.length` — an emoji counts two; not code points, not grapheme clusters) of `text` you already hold from an earlier poll: the answer carries only what arrived after them, `textOffset` says where the slice starts — the value you sent, one lower when that would have split a surrogate pair (an astral character is two units, and a slice opening on its second half would put an unpaired surrogate on the wire), or 0 when a tool round reset the reply — and `textLength` how long the whole reply is so far. Reassemble by one rule, `held = held.slice(0, textOffset) + text`, and send `textLength` back as `since`: it never falls inside a pair. Ignored unless the turn is streaming' - name: reasoningSince in: query required: false schema: type: integer minimum: 0 description: 'The number of UTF-16 code units (JavaScript `String.length`) of `reasoning` you already hold from an earlier poll — the `since` of the reasoning stream: the answer carries only what arrived after them, `reasoningOffset` says where the slice starts — the value you sent, one lower when that would have split a surrogate pair, or 0 when a tool round reset the reasoning with the text — and `reasoningLength` how long the whole reasoning is so far. Reassemble as `held = held.slice(0, reasoningOffset) + reasoning` and send `reasoningLength` back as `reasoningSince`. Ignored unless the turn is streaming' - name: X-Request-Id in: header required: false description: 'Your own correlation id, echoed back on the response and written to the platform’s log: up to 255 characters of letters, digits, `_`, `-` and `=`. Any other value is replaced by a fresh UUID (the response carries the id that was used), and uniqueness of an id you choose is yours to keep.' schema: type: string pattern: ^[A-Za-z0-9_=-]{1,255}$ responses: '200': description: Idle when no turn is running; otherwise the live status content: application/json: schema: type: object required: - status properties: status: type: string enum: - idle - queued - streaming messageId: type: string description: The assistant message the turn streams into (present while queued or streaming) text: type: string description: The reply text streamed so far — from `textOffset` on when `since` was sent, always whole characters (present while streaming) textOffset: type: integer minimum: 0 description: 'Where `text` starts, in UTF-16 code units: the `since` you sent, one lower when that would have split a surrogate pair, or 0 when the reply was reset since — reassemble as `held = held.slice(0, textOffset) + text` (present while streaming)' textLength: type: integer minimum: 0 description: The length of the whole reply streamed so far, in UTF-16 code units (JavaScript `String.length`) — send it back as `since` on the next poll; it never falls inside a surrogate pair (present while streaming) lastMessageId: type: string description: The newest assistant message on the thread (present while idle, once the thread has one) — compare with the `messageId` a 202 named to know whether that turn settled lastStatus: type: string enum: - pending - complete - failed - cancelled description: How `lastMessageId` settled (present with it) reasoning: type: string description: The model’s reasoning streamed so far — from `reasoningOffset` on when `reasoningSince` was sent, always whole characters; display-only, may be empty (present while streaming) reasoningOffset: type: integer minimum: 0 description: 'Where `reasoning` starts, in UTF-16 code units: the `reasoningSince` you sent, one lower when that would have split a surrogate pair, or 0 when the reasoning was reset since (present while streaming)' reasoningLength: type: integer minimum: 0 description: The length of the whole reasoning streamed so far, in UTF-16 code units — send it back as `reasoningSince` on the next poll; it never falls inside a surrogate pair (present while streaming) cancelRequested: type: boolean description: A stop was asked for and the turn has not settled yet (present while streaming) updatedAt: type: integer minimum: 0 description: Epoch ms of the last progress write (present while streaming) headers: ETag: $ref: '#/components/headers/ETag' Cache-Control: $ref: '#/components/headers/CacheControl' X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '304': description: Not Modified — `If-None-Match` named the current `ETag`, so the body is not sent headers: ETag: $ref: '#/components/headers/ETag' Cache-Control: $ref: '#/components/headers/CacheControl' X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '400': description: 'Invalid request: a body the schema refuses — a wrong type, an unknown key, a missing field, a NUL or an unpaired surrogate, a whole number beyond 2^53 − 1 (`INVALID_BODY`, every problem under `data.issues`); a query parameter the operation does not take, given twice or left blank (`INVALID_QUERY`); on a list, a `limit` that is not a whole number (`INVALID_LIMIT`) or a `cursor` this list did not answer (`INVALID_CURSOR`)' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '401': description: Missing or invalid API key (`UNAUTHORIZED`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' WWW-Authenticate: $ref: '#/components/headers/WWWAuthenticate' '403': description: '`X-Organization-Slug` names an organization the key holder is no member of (`ORG_FORBIDDEN`, with the slugs the key holder may send under `data.organizations`)' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '404': description: Thread missing, owned by another user, or outside the visible URL scope; or `X-Organization-Slug` names no organization (`ORG_SLUG_INVALID`, with the slugs the key holder may send under `data.organizations`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '405': description: The path exists, but not for this method (`METHOD_NOT_ALLOWED`); `Allow` names the methods it serves content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Allow: $ref: '#/components/headers/Allow' '429': description: 'The key holder’s request budget is spent (`RATE_LIMITED`): `Retry-After` names the wait in whole seconds and `data.retryAfterMs` in milliseconds; the envelope carries a `requestId`' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Retry-After: $ref: '#/components/headers/RetryAfter' '500': description: Internal error (`INTERNAL_ERROR`); the envelope carries a `requestId` to quote when reporting it content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '503': description: 'The platform’s database is restarting or cannot be reached (`DATABASE_UNAVAILABLE`): `Retry-After` names the wait in whole seconds — retry with backoff; the envelope carries a `requestId`' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Retry-After: $ref: '#/components/headers/RetryAfter' delete: tags: - Threads summary: Cancel the running turn description: 'Only the key holder’s threads belonging to the URL project; requires project read access. Asks the running turn to stop — the app’s own stop: the turn reads the flag at its next progress write and settles what it has. 202 means the stop is requested, not done; poll until idle. A send still queued (no worker has opened it) is stopped the same way: 202 naming the reply the send’s 202 promised, the model is never called, and that reply settles as `cancelled` with empty `parts`. Nothing running and nothing queued answers 404 `CHAT_TURN_NOT_RUNNING` with `data.lastMessageId` and `data.lastStatus` — the idle poll’s answer, so a stop that lost the race against a fast model needs no second call.' operationId: cancelProjectThreadGeneration security: - bearerAuth: [] parameters: - name: X-Organization-Slug in: header required: false schema: type: string description: The organization to operate on. STRICTLY REQUIRED on every call, reads included, when the key holder belongs to more than one organization — without it such requests are refused with 400 `ORG_SLUG_REQUIRED` (the dashboard’s last-active organization never steers a machine call). A slug that names no organization answers 404 `ORG_SLUG_INVALID`, one the key holder is no member of 403 `ORG_FORBIDDEN`. Single-organization keys may omit it; a blank or whitespace-only value reads as the header absent. Each of the three refusals lists the slugs the key holder may send under `data.organizations`; `GET /api/v1/me` answers them too. - name: id in: path required: true schema: type: string description: Project ID - name: threadId in: path required: true schema: type: string description: Thread ID - name: X-Request-Id in: header required: false description: 'Your own correlation id, echoed back on the response and written to the platform’s log: up to 255 characters of letters, digits, `_`, `-` and `=`. Any other value is replaced by a fresh UUID (the response carries the id that was used), and uniqueness of an id you choose is yours to keep.' schema: type: string pattern: ^[A-Za-z0-9_=-]{1,255}$ responses: '202': description: Cancellation requested — of the running turn, or of the queued send content: application/json: schema: type: object required: - status properties: status: type: string enum: - cancelling messageId: type: string description: The assistant message the turn was writing, or the one the queued send was to write headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '400': description: 'Invalid request: a body the schema refuses — a wrong type, an unknown key, a missing field, a NUL or an unpaired surrogate, a whole number beyond 2^53 − 1 (`INVALID_BODY`, every problem under `data.issues`); a query parameter the operation does not take, given twice or left blank (`INVALID_QUERY`); on a list, a `limit` that is not a whole number (`INVALID_LIMIT`) or a `cursor` this list did not answer (`INVALID_CURSOR`)' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '401': description: Missing or invalid API key (`UNAUTHORIZED`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' WWW-Authenticate: $ref: '#/components/headers/WWWAuthenticate' '403': description: The project is archived; or `X-Organization-Slug` names an organization the key holder is no member of (`ORG_FORBIDDEN`, with the slugs the key holder may send under `data.organizations`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '404': description: Thread missing, owned by another user, or outside the visible URL scope (`THREAD_NOT_FOUND`); or no turn is running and no send is queued (`CHAT_TURN_NOT_RUNNING` — `data.lastMessageId` and `data.lastStatus` name the newest assistant message, as the idle poll does); or `X-Organization-Slug` names no organization (`ORG_SLUG_INVALID`, with the slugs the key holder may send under `data.organizations`) content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '405': description: The path exists, but not for this method (`METHOD_NOT_ALLOWED`); `Allow` names the methods it serves content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Allow: $ref: '#/components/headers/Allow' '429': description: 'The key holder’s request budget is spent (`RATE_LIMITED`): `Retry-After` names the wait in whole seconds and `data.retryAfterMs` in milliseconds; the envelope carries a `requestId`' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Retry-After: $ref: '#/components/headers/RetryAfter' '500': description: Internal error (`INTERNAL_ERROR`); the envelope carries a `requestId` to quote when reporting it content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' '503': description: 'The platform’s database is restarting or cannot be reached (`DATABASE_UNAVAILABLE`): `Retry-After` names the wait in whole seconds — retry with backoff; the envelope carries a `requestId`' content: application/json: schema: $ref: '#/components/schemas/Error' headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-Tale-Api-Version: $ref: '#/components/headers/XTaleApiVersion' Retry-After: $ref: '#/components/headers/RetryAfter' components: schemas: ToolCallPart: type: object required: - type - callId - capabilityId - input properties: type: type: string enum: - tool-call callId: type: string capabilityId: type: string input: {} AttachmentPart: type: object required: - type - name - mediaType properties: type: type: string enum: - attachment name: type: string mediaType: type: string fileId: type: string sizeBytes: type: integer url: type: string text: type: string description: Extracted text, when any ChatModel: type: object required: - id - label - providerSlug - providerLabel - contextWindow - capabilities - tags properties: id: type: string description: Send as `model` label: type: string description: A display name for a picker (`DeepSeek V4 Flash`, `GLM 5v Turbo`) — derived from the id where the catalog publishes none; never send it as `model` providerSlug: type: string description: Send as `providerSlug` when the same id is listed under more than one provider providerLabel: type: string contextWindow: type: integer minimum: 1 description: Total context window in tokens maxOutputTokens: type: integer minimum: 1 description: The largest reply the model produces, in tokens. Absent when the catalog declares no ceiling — then no `maxOutputTokens` check applies to a send. capabilities: type: object required: - tools - vision - reasoning additionalProperties: false properties: tools: type: boolean description: Accepts function tools vision: type: boolean description: 'Reads images — a fact about the model, not an input this surface offers: the REST send is text only (`content`), so `vision` matters here for a thread the app continues with image attachments, whose `attachment` parts the model then sees. An image pasted into `content` as a data URI is read as text.' reasoning: type: boolean description: Has a controllable reasoning depth pricing: type: object required: - inputCentsPerMillion - outputCentsPerMillion additionalProperties: false description: The catalog price in US cents per million tokens; absent when the source publishes none properties: inputCentsPerMillion: type: number minimum: 0 outputCentsPerMillion: type: number minimum: 0 tags: type: array items: type: string description: The catalog’s capability tags (`chat`, `vision`, …) — an open, additive vocabulary describing the model, like `capabilities`; `vision` does not add an image input to the text-only REST send default: type: boolean description: Present and true on the organization’s default pick for this key holder, when one is configured and accessible ReasoningPart: type: object required: - type - text description: The model’s reasoning ahead of its reply — display-only, never replayed to the model; it may quote the assistant’s own instructions verbatim, so it is not safe to render as the answer. properties: type: type: string enum: - reasoning text: type: string Error: type: object required: - error - code properties: error: type: string description: What went wrong code: type: string enum: - ACTOR_AMBIGUOUS - ACTOR_DISABLED - ACTOR_FORBIDDEN - ACTOR_NOT_FOUND - ACTOR_REBOUND - ACTOR_UNVERIFIED - API_KEY_SCOPE_FORBIDDEN - ATTACHMENT_NOT_FOUND - ATTACHMENT_NOT_OWNED - ATTACHMENT_NOT_STAGED - ATTACHMENT_SIZE_MISMATCH - AUTHOR_UNVERIFIED - AUTOMATION_DELIVERY_SCOPE_MISMATCH - AUTOMATION_HAS_ACTIVE_RUNS - AUTOMATION_INPUT_INVALID - AUTOMATION_NOT_DEPLOYED - AUTOMATION_NOT_FOUND - AUTOMATION_NOT_INSTALLED - AUTOMATION_PROJECT_FORBIDDEN - AUTOMATION_PROJECT_SCOPE_REQUIRED - AUTOMATION_TRIGGER_INVALID - AUTOMATION_VERSION_NOT_DEPLOYED - AUTOMATION_VERSION_UNKNOWN - BLOB_NOT_FOUND - BODY_CHUNK_MALFORMED - BODY_LENGTH_MISMATCH - BODY_TOO_LARGE - BROWSER_SESSION_NOT_FOUND - BUDGET_EXCEEDED - CHAT_MODEL_AMBIGUOUS - CHAT_MODEL_NOT_ON_PROVIDER - CHAT_MODEL_UNKNOWN - CHAT_PROVIDER_UNKNOWN - CHAT_THREAD_ARCHIVED - CHAT_THREAD_NOT_DIRECT - CHAT_TURN_IN_PROGRESS - CHAT_TURN_NOT_RUNNING - CONTACT_AMBIGUOUS - CONTACT_DUPLICATE_EMAIL - CONTACT_DUPLICATE_EXTERNAL_ID - CONTACT_IDENTITY_REQUIRED - CONTACT_NOT_FOUND - CONTACT_STALE - CONVERSATION_CLOSED - CONVERSATION_CONTACT_CONFLICT - CONVERSATION_CONTACT_TRASHED - CONVERSATION_NOT_FOUND - CONVERSATION_SNAPSHOT_CONFLICT - CONVERSATION_SOURCE_NOT_FOUND - DATABASE_UNAVAILABLE - DELIVERY_NOT_FOUND - DELIVERY_RECEIPT_CONFLICT - DELIVERY_RETRY_UNAVAILABLE - DELIVERY_UNACKNOWLEDGED - DOCUMENT_HAS_KNOWLEDGE_ENTRY - DOCUMENT_NOT_FOUND - DOCUMENT_RECORD_FROZEN - DOCUMENT_RECORD_PROTECTED - DOCUMENT_RECORD_REPLACEMENT_REQUIRED - DOCUMENT_STALE - DUPLICATE_PRODUCT_EXTERNAL_ID - DUPLICATE_PRODUCT_NAME - EMBEDDING_CREDENTIAL_REJECTED - EMBEDDING_CREDIT_EXHAUSTED - EMBEDDING_NOT_CONFIGURED - EMBEDDING_UPSTREAM_ERROR - EMPTY_ANSWER - FILE_NOT_FOUND - FILE_SIZE_INVALID - FILE_TOO_LARGE - FOLDER_DEPTH_EXCEEDED - FOLDER_NAME_INVALID - FOLDER_NAME_TAKEN - FOLDER_NOT_ACCESSIBLE - FOLDER_NOT_FOUND - FORBIDDEN_DEPLOYMENT_EDITOR - FORBIDDEN_INSTANCE_ADMIN - HTTP_ERROR - HUMAN_ASK_EXPIRED - HUMAN_ASK_NOT_FOUND - HUMAN_ASK_NOT_PENDING - IDEMPOTENCY_KEY_REUSED - INTEGRATION_NOT_OWNED - INTERNAL_ERROR - INVALID_BODY - INVALID_CURSOR - INVALID_HEADER - INVALID_LIMIT - INVALID_QUERY - INVALID_SESSION - INVALID_SKILL - INVALID_SKILL_SLUG - INVALID_URL - KNOWLEDGE_ENTRY_DUPLICATE - KNOWLEDGE_ENTRY_FORBIDDEN - KNOWLEDGE_ENTRY_NOT_FOUND - KNOWLEDGE_ENTRY_STORE_TIMEOUT - KNOWLEDGE_ENTRY_SUPERSEDED - LEGAL_HOLD_ACTIVE - MENTION_DIRECTORY_UNAVAILABLE - MESSAGE_NOT_FOUND - METHOD_NOT_ALLOWED - MODEL_API_CONCURRENCY_EXCEEDED - MODEL_API_DISABLED - MODEL_API_FORBIDDEN - MODEL_API_GUARDRAIL_BLOCKED - MODEL_API_GUARDRAIL_UNAVAILABLE - MODEL_API_GUARDRAIL_UNSUPPORTED - MODEL_API_MODEL_FORBIDDEN - MODEL_API_MODEL_UNKNOWN - MODEL_API_TEXT_TOO_LARGE - MODEL_API_TOOLS_UNSUPPORTED - MODEL_API_UNAVAILABLE - MODEL_API_UPSTREAM_ERROR - MODEL_API_VENDOR_TOOL_UNSUPPORTED - MODEL_API_VISION_UNSUPPORTED - NOT_FOUND - OBJECT_STORE_UNAVAILABLE - OBJECT_STORE_UNCONFIGURED - ORG_FORBIDDEN - ORG_SLUG_INVALID - ORG_SLUG_REQUIRED - ORIGIN_FORBIDDEN - PRECONDITION_FAILED - PRODUCT_NOT_FOUND - PRODUCT_STALE - PROJECT_AGENT_CONNECTOR_UNKNOWN - PROJECT_AGENT_HARNESS_INVALID - PROJECT_AGENT_LIMIT - PROJECT_AGENT_MANAGED - PROJECT_AGENT_MODEL_INVALID - PROJECT_AGENT_NAME_TAKEN - PROJECT_AGENT_NOT_FOUND - PROJECT_AGENT_PROVIDER_UNKNOWN - PROJECT_AGENT_SECRETS_FORBIDDEN - PROJECT_AGENT_SECRET_UNKNOWN - PROJECT_AGENT_SKILL_UNKNOWN - PROJECT_AGENT_STALE - PROJECT_AGENT_TOOL_UNKNOWN - PROJECT_ARCHIVED - PROJECT_DUPLICATE_EXTERNAL_ID - PROJECT_FORBIDDEN - PROJECT_HAS_BOUND_AUTOMATIONS - PROJECT_HAS_PROTECTED_RECORDS - PROJECT_KEY_INVALID - PROJECT_KEY_TAKEN - PROJECT_LEGAL_HOLD - PROJECT_NOT_FOUND - PROJECT_SHARING_INVALID - PURGE_INCOMPLETE - RATE_LIMITED - RBAC_FORBIDDEN - REQUEST_TIMEOUT - REVIEW_COMPETENCE_REQUIRED - REVIEW_INDEPENDENT_REVIEWER_REQUIRED - ROLE_FORBIDDEN - RUN_ACTIVE - RUN_NOT_FOUND - RUN_QUARANTINED - RUN_QUARANTINE_CHANGED - SETUP_FOLDER_MISSING - SKILL_EXISTS - SKILL_FILE_NOT_FOUND - SKILL_FORBIDDEN - SKILL_MALFORMED - SKILL_NOT_FOUND - SKILL_PUBLISH_FORBIDDEN - SKILL_STALE - SKILL_TEAM_UNKNOWN - STANDARD_AGENT_OFF - STANDARD_AGENT_UNAVAILABLE - TASK_AGENT_REVIEW_REQUIRED - TASK_ARCHIVED - TASK_AUTOMATION_DISABLED - TASK_AUTOMATION_UNAVAILABLE - TASK_EXTERNAL_REF_INVALID - TASK_EXTERNAL_STATUS_STALE - TASK_HAS_LIVE_RUN - TASK_HAS_OPEN_SUBTASKS - TASK_LABEL_UNKNOWN - TASK_NOT_FOUND - TASK_NOT_IN_REVIEW - TASK_REVIEW_POLICY_UNAVAILABLE - TASK_STATUS_CONFLICT - TEAM_ACCESS_DENIED - TEAM_INHERITED_FROM_FOLDER - TEAM_NOT_IN_ORG - THREAD_NOT_FOUND - UNAUTHORIZED - UNSUPPORTED_FILE_TYPE - UPLOAD_FILE_MISMATCH - UPLOAD_INTENT_INVALID - UPLOAD_POLICY_REJECTED - UPSTREAM_UNAVAILABLE - URI_TOO_LONG - WEBSITE_DOMAIN_IMMUTABLE - WEBSITE_DOMAIN_INVALID - WEBSITE_DOMAIN_NOT_CRAWLABLE - WEBSITE_DUPLICATE_DOMAIN - WEBSITE_INVALID_LIST_URL - WEBSITE_NOT_FOUND description: 'A stable refusal code — every refusal the door itself makes carries one, and so does every domain refusal it lets through. The set is additive: a new code is a minor change, so treat a value you do not know as a generic refusal of the status you got.' requestId: type: string description: The response’s `X-Request-Id`, repeated on a 429, on a 500, on a body-size 413 and on a URL-size 414 so a caller can quote it data: type: object properties: retryAfterMs: type: number description: Wait in milliseconds for RATE_LIMITED providers: type: array items: type: string description: For CHAT_MODEL_AMBIGUOUS, the provider slugs that serve the model — name one as `providerSlug` lastMessageId: type: string description: 'For CHAT_TURN_NOT_RUNNING, the newest assistant message on the thread (once it has one) — the idle poll’s own disambiguation: equal to the `messageId` a 202 named means that turn already settled' lastStatus: type: string enum: - pending - complete - failed - cancelled description: For CHAT_TURN_NOT_RUNNING, how `lastMessageId` settled (present with it) organizations: type: array description: For ORG_SLUG_REQUIRED, ORG_SLUG_INVALID and ORG_FORBIDDEN, the organizations the key holder belongs to (a disabled membership is left out; the list is empty when none remains) — send one of the slugs as `X-Organization-Slug` items: type: object required: - slug - name properties: slug: type: string name: type: string issues: type: array description: For INVALID_BODY and INVALID_QUERY, every problem the schema found in the body or the query string; for INVALID_LIMIT and INVALID_CURSOR, the one parameter refused (`limit`, `cursor`); for AUTOMATION_INPUT_INVALID, every problem the automation’s inputs schema found in the run input (at most 20 either way), each naming the field and the reason; `error` repeats the first one items: type: object required: - path - message properties: path: type: string description: The field, dotted for nesting (`address.city`, `contacts.2.email`); empty for a problem with the body as a whole message: type: string scope: type: string enum: - user - team - project - org - apiKey description: 'For BUDGET_EXCEEDED, whose cap is reached: the key holder’s own (`user`), one of their teams’ (`team`), that of the project the work belongs to (`project`), the organization’s (`org`) or this API key’s (`apiKey`)' period: type: string enum: - daily - weekly - monthly description: For BUDGET_EXCEEDED, what the cap counts over — a calendar day, ISO week or calendar month in UTC limitCode: type: string enum: - TOKEN_LIMIT - COST_LIMIT - REQUEST_LIMIT description: 'For BUDGET_EXCEEDED, which cap is reached: tokens, cost in cents, or requests' used: type: number description: For BUDGET_EXCEEDED, the usage the cap measured this period, in its unit (tokens, cents or requests) limit: type: number description: For BUDGET_EXCEEDED, the cap, in the same unit resetsAt: type: number description: For BUDGET_EXCEEDED, when the period resets, in epoch milliseconds — `Retry-After` names the same wait in whole seconds ApprovalPart: type: object required: - type - approvalId - question properties: type: type: string enum: - approval approvalId: type: string question: type: string decision: type: string enum: - approved - rejected Thread: type: object required: - id - kind - archived - createdAt - updatedAt - generating properties: id: type: string title: type: string kind: type: string harness: type: string description: The sandbox harness a thread the app started runs on (`claude-code`, …) — present only on such a thread; the direct threads this surface creates never carry it projectId: type: string description: Present only for a thread returned through its project URL archived: type: boolean archivedAt: type: integer minimum: 0 description: Epoch ms of the archive — present on an archived thread whose archive the deployment recorded; `updatedAt` does not move on archive or restore isShared: type: boolean description: Whether the owner’s share link for the thread is live — present once the thread has been shared or unshared in the app, absent on one that never was generating: type: boolean createdAt: type: integer minimum: 0 description: Epoch milliseconds updatedAt: type: integer minimum: 0 description: Epoch ms of the last message activity — archiving, restoring and renaming leave it untouched MessagePart: description: 'One ordered piece of a message, discriminated by `type`: the `mapping` names the schema each kind validates against (`TextPart` … `ApprovalPart`), so a discriminator-honouring validator resolves every part. The kinds listed are the vocabulary as of this version; the set is additive, so a client renders a kind it does not know as opaque.' discriminator: propertyName: type mapping: text: '#/components/schemas/TextPart' reasoning: '#/components/schemas/ReasoningPart' attachment: '#/components/schemas/AttachmentPart' tool-call: '#/components/schemas/ToolCallPart' tool-result: '#/components/schemas/ToolResultPart' approval: '#/components/schemas/ApprovalPart' oneOf: - $ref: '#/components/schemas/TextPart' - $ref: '#/components/schemas/ReasoningPart' - $ref: '#/components/schemas/AttachmentPart' - $ref: '#/components/schemas/ToolCallPart' - $ref: '#/components/schemas/ToolResultPart' - $ref: '#/components/schemas/ApprovalPart' ToolResultPart: type: object required: - type - callId - capabilityId - output - structured properties: type: type: string enum: - tool-result callId: type: string capabilityId: type: string output: {} structured: type: boolean description: False when the capability declares no output schema Message: type: object required: - id - role - parts - sequence - status - createdAt properties: id: type: string role: type: string enum: - user - assistant parts: type: array items: $ref: '#/components/schemas/MessagePart' description: The message parts, in order sequence: type: integer description: The paging key status: type: string enum: - pending - complete - failed - cancelled description: '`pending` is the placeholder a running turn fills in (the row GET …/generation names as messageId); `complete` is a settled reply — a reply the output cap cut short included, so read `finishReason` for that; `cancelled` is a turn stopped through DELETE …/generation — `parts` and `usage` hold what had streamed; `failed` carries `error`, and `usage` when the turn had consumed any.' finishReason: type: string enum: - stop - length - tool-calls - content-filter - cancelled - other description: 'Why the turn stopped, on a settled assistant message, in one vocabulary over every provider: `stop` (a natural end), `length` (the `maxOutputTokens` cap or the model’s own ceiling cut a round of this reply short — the final text, or, in a tool-calling turn, the tool calls of an earlier round: every call of that round is withheld, its tool-result reads `status: "invalid_args"` — the message saying whether its arguments were cut or complete — and the final text may be whole; the status still reads `complete`), `tool-calls` (the final round ended on tool calls), `content-filter` (the provider’s or the platform’s filter), `cancelled` (stopped through DELETE …/generation), `other` (a reason the provider named that has no bucket here). Absent when the provider reported none.' model: type: string providerSlug: type: string blockedReason: type: string error: type: string description: Why a failed turn produced no reply errorCode: type: string enum: - missing_api_key - credit_exhausted - model_not_entitled - auth_error - model_not_found - unsupported_parameter - output_cap_too_high - token_limit - rate_limited - content_filter - context_length - tool_failure - provider_unreachable - provider_error - thread_busy - budget_exceeded - generic description: Stable chat error classification when available. `credit_exhausted` and `model_not_entitled` mean the provider account, not the request; neither is `rate_limited`. `budget_exceeded` means a budget cap that binds the key holder was reached after the send was accepted, so the turn never ran; a send made once a cap is reached is refused up front with 429 `BUDGET_EXCEEDED`. usage: type: object additionalProperties: false description: The token counters the finished turn recorded and the catalog cost estimate stamped beside them; absent until the turn settles. A failed turn carries what it consumed — its finished model rounds and, if the provider had already accepted the request, the round that failed; a round the provider refused (an HTTP error status, or a refusal such as a rate limit reported before any answer or positive reported usage) adds nothing, so a turn refused on its first round carries none. Counts are the provider’s own unless `estimated` is present. properties: inputTokens: type: integer description: Prompt tokens — the assistant’s instructions and tool schemas included outputTokens: type: integer description: Reply tokens, the reasoning share included cachedInputTokens: type: integer description: The share of `inputTokens` the provider served from its prompt cache reasoningTokens: type: integer description: The share of `outputTokens` spent thinking before the reply — absent when the provider reported no count (a `0` is a reported zero) costEstimateCents: type: number minimum: 0 description: The turn’s cost in US cents (fractional) from the catalog price the organization ledger also books — the same figure as the ledger; absent when the catalog publishes no price for the model. Cached input is priced at the input rate, so on a cached turn the figure is an upper bound; estimated when the provider’s count was lost (`estimated`). estimated: type: boolean enum: - true description: Present when the counts are the platform’s own estimate — the provider’s usage frame was lost (a cancelled turn, or one that failed mid-stream, typically) or never sent — so the cost is an estimate too stepLimitHit: type: boolean enum: - true description: Present when the tool loop spent its whole round budget and the final round had tools withheld — the answer may have been forced createdAt: type: integer minimum: 0 description: Epoch ms — when the row was created (for an assistant row, when its turn started) TextPart: type: object required: - type - text properties: type: type: string enum: - text text: type: string headers: XTaleApiVersion: description: The version of this contract (`info.version`) the serving instance implements — semver for the wire schema: type: string example: 3.27.0 Location: description: The created resource’s own path, relative to the request URL — follow it with GET whatever shape the 201 body has schema: type: string Allow: description: The methods this path serves, comma-separated schema: type: string example: GET, POST, HEAD, OPTIONS RetryAfter: description: How long to wait before retrying, in whole seconds schema: type: integer minimum: 1 WWWAuthenticate: description: 'The challenge RFC 9110 §11.6.1 requires of a 401: `Bearer`, or `Bearer error="invalid_token"` when a key was presented and refused' schema: type: string ETag: description: 'A validator: over the answer’s bytes on a JSON read, the stored object’s tag on a download. Send it back as `If-None-Match` — as received; the weak and edge-suffixed forms match too — to be told 304 when nothing changed.' schema: type: string CacheControl: description: '`private, no-cache` on a validated read and on a download: keep the answer, revalidate it before reuse, and let no shared cache keep it. Every other answer says `no-store`.' schema: type: string XRequestId: description: The request id the platform logged this call under — quote it when reporting a problem. Echoes a sane `X-Request-Id` the caller sent. schema: type: string securitySchemes: bearerAuth: type: http scheme: bearer description: An organization API key. Create one in Settings → API. Pasted into the explorer on /docs it stays in the page’s memory only and is forgotten on reload — it is never stored.