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"

claude

The base URL must be https://api.sansaml.com without /v1. Claude Code appends /v1/messages itself.

Inside Claude Code:

  1. Run /status and confirm the Anthropic base URL is https://api.sansaml.com.
  2. Send a test prompt.
  3. 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=1

At 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 pathAuthenticationBehavior
POST /v1/messagesSansa key; messages scopeCreates or streams a Message
POST /v1/messages/count_tokensSansa key; messages scopeReturns 404; see Token counting
HEAD /api/helloNoneReturns 204 for Claude Code's optional connection-warming probe
GET /v1/models?limit=1000Sansa keyReturns Claude model IDs for opt-in discovery; free
GET /v1/mcp_servers?limit=1000Sansa key; messages scopeReturns 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/messages
  • POST /v1/messages/count_tokens
  • GET /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.

HeaderRequired valueNotes
Content-Typeapplication/jsonRequired on POSTs; parameters such as ; charset=utf-8 are allowed
anthropic-version2023-06-01Required on Messages, token count, and MCP; other versions fail with guidance
Authorization or x-api-keyYour Sansa keyRequired on Messages, token count, MCP, and model discovery
anthropic-betaOptional, comma-separated beta IDsRequired 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.

FieldRequiredAccepted values and behavior
modelYesNon-empty Claude alias, any available Sansa catalog model ID, or sansa-auto
max_tokensYesInteger of at least 1; includes visible and thinking output
messagesYesNon-empty ordered array, up to 100,000 messages
systemNoString or a non-empty ordered array of text blocks
streamNoBoolean; default false
temperatureNoNumber from 0 through 1
top_pNoNumber from 0 through 1
top_kNoNon-negative integer
stop_sequencesNoUp to four strings
toolsNoCustom or supported Anthropic-hosted tool definitions
tool_choiceNoauto, any, none, or a named tool choice
thinkingNodisabled, adaptive, or manually enabled
output_configNoEffort, JSON Schema output, and supported task-budget controls
cache_controlNoAnthropic ephemeral cache control
context_managementNoSupported native context edits; see below
diagnosticsNoBest-effort prompt-cache diagnosis; requires its beta
metadataNoObject containing only user_id, as a string or null
service_tierNoauto or standard_only
speedNostandard 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, and Infinity are 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 valueBehavior
claude-opus-5, claude-sonnet-5, claude-fable-5Maps to the corresponding live Sansa Claude model
opus, sonnet, haiku, fableClaude Code-style aliases
An undated version such as claude-opus-4-8Maps to the corresponding catalog form, such as anthropic/claude-opus-4.8
A dated snapshot ID Sansa lists, such as claude-sonnet-4-5-20250929Maps to that release; other dated IDs return 400
An ID from GET /v1/models?limit=1000Calls that Claude catalog model directly
Any available Sansa catalog model IDCalls that model directly
sansa-autoLets 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

RoleContent
userString, or text, image, and tool_result blocks
assistantString, or text, thinking, tool-use, and current server-tool result blocks
systemClaude 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_use and mcp_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, and input_schema
  • strict
  • cache_control
  • defer_loading
  • eager_input_streaming
  • input_examples
  • allowed_callers

Tool names must be unique within one request.

Tool choice

Anthropic valueBehavior
{"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

  1. Send tools with the user message.
  2. Read the assistant's tool_use block and preserve its id, name, and input.
  3. Execute the tool in your application.
  4. Append the complete assistant response to the transcript.
  5. Append a user tool_result whose tool_use_id matches the call.
  6. 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:

FamilyAccepted versions
Web searchweb_search_20250305, web_search_20260209, web_search_20260318
Web fetchweb_fetch_20250910, web_fetch_20260209, web_fetch_20260309, web_fetch_20260318
Code executioncode_execution_20250522, code_execution_20250825, code_execution_20260120, code_execution_20260521
Tool searchRegex and BM25 types, including the 20251119 versions
Advisoradvisor_20260301

Limits:

  • Every web-search definition needs max_uses from 1 through 100.
  • Advisor needs max_uses from 1 through 100 and an available advisor model. Optional advisor max_tokens must 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_toolset isn'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"
  }
}
ModeRules
disabledCannot be combined with output_config.effort
adaptiveMay use effort; display may be summarized, omitted, or the current beta value updates
enabledbudget_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:

  • type must be ephemeral.
  • ttl may be omitted, 5m, or 1h. Omitted means the default five-minute cache.
  • scope, when supplied, must be global.
  • 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_20251015
  • clear_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

EventPurpose
message_startStarts the Message and carries initial usage
content_block_startOpens one indexed content block
content_block_deltaAdds text, thinking, signature, JSON, citation, or another native delta
content_block_stopCloses that indexed block
message_deltaCarries final stop data and cumulative usage
message_stopSuccessful terminal event
pingKeepalive during an otherwise idle period
errorTerminal 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.

StatusAnthropic error typeTypical cause
400invalid_request_errorInvalid JSON, missing version, bad field, unsupported capability
401authentication_errorMissing, invalid, duplicated, or conflicting key
402billing_errorInsufficient credits or a blocking spend cap
403permission_errorMissing messages scope, model restriction, or abuse rejection
404not_found_errorOptional token-count endpoint is unavailable, or a route does not exist
413request_too_largeRequest exceeds the accepted body size
429rate_limit_errorRate limited
500 / 502api_errorServer error
503overloaded_error or api_errorTemporarily unavailable
504timeout_errorTimed out
529overloaded_errorTemporarily 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:

CapabilityInstead
Non-empty containerNot available
inference_geoNot available
Non-empty top-level mcp_serversConfigure 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 directSansa
https://api.anthropic.comhttps://api.sansaml.com
Anthropic API keySansa API key
Anthropic account limitsSansa credits, scopes, model policies, and spend caps
Direct model IDClaude 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.