Stav speaks the Anthropic Messages contract as a first-class surface, not a shim. For claude-* models the request is proxied to Anthropic essentially untouched, which means the features that usually break through a gateway — extended thinking, cache_control, beta headers, new fields Anthropic shipped last week — keep working.
The two lines
import os
from anthropic import Anthropic
client = Anthropic(
api_key=os.environ["STAV_API_KEY"], # sk_stav_live_...
base_url="https://api.stav.ai", # NOT .../v1 — the SDK appends it
)
const client = new Anthropic({
apiKey: process.env.STAV_API_KEY,
baseURL: "https://api.stav.ai",
});
The missing /v1 is the single most common setup mistake. The Anthropic SDK appends /v1/messages itself, so a base URL ending in /v1 produces a request to /v1/v1/messages — which reaches Stav and 404s.
The SDK sends x-api-key, which Stav accepts. You do not need to change the credential header.
Two paths, one endpoint
POST /v1/messages behaves differently depending on the model:
claude-* models — verbatim proxy. The full native contract: system, messages, max_tokens, temperature, top_p, top_k, stop_sequences, tools, tool_choice, metadata, stream, cache_control markers, , beta features. Thinking blocks come back in . Prompt caching works and reports .
Everything else — translated. Any other catalogue model is translated to the chat contract under the hood. Anthropic-only features have no equivalent there and are refused by name, with the error saying which model resolved:
Extended thinking (`thinking`) requires a Claude model, but model 'gpt-5-mini' resolved to …
This matters when you send model="auto": the router may resolve to a non-Claude model, at which point a native thinking object becomes an error. If you need native thinking, pin a Claude model on this surface, or use reasoning_effort on /v1/chat/completions, which works across all four dialects.
Extended thinking
msg = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=8192,
thinking={"type": "enabled", "budget_tokens": 4096},
messages=[{"role": "user", "content": "Plan a Postgres 14 → 17 migration."}],
)
for block in msg.content:
if block.type == "thinking":
print("[reasoning]", block.thinking)
Two things to keep in mind:
budget_tokensmust be ≥ 1024, and strictly less thanmax_tokens. Clients that scale the budget frommax_tokenscan produce a budget below the minimum ifmax_tokensis small — setmax_tokensto at least ~4096 when thinking is on. Stav relays Anthropic's rejection verbatim if you get this wrong.- Thinking tokens are recorded on your request log, so cost analysis is consistent with the chat surface.
Prompt caching
msg = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
system=[{
"type": "text",
"text": LONG_SYSTEM_PROMPT,
"cache_control": {"type": "ephemeral"},
}],
messages=messages,
)
print(msg.usage.cache_read_input_tokens)
This is the largest cost lever available to a long-running agent on this surface. A large system prompt plus tool schemas that would otherwise be re-billed every turn becomes a cache read.
cache_control also works on /v1/chat/completions when the request resolves to an Anthropic model. On Google models it is stripped; on OpenAI, xAI and Mistral it is ignored, because those providers cache implicitly. That contract is declared rather than accidental — see Request parameters.
Routing on this surface
client.messages.create(model="auto", max_tokens=1024, messages=messages)
client.messages.create(model="@acme/eu-only", max_tokens=1024, messages=messages)
Routing applies here as it does on the chat surface — same team policy, same sovereignty floor, same named routers. Just remember that auto may resolve to a non-Claude model and put you on the translated path.
What you get on top
raw = client.messages.with_raw_response.create(
model="claude-sonnet-4-5-20250929",
max_tokens=256,
messages=messages,
)
print(raw.headers["X-Stav-Model"])
print(raw.headers["X-Stav-Sovereignty-Level"])
- Every request logged with model, provider, jurisdiction, token counts and cost
- A sovereignty floor enforced centrally
- One key, one bill, one audit trail across Claude and everything else in the catalogue
- Failover to your next-best model on provider outages
Scopes
inference:messages is sufficient for the Anthropic SDK — it does not call /v1/models, so unlike the OpenAI SDK you do not need inference:models for the dropdown to work.
Migration checklist
- Mint a
sk_stav_test_key withinference:messages. - Change
base_urltohttps://api.stav.ai— without/v1. - Run your suite unchanged against a pinned
claude-*model. It should pass. - Check
X-Stav-Sovereignty-Levelon the responses and confirm it matches expectations. - If you want routing, switch to
model="auto"— and move any nativethinkingusage toreasoning_efforton/v1/chat/completionsfirst.
Not available
- Files API and PDF upload. No file ingestion endpoint on Stav.
- Computer use works only where the request proxies verbatim to Anthropic — it is Anthropic's feature, not a Stav feature, and it does not exist on any other model.
- Batch API. Not on this surface.
Next steps
- Reasoning — the cross-provider
reasoning_effortcontract - Request parameters
- Smart Router