Messages
Anthropic Messages API compatibility, Claude Code setup, tools, streaming, caching, and errors
Sansa implements the Anthropic Messages API used by Claude Code and the Anthropic SDKs. Requests, responses, streaming, and errors all use Anthropic's format.
POST /v1/messages
The endpoint is stateless. Send the complete conversation in messages on every request. Claude Code does this automatically.
See the code panel for complete Claude Code, curl, TypeScript, and Python examples.
For direct SDK use, install @anthropic-ai/sdk for TypeScript or anthropic for Python.
Claude Code quick start
Create a Sansa API key in your dashboard, then start Claude Code from a shell with these variables:
export SANSA_API_KEY="sk-sansa-..."
export ANTHROPIC_BASE_URL="https://api.sansaml.com"
export ANTHROPIC_AUTH_TOKEN="$SANSA_API_KEY"
claudeThe base URL must be https://api.sansaml.com without /v1. Claude Code appends /v1/messages itself.
Inside Claude Code:
- Run
/statusand confirm the Anthropic base URL ishttps://api.sansaml.com. - Send a test prompt.
- If you use extended thinking across turns, select and keep one explicit Claude model with
/model; see Model-bound thinking.
ANTHROPIC_AUTH_TOKEN sends the Sansa key as Authorization: Bearer .... You may instead set ANTHROPIC_API_KEY, which sends it as x-api-key: .... Sansa supports both.
To persist the configuration, put the variables in the env object in your user-level ~/.claude/settings.json. Do not commit a key in a project's shared .claude/settings.json. For rotating credentials, prefer Claude Code's apiKeyHelper.
Claude Code gateway model discovery is optional:
export CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1At startup, Claude Code then calls GET /v1/models?limit=1000 and adds the returned Claude models to /model. Discovery is off by default.
For Claude Code's gateway behavior, see Anthropic's gateway connection guide and gateway protocol reference.
Endpoint map
Claude Code may call several endpoints around the main inference request:
| Method and path | Authentication | Behavior |
|---|---|---|
POST /v1/messages | Sansa key; messages scope | Creates or streams a Message |
POST /v1/messages/count_tokens | Sansa key; messages scope | Returns 404; see Token counting |
HEAD /api/hello | None | Returns 204 for Claude Code's optional connection-warming probe |
GET /v1/models?limit=1000 | Sansa key | Returns Claude model IDs for opt-in discovery; free |
GET /v1/mcp_servers?limit=1000 | Sansa key; messages scope | Returns an empty list. MCP servers configured in Claude Code are unaffected |
Claude Code sends inference to /v1/messages?beta=true; the query string is optional for direct calls.
Authentication and API key scope
Use either credential form:
Authorization: Bearer sk-sansa-...x-api-key: sk-sansa-...If both are sent, they must hold the same key; conflicting or duplicated credential headers return 401.
The messages endpoint scope
If an API key has an endpoint allowlist, it must include the exact slug messages. A key restricted to chat.completions does not imply access to Messages.
The messages scope covers:
POST /v1/messagesPOST /v1/messages/count_tokensGET /v1/mcp_servers
An API key with an empty endpoint allowlist is unrestricted and can call Messages. Normal model allowlists, rate limits, balances, spend caps, and abuse checks still apply.
Required headers
The two Messages POST endpoints require all headers below. GET /v1/mcp_servers requires the credential and anthropic-version, but has no JSON body or Content-Type.
| Header | Required value | Notes |
|---|---|---|
Content-Type | application/json | Required on POSTs; parameters such as ; charset=utf-8 are allowed |
anthropic-version | 2023-06-01 | Required on Messages, token count, and MCP; other versions fail with guidance |
Authorization or x-api-key | Your Sansa key | Required on Messages, token count, MCP, and model discovery |
anthropic-beta | Optional, comma-separated beta IDs | Required only by the beta capability being requested |
Content-Type, anthropic-version, anthropic-beta, and the session header must each appear once.
Beta headers
anthropic-beta and other anthropic-* headers are passed on in order. A beta header doesn't enable a body field Sansa doesn't support; such a field fails with capability_rejected: guidance.
Claude Code sends the beta headers its requests need. Direct callers send the current beta for each experimental feature they use (the SDKs take them as betas); see Anthropic's beta-header reference.
Claude Code session header
Claude Code sends x-claude-code-session-id. Sansa trims it, requires a non-empty value of at most 256 characters, and uses it to keep the conversation on the same model so its prompt cache stays warm.
This header is not conversation storage. It does not let you omit earlier messages.
Request body
The minimal request is:
{
"model": "claude-sonnet-5",
"max_tokens": 1024,
"messages": [
{
"role": "user",
"content": "Explain why the sky is blue in two sentences."
}
]
}Core parameters
On sansa-auto, some fields return 400; see What sansa-auto accepts.
| Field | Required | Accepted values and behavior |
|---|---|---|
model | Yes | Non-empty Claude alias, any available Sansa catalog model ID, or sansa-auto |
max_tokens | Yes | Integer of at least 1; includes visible and thinking output |
messages | Yes | Non-empty ordered array, up to 100,000 messages |
system | No | String or a non-empty ordered array of text blocks |
stream | No | Boolean; default false |
temperature | No | Number from 0 through 1 |
top_p | No | Number from 0 through 1 |
top_k | No | Non-negative integer |
stop_sequences | No | Up to four strings |
tools | No | Custom or supported Anthropic-hosted tool definitions |
tool_choice | No | auto, any, none, or a named tool choice |
thinking | No | disabled, adaptive, or manually enabled |
output_config | No | Effort, JSON Schema output, and supported task-budget controls |
cache_control | No | Anthropic ephemeral cache control |
context_management | No | Supported native context edits; see below |
diagnostics | No | Best-effort prompt-cache diagnosis; requires its beta |
metadata | No | Object containing only user_id, as a string or null |
service_tier | No | auto or standard_only |
speed | No | standard or supported fast mode |
max_tokens: 0 (prompt-cache prewarming) isn't supported. Send at least 1.
Strict JSON and unknown fields
Requests are parsed as strict JSON:
- Duplicate object keys,
NaN, andInfinityare rejected. - Unknown fields are rejected with a message naming their path.
- Invalid values are rejected, never reinterpreted.
Custom tool inputs, JSON Schemas, citation fields, and server-tool results are passed through as sent. Every other field you send is either used or rejected.
metadata may contain only user_id, a string or null.
Model selection
Messages accepts Claude Code's common names as well as Sansa catalog IDs.
| Request value | Behavior |
|---|---|
claude-opus-5, claude-sonnet-5, claude-fable-5 | Maps to the corresponding live Sansa Claude model |
opus, sonnet, haiku, fable | Claude Code-style aliases |
An undated version such as claude-opus-4-8 | Maps to the corresponding catalog form, such as anthropic/claude-opus-4.8 |
A dated snapshot ID Sansa lists, such as claude-sonnet-4-5-20250929 | Maps to that release; other dated IDs return 400 |
An ID from GET /v1/models?limit=1000 | Calls that Claude catalog model directly |
| Any available Sansa catalog model ID | Calls that model directly |
sansa-auto | Lets Sansa route the request |
Unknown IDs return 400. Discovery returns only Claude models; the Models page lists the full catalog.
The response's model echoes the value you sent; on sansa-auto, sansa.routed_model names the model that served it.
Fast mode
speed: "fast" is supported for Claude Opus 5 and Claude Opus 4.8. Claude Opus 4.6 follows Anthropic's standard-speed fallback behavior. Other model and speed combinations return 400.
Model-bound thinking
Thinking signatures only work on the model that produced them, so sansa-auto rejects a conversation that replays thinking or redacted_thinking blocks.
For extended thinking across turns, including tool flows with signed thinking in their history, keep one explicit Claude model for the whole conversation. A plain tool_use / tool_result round trip without signed thinking can use sansa-auto.
Messages and content blocks
messages is an ordered transcript. Include prior assistant content and tool results exactly as returned.
Roles
| Role | Content |
|---|---|
user | String, or text, image, and tool_result blocks |
assistant | String, or text, thinking, tool-use, and current server-tool result blocks |
system | Claude Code's beta inline system turn; string or ordered text blocks only |
Top-level system is also supported as a string or ordered text-block array.
User content
Text:
{
"type": "text",
"text": "What is in this image?",
"cache_control": { "type": "ephemeral" }
}Image by URL:
{
"type": "image",
"source": {
"type": "url",
"url": "https://example.com/photo.jpg"
}
}Image by base64:
{
"type": "image",
"source": {
"type": "base64",
"media_type": "image/png",
"data": "iVBORw0KGgo..."
}
}Supported base64 media types are image/jpeg, image/png, image/gif, and image/webp.
document blocks aren't supported; send extracted text or an image instead.
Assistant history
Assistant content can replay:
text, including citation metadata and cache control- Signed
thinking - Opaque
redacted_thinking - Client
tool_use server_tool_useandmcp_tool_use- Current advisor, code-execution, MCP, text-editor, tool-search, web-fetch, and web-search result blocks
Citation objects on replayed text must have a non-empty type.
Tool results
Return a client tool result in a user message:
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_abc123",
"content": "{\"temperature\":22,\"conditions\":\"sunny\"}",
"is_error": false
}
]
}tool_use_id must be the exact non-empty ID from the assistant's tool_use block. content may be null, a string, or an array of text, image, and supported tool_reference blocks. is_error, toolset_name, and cache_control are supported.
Custom tools
Custom tools use Anthropic's flat tool shape:
{
"name": "get_weather",
"description": "Get current weather for a location",
"input_schema": {
"type": "object",
"properties": {
"location": { "type": "string" }
},
"required": ["location"]
}
}Supported custom-tool fields are:
name,description, andinput_schemastrictcache_controldefer_loadingeager_input_streaminginput_examplesallowed_callers
Tool names must be unique within one request.
Tool choice
| Anthropic value | Behavior |
|---|---|
{"type":"auto"} | Model decides whether to call a tool |
{"type":"any"} | Model must call one of the supplied tools |
{"type":"none"} | Tool calls are disabled |
{"type":"tool","name":"get_weather"} | Force the named supplied tool |
Each object may include disable_parallel_tool_use. any requires at least one tool, and a named choice must match a supplied tool. Forced tool use is incompatible with manually budgeted extended thinking; use adaptive thinking or an automatic/none tool choice.
Tool round trip
- Send
toolswith the user message. - Read the assistant's
tool_useblock and preserve itsid,name, andinput. - Execute the tool in your application.
- Append the complete assistant response to the transcript.
- Append a user
tool_resultwhosetool_use_idmatches the call. - Send the full transcript and the tool definitions again.
Do not send only the tool result. The assistant tool-use block is part of the conversation state and is needed for correlation and thinking continuity.
Anthropic-hosted tools
Supported hosted tools:
| Family | Accepted versions |
|---|---|
| Web search | web_search_20250305, web_search_20260209, web_search_20260318 |
| Web fetch | web_fetch_20250910, web_fetch_20260209, web_fetch_20260309, web_fetch_20260318 |
| Code execution | code_execution_20250522, code_execution_20250825, code_execution_20260120, code_execution_20260521 |
| Tool search | Regex and BM25 types, including the 20251119 versions |
| Advisor | advisor_20260301 |
Limits:
- Every web-search definition needs
max_usesfrom 1 through 100. - Advisor needs
max_usesfrom 1 through 100 and an available advisor model. Optional advisormax_tokensmust be at least 1024 and within that model's limit. - Standalone hosted code execution isn't available. Use Claude Code's client-side Bash or custom tools, or web search and web fetch.
mcp_toolsetisn't available. Keep MCP servers configured in Claude Code.
Only successful web searches are charged.
Thinking and output controls
Thinking modes
{ "thinking": { "type": "disabled" } }{
"thinking": { "type": "adaptive", "display": "summarized" },
"output_config": { "effort": "high" }
}{
"thinking": {
"type": "enabled",
"budget_tokens": 4096,
"display": "summarized"
}
}| Mode | Rules |
|---|---|
disabled | Cannot be combined with output_config.effort |
adaptive | May use effort; display may be summarized, omitted, or the current beta value updates |
enabled | budget_tokens must be at least 1024 and strictly lower than max_tokens; cannot be combined with effort |
The effort values are low, medium, high, xhigh, and max; anything else returns 400.
If max_tokens is capped, the thinking budget is lowered to fit. When no valid budget fits, the request fails with guidance.
Structured output
Use output_config.format:
{
"output_config": {
"format": {
"type": "json_schema",
"name": "weather",
"strict": true,
"schema": {
"type": "object",
"properties": {
"temperature": { "type": "number" },
"conditions": { "type": "string" }
},
"required": ["temperature", "conditions"],
"additionalProperties": false
}
}
}
}schema must be a JSON Schema object. name and strict are optional; the typed Anthropic SDK doesn't include them, so omit them there.
output_config.task_budget must use type: "tokens", total of at least 20,000, and optional remaining from 0 through total.
Prompt caching and sticky routing
Add ephemeral cache control at supported breakpoints:
{
"type": "text",
"text": "A long stable system prompt...",
"cache_control": {
"type": "ephemeral",
"ttl": "1h",
"scope": "global"
}
}Rules:
typemust beephemeral.ttlmay be omitted,5m, or1h. Omitted means the default five-minute cache.scope, when supplied, must beglobal.- Cache control is supported at the top level and on relevant system, content, tool, and tool-result objects.
System blocks and cache markers keep their order, so the cached prefix doesn't change. Usage reports cache reads and writes in cache_creation_input_tokens and cache_read_input_tokens.
The session header doesn't replace cache markers and doesn't store the transcript.
Cache diagnostics
With the cache-diagnosis-2026-04-07 beta, diagnostics is accepted. On the first turn (previous_message_id: null) it returns "diagnostics": null; on later turns it returns {"cache_miss_reason":{"type":"unavailable"}}, since cache-miss comparison isn't available.
Context management
Supported edits:
clear_thinking_20251015clear_tool_uses_20250919
Their fields are validated; unknown edit types are rejected.
Server-side compaction isn't available. Keep conversation compaction in Claude Code, or send a shorter complete transcript.
Response object
A non-streaming request returns an Anthropic Message:
interface MessageResponse {
id: string; // "msg_..."
type: "message";
role: "assistant";
model: string; // echoes the model value from your request
content: Array<Record<string, unknown>>;
stop_reason: string;
stop_sequence: string | null;
usage: {
input_tokens: number;
output_tokens: number;
cache_creation_input_tokens?: number;
cache_read_input_tokens?: number;
[key: string]: unknown; // native usage extensions
};
sansa?: {
routed: boolean;
routed_model?: string;
routing_latency_ms?: number;
max_tokens_capped_to?: number;
};
[key: string]: unknown; // safe native Anthropic response extensions
}The content array may contain text, thinking, redacted thinking, tool-use, and server-tool blocks. Treat it as an ordered open list and preserve blocks you send back on the next turn.
stop_reason may gain values over time. Handle known values such as end_turn, max_tokens, stop_sequence, and tool_use, but keep a safe default for future values.
Response integrity
Tool input, citations, and model-written content are returned unchanged. A malformed response from the model returns an error instead of a partial Message.
Cost
Messages does not include usage.cost or sansa.cost. View spend in your Sansa dashboard.
Streaming
Set stream: true. The response is a named Server-Sent Events stream:
event: message_start
data: {"type":"message_start","message":{...}}
event: content_block_start
data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Hello"}}
event: content_block_stop
data: {"type":"content_block_stop","index":0}
event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"output_tokens":3}}
event: message_stop
data: {"type":"message_stop"}Event sequence
| Event | Purpose |
|---|---|
message_start | Starts the Message and carries initial usage |
content_block_start | Opens one indexed content block |
content_block_delta | Adds text, thinking, signature, JSON, citation, or another native delta |
content_block_stop | Closes that indexed block |
message_delta | Carries final stop data and cumulative usage |
message_stop | Successful terminal event |
ping | Keepalive during an otherwise idle period |
error | Terminal failure after HTTP 200 has begun |
There is no [DONE] sentinel. Stop on message_stop or error.
Sansa sends a ping about every 15 seconds while the model is quiet. Proxies must not buffer SSE or strip pings: Claude Code aborts a stream that stays silent too long.
Claude Code turns off fine-grained tool-input streaming behind a custom ANTHROPIC_BASE_URL. Set CLAUDE_CODE_ENABLE_FINE_GRAINED_TOOL_STREAMING=1 to turn it on.
Mid-stream errors
Once HTTP 200 has started, status codes can no longer represent a failure. Sansa sends:
event: error
data: {"type":"error","error":{"type":"api_error","message":"..."},"request_id":"req_..."}error is terminal; no message_stop follows.
Raw SSE clients receive this frame directly. Anthropic's TypeScript and Python SDKs surface a streamed error as an exception/error callback rather than yielding it as an ordinary Message stream event, so catch the SDK's API error around stream consumption.
See Anthropic's streaming Messages reference for the client-side event model.
Token counting
POST /v1/messages/count_tokens validates the request, then returns 404 not_found_error: exact token counts aren't available. Claude Code falls back automatically; a direct SDK integration must handle the 404 itself.
Errors
Messages endpoints use Anthropic's error envelope:
{
"type": "error",
"error": {
"type": "invalid_request_error",
"message": "Missing required 'anthropic-version' header. Send 'anthropic-version: 2023-06-01'. Nothing was processed."
},
"request_id": "req_..."
}The request-id response header matches request_id.
| Status | Anthropic error type | Typical cause |
|---|---|---|
400 | invalid_request_error | Invalid JSON, missing version, bad field, unsupported capability |
401 | authentication_error | Missing, invalid, duplicated, or conflicting key |
402 | billing_error | Insufficient credits or a blocking spend cap |
403 | permission_error | Missing messages scope, model restriction, or abuse rejection |
404 | not_found_error | Optional token-count endpoint is unavailable, or a route does not exist |
413 | request_too_large | Request exceeds the accepted body size |
429 | rate_limit_error | Rate limited |
500 / 502 | api_error | Server error |
503 | overloaded_error or api_error | Temporarily unavailable |
504 | timeout_error | Timed out |
529 | overloaded_error | Temporarily overloaded |
An unsupported field's error starts with capability_rejected:, names the field's path, and says how to fix it.
Errors use Anthropic's error format, including status, wording, and Retry-After, so Claude Code handles them as usual. Responses carry request-id and x-sansa-request-id; include x-sansa-request-id when contacting support.
Do not retry 400, 401, 402, or 403 blindly. Correct the request, credential, balance, or key policy first. Retry rate limits, overloads, timeouts, and transient 5xx failures with exponential backoff and jitter.
Billing and usage
Sansa holds the request's maximum likely cost while it runs, then charges what it used:
- Input, output, cache-read, and cache-write tokens are priced separately; five-minute and one-hour cache writes each at their own rate.
- Advisor iterations and successful hosted web searches are included when present.
- Fast mode is billed at its own rate.
- A request rejected before the model runs isn't charged, and the hold is released. If a stream fails after the model ran, the usage up to that point is charged.
See Spend caps for limits.
Unsupported capabilities
These return 400:
| Capability | Instead |
|---|---|
Non-empty container | Not available |
inference_geo | Not available |
Non-empty top-level mcp_servers | Configure MCP in Claude Code |
Adjacent Anthropic products such as Message Batches, Files, managed MCP creation, and Containers are not implemented by this endpoint.
Troubleshooting Claude Code
/status does not show the Sansa base URL
Start claude from the same shell where you exported ANTHROPIC_BASE_URL, or put it in the user-level settings env object. Do not append /v1.
Claude Code opens a login screen
Set ANTHROPIC_AUTH_TOKEN or ANTHROPIC_API_KEY. A Sansa key is the gateway credential; a saved claude.ai login is not sent to Sansa.
401 authentication_error
Confirm the key is active. If you set both credential variables, ensure they resolve to the same key or unset one. Check for duplicated proxy-added credential headers.
403 permission_error
For a restricted key, add the messages endpoint scope and allow the selected model. A chat.completions-only key is not enough.
400 mentions anthropic-version
Send exactly anthropic-version: 2023-06-01. Current Anthropic SDKs and Claude Code add it automatically; raw HTTP callers must add it themselves.
400 mentions signed thinking and sansa-auto
Keep the same explicit Claude model for all turns that replay thinking or redacted_thinking blocks.
Models do not appear in /model
Set CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1, restart Claude Code, and make sure a gateway credential is available. Discovery is skipped when CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 or any CLAUDE_CODE_USE_* provider selector is active. An administrator's modelPicker.replaceBuiltInOptions policy can also hide discovered entries.
Discovery has a short timeout and does not follow redirects, so use the final HTTPS base URL directly.
A stream stalls
Disable response buffering for text/event-stream in your reverse proxy and allow keepalive ping events through. The client must consume bytes incrementally.
Migrating from Anthropic
For an existing Anthropic SDK integration, keep the Messages request and response handling. Change only the credential and base URL:
| Anthropic direct | Sansa |
|---|---|
https://api.anthropic.com | https://api.sansaml.com |
| Anthropic API key | Sansa API key |
| Anthropic account limits | Sansa credits, scopes, model policies, and spend caps |
| Direct model ID | Claude alias, any available Sansa catalog ID, or sansa-auto |
Do not point an Anthropic SDK at https://api.sansaml.com/v1; it appends /v1/messages itself.
Keep the full transcript, tool-use blocks, tool results, thinking signatures, cache controls, and beta headers intact. The code panel includes direct Anthropic SDK examples.