An AI gateway is a single API between your app and many LLM providers. Without one, you end up rebuilding the same plumbing: a key per provider, an SDK per provider, retries, fallbacks, and a bill from everyone. A gateway puts all of that behind one endpoint.
A European AI gateway is one that runs in Europe, specifically the EU, and can keep your requests there, from the gateway itself to the model that answers.
This is a short, high-level comparison. I use OpenRouter and Opper as the two examples because they keep requests in the EU in different ways, and you can try both with the same OpenAI SDK.
That covers most "should I use a gateway" decisions. Where it gets interesting is the data.
"EU-hosted" gets used loosely. In practice there are four separate questions:
Point 4 is the one people miss. Failover and data residency pull in opposite directions, so a European setup needs a way to say "fail over, but only inside this boundary".
| OpenRouter | Opper | |
|---|---|---|
| Company | Stripe (US), which acquired OpenRouter in 2026 | Opper Technology AB, Stockholm, Sweden |
| OpenAI-compatible base URL | https://openrouter.ai/api/v1 |
https://api.opper.ai/v3/compat |
| Keeping a request in the EU | Call the EU endpoint https://eu.openrouter.ai/api/v1 , Business and Enterprise plans only |
Call an EU-hosted route on any account, or set a model access rule |
| Per-request data controls | provider.zdr ,provider.data_collection ,provider.only /ignore |
The route you call, provider/model |
| Org-wide enforcement | Privacy settings on the account | Model access rule on the org or a project |
| No eligible route left | The request errors instead of leaving the region | Refused with 403 before it reaches a provider |
| Fee on credit purchases | 5.5%, or 8% on Business, which the EU endpoint needs | 3%, EU-hosted routes included (5.5% with Control Plane rules) |
On OpenRouter you don't pick a European model for this. You call the EU endpoint with the usual model id, and it only uses provider endpoints in the EU/EEA, the UK or Switzerland, so fewer models are available than on the global endpoint.
Both fail closed, which is the right call. The difference is where the boundary lives. OpenRouter draws it at the endpoint you call. Opper draws it in a rule on your org or project, so every key and every app under it inherits the same boundary. One more detail from OpenRouter's own docs: with the EU endpoint, prompts and completions stay in the region, while account, billing and other operational records may be processed in the US.
Both speak the OpenAI API, so switching is a base URL and a model name.
OpenRouter, EU endpoint, zero data retention providers only:
import os
from openai import OpenAI
client = OpenAI(
base_url="https://eu.openrouter.ai/api/v1",
api_key=os.environ["OPENROUTER_API_KEY"],
)
resp = client.chat.completions.create(
model="mistralai/mistral-small-2603",
messages=[{"role": "user", "content": "Summarise GDPR Article 28 in two sentences."}],
extra_body={"provider": {"zdr": True, "data_collection": "deny"}},
)
print(resp.choices[0].message.content)
Opper, an EU-hosted route:
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.opper.ai/v3/compat",
api_key=os.environ["OPPER_API_KEY"],
)
resp = client.chat.completions.create(
model="mistral/mistral-small-2603", # Mistral Small on Mistral's own EU route
messages=[{"role": "user", "content": "Summarise GDPR Article 28 in two sentences."}],
)
print(resp.choices[0].message.content)
On Opper the model id carries the route as provider/model, and each route's data policy is in the public catalogue. https://api.opper.ai/v3/models?q=mistral-small-2603 lists every route for that model, and for the one above it returns "inference_location": "EU", "training": "no" and "logging": "none".
To make it a rule rather than a habit, a model access rule allowlists EU inference locations for the whole org or a project. A call that names a US route then comes back as:
403 model "anthropic/claude-sonnet-5" is blocked by the active model allowlist: inference location US is outside EU
A bare model name, like mistral-small-2603 without a provider, still fails over, but only across the allowed routes, and it's refused with 403 if none are left. That's point 4 handled in one place.
Docs if you want to dig in: OpenRouter EU in-region routing, Opper model access rules.