{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://raw.githubusercontent.com/api-evangelist/tale-dev/main/json-schema/tale-dev-message-schema.json", "title": "Message", "x-generated": "2026-10-09", "x-method": "derived", "x-generator": "derive-json-schema.py", "x-source": "openapi/tale-dev-openapi.yml#/components/schemas/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": "#/$defs/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)" } }, "$defs": { "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" ] } } }, "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" } } }, "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.", "oneOf": [ { "$ref": "#/$defs/TextPart" }, { "$ref": "#/$defs/ReasoningPart" }, { "$ref": "#/$defs/AttachmentPart" }, { "$ref": "#/$defs/ToolCallPart" }, { "$ref": "#/$defs/ToolResultPart" }, { "$ref": "#/$defs/ApprovalPart" } ] }, "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" } } }, "TextPart": { "type": "object", "required": [ "type", "text" ], "properties": { "type": { "type": "string", "enum": [ "text" ] }, "text": { "type": "string" } } }, "ToolCallPart": { "type": "object", "required": [ "type", "callId", "capabilityId", "input" ], "properties": { "type": { "type": "string", "enum": [ "tool-call" ] }, "callId": { "type": "string" }, "capabilityId": { "type": "string" }, "input": {} } }, "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" } } } } }