Choose where a request runs
Send `provider` (or the `X-Stav-Runs-In` header) with a request to keep it in the EU, in one country, or on the Inference Providers you name — on every surface, failover included.
Your workspace policy sets the rule for every request. Sometimes one request needs a tighter rule. An example: your own customer in Germany needs EU compute, and a customer elsewhere does not. The provider object sets that rule for one request.
The three fields
| Field | Value | Effect |
|---|---|---|
runs_in | "eu", "eea", a country code ("NO"), or a list of country codes (["NO", "SE"]) | Only endpoints that run there serve |
only | Inference Provider slugs | Only these providers' endpoints serve |
ignore | Inference Provider slugs | These providers' endpoints never serve |
region is another name for runs_in, with the same meaning. It is not a provider region code. To pick one of a provider's regions, put it in the model id: creator/model/provider:<code>.
Use the slugs that GET /v1/models/{model}/endpoints lists for the model.
What it guarantees
- It only narrows. The request runs inside your workspace policy and inside the filter. It can never reach an endpoint that your workspace policy excludes.
- It applies to the endpoint, not the model.
only: ["ovhcloud-ai-endpoints"]uses OVHcloud's endpoint, whichever company is listed as the model's provider. - Short names work too. Some providers also take a short name, for example
ovhforovhcloud-ai-endpoints. It is the same short name a pin takes (baai/bge-m3/ovh), and the request log shows the full slug. - Failover stays inside it. If the first endpoint fails, Stav tries the model's next endpoint that passes the filter. If no endpoint passes, you get Stav's own error. The request is never sent outside the filter to rescue it.
- Nothing is sent when nothing passes. You get a refusal and the request does not reach any provider.
Chat
from openai import OpenAI
client = OpenAI(base_url="https://api.stav.ai/v1", api_key="sk_stav_live_...")
resp = client.chat.completions.create(
model="mistralai/mistral-small-4-119b-2603",
messages=[{"role": "user", "content": "Summarise this contract clause."}],
extra_body={"provider": {"runs_in": "eu"}},
)
This works the same with model="auto" and with a Router. The Smart Router only scores endpoints inside the filter.
Messages (Anthropic SDK)
import anthropic
client = anthropic.Anthropic(base_url="https://api.stav.ai", api_key="sk_stav_live_...")
msg = client.messages.create(
model="mistralai/mistral-small-4-119b-2603",
max_tokens=512,
messages=[{"role": "user", "content": "Hei!"}],
extra_body={"provider": {"runs_in": "NO"}},
)
Most Claude endpoints are global. global is not in any region, so runs_in: "eu" refuses them until an EU endpoint for that model exists.
Responses
resp = client.responses.create(
model="openai/gpt-5",
input="Hello",
extra_body={"provider": {"ignore": ["azure"]}},
)
Stav takes provider off the body. It is never sent to the provider.
Embeddings
emb = client.embeddings.create(
model="baai/bge-m3",
input=["Hovedstaden i Norge er Oslo."],
extra_body={"provider": {"runs_in": "eu"}},
)
Rerank
curl https://api.stav.ai/v1/rerank \
-H "Authorization: Bearer $STAV_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "baai/bge-reranker-v2-m3", "query": "Where is Stavanger?",
"documents": ["Stavanger is in Norway.", "Paris is in France."],
"provider": {"runs_in": "eu"}}'
Speech and decisions
/v1/audio/speech and /v1/decisions take the same provider object in the JSON body.
Transcriptions
/v1/audio/transcriptions uses a multipart form. Send provider as one form field that holds a JSON object:
curl https://api.stav.ai/v1/audio/transcriptions \
-H "Authorization: Bearer $STAV_API_KEY" \
-F file=@meeting.wav \
-F model=mistralai/voxtral-mini-2602 \
-F 'provider={"runs_in": "eu"}'
Set the region once, as a header
Some clients wrap an SDK and can set only a base URL, a key and fixed headers, not body fields. For them, X-Stav-Runs-In sets the same rule as provider.runs_in, on every surface above:
from openai import OpenAI
client = OpenAI(
base_url="https://api.stav.ai/v1",
api_key="sk_stav_live_...",
default_headers={"X-Stav-Runs-In": "eu"},
)
It takes the same values: eu, eea, a country code (NO), or a comma-separated list of country codes (NO, SE).
- Sent with
provider.runs_in, both must hold.X-Stav-Runs-In: eeawith"runs_in": "NO"runs in Norway.X-Stav-Runs-In: euwith"runs_in": "NO"has no country in common, because Norway is in the EEA and not in the EU, so Stav refuses it with400 invalid_value. - It only narrows, like the body field. It never widens your workspace policy or the body field.
- One value. An empty value, a value Stav cannot read, or two header lines with different values is
400 invalid_valuewithparam: X-Stav-Runs-In. The same value sent twice counts once. To allow several countries, use one comma-separated value. - The request log names the source. A region from the header shows as coming from the header.
- The model list follows it.
GET /v1/modelslists only models with an endpoint inside the header, and a Router orautoonly when one of its candidates has one (without an API key,autois not listed under the header);GET /v1/models/{model}/endpointsonly those endpoints. - Counting follows it.
/v1/messages/count_tokenscounts locally when the provider's own counter runs outside the header, so the prompt is never sent there. The response headerX-Stav-Token-Countsaysexactorestimate. - Stored responses follow it. Reading or cancelling a stored response (
/v1/responses/{id}) that an endpoint outside the header holds is refused with403 endpoint_not_admissible. Deleting it is not refused, so you can always erase data held outside the region.
Fields that apply on chat only
max_price and quantizations filter the endpoints of a chat request, like the fields above: max_price is a price per million tokens, and embeddings, rerank, audio and decisions are not all billed that way. sort orders a named model's endpoints on the chat surfaces only (see Routing hints). Stav refuses all three there with 400 unsupported_parameter.
When the request is refused
| You get | Because | What to do |
|---|---|---|
400 invalid_value, param: provider.runs_in | The value is not eu, eea or a country code, or it mixes a region and countries in one list | Send one region, or a list of countries |
400 invalid_value, param: X-Stav-Runs-In | The header is empty or unreadable, sent twice with different values, or it has no country in common with provider.runs_in | Send one value both can hold, or only one of them |
400 invalid_value, param: provider.only (or ignore) | A slug is not an Inference Provider | Use a slug from GET /v1/models/{model}/endpoints |
400 unsupported_parameter, param: provider.<field> | The field is not supported on this surface | Remove it |
400 invalid_value, param: provider | The model id pins a provider that the filter excludes | Remove the provider from the model id, or change the filter |
403 endpoint_not_admissible | No endpoint of the model passes the filter together with your workspace policy | Change the filter, or pick a model with an endpoint inside it |
403 provider_blocked | Every endpoint of the model is on a provider that ignore names | Remove it from ignore |
Check it in the response
Every response says where it ran, so your code can check a runs_in: "eu" call with no second request.
| Where | Value |
|---|---|
X-Stav-Compute-Region header | eu, eea, us, global or unknown. Always sent |
X-Stav-Compute-Countries header | Every country the request may have run in, e.g. FI,FR. Sent when a country is known |
usage.compute_region, usage.compute_countries in the body | The same two values. On a stream, on the final frame that carries usage |
global means the Inference Provider pins no residency. unknown means Stav holds no fact about where the endpoint runs. They are two different values.
A call ran in the EU when the region is eu, or when every country is an EU state. Speech answers with audio, so it has the headers only. A transcription in text, srt or vtt has the headers only too.
See what happened
Every request with a filter shows it in Your workspace → Monitor → Request log. Open the request to see the filter, the endpoint that served, and the country it ran in.
Next steps
- Enforce a sovereignty floor — the workspace rule this narrows
- Sovereignty
- Routing hints