Migrate from OpenRouter
If your app calls OpenRouter through an OpenAI-compatible client, moving to Belarel is mostly a configuration change. This page lists what carries over as-is, the OpenRouter-specific options Belarel refuses, and the controls you get on the Belarel side.
Swap the base URL and the key
Section titled “Swap the base URL and the key”Replace OpenRouter’s base URL with https://api.belarel.com/v1 and your OpenRouter key with a Belarel key (bel_live_…, or bel_test_… for the sandbox). Keep sending X-Title and HTTP-Referer if you already do: Belarel reads them.
curl https://api.belarel.com/v1/chat/completions \ -H "Authorization: Bearer $BELAREL_API_KEY" \ -H "Content-Type: application/json" \ -H "X-Title: Acme Assistant" \ -H "HTTP-Referer: https://app.acme.example/chat" \ -d '{"model": "<model-id>", "messages": [{"role": "user", "content": "Hello"}], "max_tokens": 64}'import osfrom openai import OpenAI
client = OpenAI( base_url="https://api.belarel.com/v1", api_key=os.environ["BELAREL_API_KEY"], default_headers={"X-Title": "Acme Assistant", "HTTP-Referer": "https://app.acme.example/chat"},)r = client.chat.completions.create( model="<model-id>", messages=[{"role": "user", "content": "Hello"}], max_tokens=64,)import OpenAI from 'openai';
const client = new OpenAI({ baseURL: 'https://api.belarel.com/v1', apiKey: process.env.BELAREL_API_KEY, defaultHeaders: { 'X-Title': 'Acme Assistant', 'HTTP-Referer': 'https://app.acme.example/chat' },});const r = await client.chat.completions.create({ model: '<model-id>', messages: [{ role: 'user', content: 'Hello' }], max_tokens: 64,});What carries over
Section titled “What carries over”| OpenRouter habit | On Belarel |
|---|---|
X-Title header |
Recorded as the tag app (up to 200 characters). |
HTTP-Referer header |
Recorded as the tag app_url — the URL’s origin only (https://app.acme.example). A value that is not a URL is ignored. |
Catalog fields on /models |
GET /v1/models returns name, description, context_length, architecture, top_provider, and supported_parameters, plus a belarel block with capabilities and your price. Without a key, it returns the public catalog. |
| Reading the key’s own limits | GET /v1/key — caps, spend today and this month, what remains, and when each window resets. |
| Reading the credit balance | GET /v1/credits — included, consumed, and balance, in credits. |
| Usage in the response | Always included, with no opt-in, plus the cost of the call in belarel.cost. |
X-Title and HTTP-Referer are attribution only: they never cause a refusal. An explicit tag with the same name (app or app_url) wins, and if a call already carries 16 tags, these two are dropped rather than refused. See Tags and attribution.
What has no equivalent
Section titled “What has no equivalent”| OpenRouter feature | Behavior on Belarel |
|---|---|
provider routing object (order, allow_fallbacks, sort, ignore…) |
Refused: 400 unsupported_parameter, param: "provider". Each model id in your catalog has one serving route: you cannot pick or order providers per call. |
models fallback array |
Refused: 400 unsupported_parameter, param: "models". Send one model. |
route, transforms, plugins, usage request fields |
Refused: 400 unsupported_parameter, with param naming the field. Usage is always in the answer, without asking. |
Extra sampling fields such as top_k, min_p, top_a, repetition_penalty |
Refused: 400 unsupported_parameter. temperature, top_p, stop, seed, presence_penalty and frequency_penalty are forwarded. |
reasoning request object |
Refused on Chat Completions (400 unsupported_parameter). Use OpenAI’s reasoning_effort (minimal, low, medium, high) on models that support it. On the Responses API, reasoning.effort is read. |
Model variants such as :online, :nitro, :floor |
The suffix is part of the id. An id that is not in your catalog answers 404 model_not_found. |
If you used fallbacks, move the logic into your client: on 503 model_unavailable or 503 service_unavailable, retry with another id from GET /v1/models. On 404 or 403, don’t retry the same model — the answer will not change.
If you used :online, Belarel has an explicit switch: "web_search": true on Chat Completions, or a {"type": "web_search"} tool on Responses. It works on models whose belarel.capabilities.web_search is true and that your organization allows; otherwise the call is refused (403 web_search_not_allowed or 400 web_search_unavailable) rather than answered without search.
What you get on Belarel
Section titled “What you get on Belarel”- Caps on every key. Spend per day and per month in USD, requests and tokens per minute, calls in flight, and a ceiling on output tokens. A capped key answers
402 key_cap_exceeded. See Budgets and caps. - Sub-keys through the API. A key with the
keys:managescope mints, edits, disables, and revokes its own sub-keys, each with its own caps and tags, never above its parent. See One sub-key per customer. - Your organization’s policy, enforced on every call. The models and providers your contract allows, your residency zone, and the models your organization opened to its API keys. A refusal names the rule in
belarel.policy.decisions. See Policies and catalog. - Zero data retention per key. A key in
zero_retentionmode only reaches models with a zero-retention agreement and refuses the others with400 model_not_zdr. See Zero data retention. - The cost in every answer.
belarel.costgives USD and credits at your organization’s price, and GET/v1/usageaggregates it by key, model, day, or tag. - Required tags. A key can require tags such as
customeron every model call, and refuses calls to the model endpoints without them (400 missing_tags). Reading the catalog, usage, credits or requests, and managing keys, never needs them.

