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"

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 scopeValidates the request, then returns a guided 404; Claude Code falls back through inference
HEAD /api/helloNoneReturns 204 for Claude Code's optional connection-warming probe
GET /v1/models?limit=1000Sansa keyReturns live Claude model IDs for opt-in gateway discovery; no provider call and no charge
GET /v1/mcp_servers?limit=1000Sansa key; messages scopeReturns {"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/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

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-beta list is preserved in its original order and forwarded.
  • Future singleton anthropic-* headers are forwarded rather than removed by a fixed allowlist.
  • Authentication, Content-Type, and Accept are 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

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, 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 -Infinity are 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 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 supported immutable snapshot IDMaps only when Sansa has an explicit exact snapshot binding; arbitrary dates are never stripped
An ID from GET /v1/models?limit=1000Calls that Claude catalog model directly
Any available model ID from the Sansa catalogCalls that model directly through the native Messages transport
sansa-autoLets 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

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. 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_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. 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, 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

Sansa's native Messages transport currently validates and preserves these hosted tool families:

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

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_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 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_toolset is 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"
  }
}
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 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:

  • 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.

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: null returns "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_20251015
  • clear_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 sansa field 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

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 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.

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_errorSansa or provider rate limit
500 / 502api_errorGateway or provider integrity failure
503overloaded_error or api_errorProvider unavailable or another temporary gateway dependency failure
504timeout_errorProvider timed out
529overloaded_errorProvider 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-id contains the upstream ID.
  • x-sansa-request-id contains 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.

CategoryBehavior
Validated Anthropic fields and block orderPreserved in the native request
anthropic-* headersValidated as singletons and forwarded through the provider's Anthropic-header mechanism
model and streamSet to the provider model and actual request mode at dispatch; public model remains the caller's value
x-claude-code-session-idConsumed and added as provider session_id
diagnosticsConsumed for an honest local best-effort result; never repurposed
Output/thinking budget after a gateway clampReconciled explicitly or rejected
Unknown or unsupported semantic fieldsRejected at their exact path
Provider-private metadata and costRemoved 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:

CapabilityGuidance
Non-empty containerContainers are not available through Messages
inference_geoGeographic inference selection is not available
Non-empty top-level mcp_serversConfigure MCP in Claude Code instead
Hosted mcp_toolsetRequires Anthropic-managed MCP connections
document content blocksSend extracted text or a supported image
max_tokens: 0 prewarmingSend at least 1 output token
Server-side compaction editsCompact in the client and resend the full transcript
Standalone metered hosted code executionUse client-side tools or a qualifying hosted search/fetch companion
Exact local token countingClaude 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 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
Native token countGuided 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.