Messages
Anthropic Messages API compatibility, Claude Code setup, tools, streaming, caching, and errors
Sansa implements the Anthropic Messages API shape used by Claude Code and the Anthropic SDKs.
POST /v1/messages
This is a native Anthropic-format endpoint. Requests, responses, streaming events, and errors use Anthropic's wire format rather than the OpenAI Chat Completions or Responses formats.
The endpoint is stateless. Send the complete conversation in messages on every request. Claude Code does this automatically. Sansa does not store a conversation or resolve a previous message ID for you.
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. The code panel includes both install commands.
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 | Validates the request, then returns a guided 404; Claude Code falls back through inference |
HEAD /api/hello | None | Returns 204 for Claude Code's optional connection-warming probe |
GET /v1/models?limit=1000 | Sansa key | Returns live Claude model IDs for opt-in gateway discovery; no provider call and no charge |
GET /v1/mcp_servers?limit=1000 | Sansa key; messages scope | Returns {"data":[],"next_page":null}; Sansa does not create Anthropic-managed MCP connections |
The empty MCP response does not affect MCP servers configured locally or per project in Claude Code. Those remain managed by Claude Code.
The MCP probe accepts limit once as an integer from 1 through 1000 and an optional non-empty page cursor of at most 2,048 characters. Other or repeated query parameters fail with guidance.
Claude Code normally sends inference to /v1/messages?beta=true. Sansa accepts that exact compatibility marker. For direct API calls, the query string is optional. beta may appear once and, when present, must equal true; other query parameters are rejected.
Authentication and API key scope
Use either credential form:
Authorization: Bearer sk-sansa-...x-api-key: sk-sansa-...If both are present, they must contain the same key. Conflicting or duplicated credential headers return 401 rather than allowing header order to decide which identity is used.
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 |
Singleton headers must appear exactly once. Sansa rejects duplicate Content-Type, anthropic-version, anthropic-beta, and session headers instead of merging ambiguous values.
Beta and future Anthropic headers
On /v1/messages, Sansa treats the anthropic-* namespace as open:
- A valid
anthropic-betalist is preserved in its original order and forwarded. - Future singleton
anthropic-*headers are forwarded rather than removed by a fixed allowlist. - Authentication,
Content-Type, andAcceptare gateway-owned and cannot override the upstream transport.
Forwarding a beta header does not automatically implement an unknown request-body field. Body fields are validated separately; an unsupported field fails at its exact path with capability_rejected: guidance.
Claude Code supplies the beta headers that match the body fields it uses. Direct HTTP and SDK callers must supply the current beta for each selected experimental capability and keep the header/body pair together. For example, the currently tested fast-mode and task-budget headers are fast-mode-2026-02-01 and task-budgets-2026-03-13; cache diagnosis uses cache-diagnosis-2026-04-07. Beta names evolve, so check Anthropic's beta-header reference and the selected feature's documentation rather than freezing this example list. Anthropic SDK beta calls accept the identifiers through their betas parameter.
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 for sticky routing and provider cache affinity.
This header is not conversation storage. It does not let you omit earlier messages.
Other Claude Code attribution headers, such as agent and parent-agent IDs, are not Anthropic model capabilities and are not forwarded as request-body semantics. Do not depend on them reaching the model provider.
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
| 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, which Anthropic documents for prompt-cache prewarming, is intentionally rejected. Sansa's admission and settlement pipeline cannot safely promise a zero-output prewarm. Send at least 1.
Strict JSON and unknown fields
Sansa decodes the original request as strict RFC JSON:
- Duplicate object keys are rejected; last-key-wins behavior is never applied.
NaN,Infinity, and-Infinityare rejected.- Unknown caller-controlled top-level, message, block, tool, and nested semantic fields are rejected with a path-specific message.
- Invalid values are not coerced to another meaning.
Opaque/open JSON remains open where changing it would corrupt caller or provider data: custom tool input and JSON Schema objects, provider citation fields after a valid citation type, and validated server-tool result payloads. These objects are preserved rather than recursively allowlisted.
This endpoint has no broad “accepted and ignored” body-field bucket. A value is preserved through the native transport, explicitly consumed for gateway behavior, or rejected before provider work begins.
metadata may contain only user_id. A string participates in internal attribution; an explicit null remains in the native request but intentionally does not create an attribution value.
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 supported immutable snapshot ID | Maps only when Sansa has an explicit exact snapshot binding; arbitrary dates are never stripped |
An ID from GET /v1/models?limit=1000 | Calls that Claude catalog model directly |
| Any available model ID from the Sansa catalog | Calls that model directly through the native Messages transport |
sansa-auto | Lets Sansa route the request |
Unknown IDs return a guided 400. Claude Code discovery intentionally returns only live anthropic/claude... IDs, while the Models page lists the full Sansa catalog.
The public response's model field echoes the value you sent. Sansa does not replace it with a private provider identifier. For auto-routed calls, sansa.routed_model reports the selected catalog model when available.
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. Unsupported model/speed combinations fail before inference, and provider usage must confirm the requested tier before it is billed.
Model-bound thinking
Anthropic thinking signatures are bound to the model that produced them. For that reason, sansa-auto rejects a conversation that replays thinking or redacted_thinking assistant blocks: auto-routing could select a different model and silently invalidate the signature.
For multi-turn extended-thinking continuity—and tool flows whose assistant history includes signed thinking—pin the same explicit Claude model for the whole conversation. A plain tool_use / tool_result round trip without signed thinking can use sansa-auto. Sansa preserves signed and encrypted blocks; it never removes them to make an auto-routed request pass.
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. Block order and cache markers are preserved.
Claude Code places an attribution block first in its system array. Sansa retains that position and does not merge the array into one string, which keeps attribution and prompt-cache behavior consistent with the client request.
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.
Anthropic document blocks are not supported on this endpoint. Send extracted text or a supported image instead; the block is rejected rather than discarded.
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. Their fields stay in the native history; Sansa does not flatten cited text into ordinary text or remove citation metadata.
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 preserved when supplied.
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
Sansa's native Messages transport currently validates and preserves these hosted tool families:
| 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 |
The exact native definitions, server calls, results, citations, and usage fields are preserved. Gateway-specific safety rules keep spend bounded before any provider work:
- 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 is rejected because its runtime charge cannot be settled faithfully. Use Claude Code's client-side Bash/custom tools, or a qualifying current web-search/web-fetch companion for which Anthropic does not add a separate execution charge.
mcp_toolsetis rejected because Sansa does not create Anthropic-managed MCP connections. Keep MCP servers configured in Claude Code.
A web search is charged only when a successful correlated result is reported. Failed or unmatched searches are not counted as completed paid searches.
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 Anthropic SDK-compatible effort values are low, medium, high, xhigh, and max. Sansa's raw JSON endpoint additionally accepts minimal; current Anthropic SDK type definitions do not include that extension. Internally, max is the highest effort tier.
If routing or an abuse-control token clamp lowers max_tokens, Sansa reconciles the thinking budget before provider dispatch. If it cannot retain a valid minimum budget, the request fails with guidance instead of sending an internally inconsistent body.
Structured output
Use output_config.format. This raw-JSON example includes Sansa's optional name and strict extensions; omit those two fields when using the current typed Anthropic SDK:
{
"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. Sansa's raw JSON surface also accepts optional name and strict fields and forwards them when supplied. Current Anthropic SDK JSONOutputFormat types expose only type and schema, so omit the extensions for typed SDK compatibility. If name or strict is omitted, it remains omitted in the preserved native body.
output_config.task_budget is also preserved for the native task-budget capability. It 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.
Sansa preserves ordered system blocks and cache markers because merging or reordering them would change the prompt cache key. Native usage reports cache reads and writes through fields such as cache_creation_input_tokens, cache_read_input_tokens, and any provider-supported cache-creation breakdown.
Claude Code's x-claude-code-session-id is mapped to provider session_id for sticky routing. It helps keep a session on a compatible provider cache path, but it does not replace cache markers and does not store the transcript.
Cache diagnostics
Cache diagnosis is best effort and separate from sticky routing:
anthropic-beta: cache-diagnosis-2026-04-07{
"diagnostics": {
"previous_message_id": null
}
}Behavior:
- On the first turn,
previous_message_id: nullreturns"diagnostics": null. - A real prior message ID must be non-empty and at most 256 characters.
- Because Sansa's configured upstream has no Anthropic fingerprint-comparison service, a real ID returns
{"cache_miss_reason":{"type":"unavailable"}}.
previous_message_id is never reinterpreted as a session ID, cache key, or provider-affinity hint. The diagnostics object is consumed by Sansa rather than forwarded with altered semantics.
Context management
The native transport currently supports:
clear_thinking_20251015clear_tool_uses_20250919
Their recognized fields are validated and preserved for the provider. Unknown edit types are rejected.
Server-side compaction is not available because its separate usage cannot yet be settled without loss. Keep conversation compaction in Claude Code, or send a shorter complete transcript.
Response object
A non-streaming request returns the provider-native Anthropic Message with gateway-private provider fields removed:
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 is provider-native and 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
Sansa removes only provider-private envelope fields and private cost details. It does not recursively strip identically named keys inside tool input, citations, or model-authored content.
A response fails closed if the provider returns:
- A different model than the one Sansa dispatched
- Missing or malformed required Message fields
- Duplicate JSON keys or non-finite numbers
- Invalid usage accounting
- A reserved top-level
sansafield that would collide with gateway metadata
/v1/messages, /v1/messages/count_tokens, and /v1/mcp_servers responses have a request-id header.
Cost
Messages does not add usage.cost or sansa.cost. Provider cost fields are private and are removed from the public Anthropic response. View settled 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 emits a ping about every 15 seconds during provider silence. Proxies must not buffer SSE or strip pings; Claude Code aborts a stream that remains silent for too long.
The stream accumulator validates event ordering, indexes, tool-call IDs, JSON deltas, thinking-signature presence and placement, usage, and the final model. Thinking signatures remain opaque; Sansa does not claim to cryptographically authenticate them. Unknown future native events and deltas are preserved when they do not conflict with structural integrity.
Claude Code disables fine-grained tool-input streaming by default behind a custom ANTHROPIC_BASE_URL. Set CLAUDE_CODE_ENABLE_FINE_GRAINED_TOOL_STREAMING=1 if you want Claude Code to request that beta behavior; Sansa preserves the beta header and native tool-input deltas. Normal Messages streaming does not require this variable.
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. Sansa does not append a fake message_stop after it.
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 is an optional Claude Code gateway endpoint. Sansa does not expose the upstream model's native tokenizer, so returning a local estimate as an exact input_tokens value would be misleading.
After authentication, version, scope, model, and semantic validation, this endpoint intentionally returns:
- HTTP
404 - An Anthropic
not_found_error - Guidance that authoritative counting is unavailable
- A note that Claude Code falls back through
POST /v1/messages
Claude Code handles this fallback automatically. A direct Anthropic SDK integration must handle the 404 itself. No provider call is made by the token-count route, and it never returns a guessed successful count.
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 | Sansa or provider rate limit |
500 / 502 | api_error | Gateway or provider integrity failure |
503 | overloaded_error or api_error | Provider unavailable or another temporary gateway dependency failure |
504 | timeout_error | Provider timed out |
529 | overloaded_error | Provider temporarily overloaded |
Unsupported semantics include a stable capability_rejected: marker and the exact request path in the message. The error also tells you whether to remove the value, change it, pin a model, or use another endpoint. “Nothing was processed” means no provider inference began.
Canonical upstream Anthropic errors are preserved, including their status, wording, Retry-After, request ID, and safe extension fields. Claude Code uses upstream wording to decide when it can disable a rejected capability and retry. If the upstream request ID differs from Sansa's local correlation ID:
request-idcontains the upstream ID.x-sansa-request-idcontains the local Sansa ID.
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.
The general Errors guide documents OpenAI-format endpoints as well as this Messages exception.
Integrity and transport guarantees
Sansa sends the validated body through a native Anthropic Messages transport. An internal projection is also built for routing, abuse scanning, reservation, and telemetry, but that projection is not used to reconstruct the public response.
| Category | Behavior |
|---|---|
| Validated Anthropic fields and block order | Preserved in the native request |
anthropic-* headers | Validated as singletons and forwarded through the provider's Anthropic-header mechanism |
model and stream | Set to the provider model and actual request mode at dispatch; public model remains the caller's value |
x-claude-code-session-id | Consumed and added as provider session_id |
diagnostics | Consumed for an honest local best-effort result; never repurposed |
| Output/thinking budget after a gateway clamp | Reconciled explicitly or rejected |
| Unknown or unsupported semantic fields | Rejected at their exact path |
| Provider-private metadata and cost | Removed only from response envelopes |
This is semantic preservation, not a promise of byte-for-byte forwarding: routing-owned fields and safety clamps must reflect what Sansa actually executes. Any transformation that could change caller-visible meaning either has documented behavior above or fails loudly.
Billing and usage
Sansa estimates and reserves the maximum likely cost before provider work, then settles against validated native usage:
- Input, output, cache-read, and cache-write tokens are accounted separately where the provider reports them.
- Five-minute and one-hour cache-write usage is priced and reconciled separately; the preflight hold uses a conservative strongest-TTL estimate.
- Advisor iterations and successful hosted web searches are included when present.
- Fast-mode usage is verified against the requested pricing tier.
- Requests rejected before provider inference are not charged, and unused reservation is released. If provider work already occurred before a mid-stream or public-response integrity failure, Sansa can settle the validated usage already incurred.
Spend caps and insufficient credits fail before inference when possible. See Spend caps for key, environment, project, and organization limits.
Because public Messages usage intentionally excludes private provider cost fields, use the dashboard for the settled dollar amount.
Unsupported capabilities
Sansa rejects these rather than silently degrading them:
| Capability | Guidance |
|---|---|
Non-empty container | Containers are not available through Messages |
inference_geo | Geographic inference selection is not available |
Non-empty top-level mcp_servers | Configure MCP in Claude Code instead |
Hosted mcp_toolset | Requires Anthropic-managed MCP connections |
document content blocks | Send extracted text or a supported image |
max_tokens: 0 prewarming | Send at least 1 output token |
| Server-side compaction edits | Compact in the client and resend the full transcript |
| Standalone metered hosted code execution | Use client-side tools or a qualifying hosted search/fetch companion |
| Exact local token counting | Claude Code falls back through inference |
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.
Token counting returns 404
This is expected. Claude Code automatically falls back. Do not replace it with a guessed count in your integration.
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 |
| Native token count | Guided 404; Claude Code fallback |
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.