If your code already uses the OpenAI Python or Node SDK, the migration is two lines plus two settings. This page covers both, and then the parts of the OpenAI contract Stav deliberately does not replicate.
The two lines
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["STAV_API_KEY"], # sk_stav_live_...
base_url="https://api.stav.ai/v1", # was api.openai.com/v1
)
const client = new OpenAI({
apiKey: process.env.STAV_API_KEY,
baseURL: "https://api.stav.ai/v1",
});
Everything the SDK does — chat.completions.create, streaming, tools, response_format, embeddings.create, models.list, raw-response access — keeps working.
The two settings
1. Send max_tokens, not max_completion_tokens.
Recent OpenAI SDKs and many agent frameworks emit max_completion_tokens. Stav does not accept that field inbound; it renames max_tokens per provider for you. Most clients expose a compatibility switch — set it to max_tokens. If you construct request bodies yourself, use max_tokens.
2. Do not send store.
Stav has no completion store, so store — including store: false — is refused by name. Some agent clients set it unconditionally; turn it off.
Both refusals are explicit 400s naming the field, so you will find them within a minute of your first request rather than in production.
Model names
Stav's catalogue has its own slugs. There is no alias resolution — the string you send is matched exactly.
client.models.list() # requires the inference:models scope
You have four options for the model field:
model="auto" # let Stav route
model="@acme/eu-only" # your team's named router
model="gpt-4o-mini" # pin a catalogue model
For a first migration, auto is usually the right answer: it gives you failover and cost optimisation immediately, and it means your code stops carrying a model name that will be wrong in six months.
Scopes: the one that bites
The OpenAI SDK calls GET /v1/models for listings, and almost every IDE and agent integration populates its model dropdown from it. A key scoped to inference:chat alone completes fine and shows an empty model list.
Mint keys with inference:chat and inference:models unless you have a reason not to.
What is different, precisely
| OpenAI behaviour | On Stav |
|---|---|
| Unknown request fields ignored | Rejected with a 400 naming the field |
| Parameter unsupported by the model → silently dropped | Rejected with a 400 naming the parameter, the model, and the providers that would accept it |
n > 1 returns multiple choices | Rejected. Send parallel requests |
max_completion_tokens | Send max_tokens |
store / metadata |
The pattern behind the table: Stav refuses rather than dropping. That is more work on day one and much less work on day ninety, when a setting you believed was applied turns out to have been discarded all along. See Request parameters.
Streaming usage
stream = client.chat.completions.create(
model="auto",
messages=messages,
stream=True,
stream_options={"include_usage": True}, # required for token counts
)
Without include_usage a streaming response carries no usage object at all — the same as OpenAI, but easy to forget when you were previously reading token counts off a non-streaming path.
Also check each frame for an error key: a late refusal on an open stream arrives as an SSE error frame, not an HTTP status.
What you gain
raw = client.chat.completions.with_raw_response.create(
model="auto",
messages=messages,
)
print(raw.headers["X-Stav-Model"]) # what actually served
print(raw.headers["X-Stav-Sovereignty-Level"]) # under which jurisdiction
print(raw.headers["X-Stav-Route-Reason"]) # why
- Routing — one model string, a policy you change in the portal
- Failover — when you route, provider 5xx, 429 and timeouts are retried against your next-best candidate inside the same request
- A sovereignty floor — enforced centrally, not in each service
- Per-request audit — model, provider, jurisdiction, cost and app on every row
- Reasoning across vendors —
reasoning_effortworks on OpenAI, Anthropic, Google and Mistral models alike
A migration that takes an afternoon
- Mint a
sk_stav_test_key withinference:chatandinference:models. - Point one non-critical service at
https://api.stav.ai/v1with the model string unchanged. - Run your test suite. Fix whatever
400s — this is wheremax_completion_tokensandstoresurface. - Switch that service's model to
"auto". Compare quality on your own evaluation set. - Set a sovereignty floor at the team level and confirm
X-Stav-Sovereignty-Levelon live traffic. - Roll out with a
sk_stav_live_key. AddX-Titleso the traffic is attributable.
Not on the OpenAI-compatible surface
- Assistants, threads, vector stores, file upload. Retrieval is your pipeline;
/v1/embeddingsand/v1/rerankare the building blocks. - Server-side tools. No hosted web search, code execution or file search.
toolsentries are your functions, executed by you. - Audio and image generation. Text and embeddings.
- Codex-family models on the chat surface. These are Responses-API-only.
/v1/responsesis a verbatim proxy for OpenAI models only —auto, named routers and non-OpenAI models are rejected there withmodel_not_responses_capable.