# OpenRouter compatibility

> What the Numes API shares with OpenRouter, every intentional difference, and a checklist for migrating an existing integration.

Source: https://0.0.0.0:3000/docs/api-reference/compatibility
Documentation index: https://0.0.0.0:3000/docs/llms.txt

Numes implements a subset of the OpenRouter API, which extends the OpenAI Chat Completions API. Request and response shapes, the stream format, the error envelope and the usage object follow OpenRouter, so OpenAI SDK and OpenRouter-style clients usually work after changing the base URL and key. Every deliberate difference is listed here; most exist so the API never appears to honor something it doesn't, or so billing stays exact. This page was last reviewed against OpenRouter's documentation in September 2026.

## Endpoints

| Endpoint | Notes |
| --- | --- |
| `GET /models` | Filters `output_modalities` and `supported_parameters`; four sort orders; prices in your key's currency |
| `GET /models/{author}/{slug}/endpoints` | Per-endpoint pricing, limits, parameters and routing `tag` |
| `POST /chat/completions` | Buffered and streamed; tools, structured outputs, reasoning, caching, multimodal input |
| `GET /generation` | Its own field set; see [Billing and delivery](#billing-and-delivery) |
| `POST /embeddings` | Text, token and multimodal inputs |
| `POST /images` | Buffered or streamed, base64 output |
| `POST /audio/speech` | MP3, PCM and WAV |
| `POST /audio/transcriptions` | Multipart or JSON input; `json` and `verbose_json` output |
| `GET /key` | The current key's metadata; inference or management key |
| `GET`, `POST /keys`; `GET`, `PATCH`, `DELETE /keys/{hash}` | Management key; manages inference keys only |
| `GET /credits` | Management key; requires `currency=USD` or `currency=BTC` |
| `POST /e2ee/resolve`, `POST /e2ee/chat/completions` | Extension for [encrypted inference](/docs/inference/encrypted) |

## Request shape

| Behavior | OpenRouter | Numes | What to do |
| --- | --- | --- | --- |
| Unknown request fields | Generally ignored | Chat forwards ordinary generation hints; gateway-owned controls have separate validation | Use [Parameters](/docs/api-reference/parameters) to check which controls the gateway handles |
| Models per request | `models` array falls back across models | Exactly one: `model`, or `models` with one entry | Implement model fallback in your client |
| Requested features | Unsupported parameters may be ignored | Tools, `response_format`, reasoning, cache directives and output `modalities` are hard requirements; no matching endpoint returns `503 provider_unavailable`. Unsupported sampling parameters are omitted unless `provider.require_parameters` is `true` | Check `supported_parameters` before sending a feature |
| Output limit | Large values accepted | `max_tokens` and `max_completion_tokens` must be 1–131,072, otherwise `400`. When omitted, the endpoint's own maximum applies | Use the model's `top_provider.max_completion_tokens` or less |
| Unknown model | An error naming the invalid model | `503 provider_unavailable`, the same response as any request no endpoint can serve | Copy IDs from `GET /models` |
| Message `name` | Accepted on any role | Accepted only on `tool` messages, and not forwarded | Remove `name` from other roles |
| File references | OpenAI-style `file_id` | `file_id` is rejected; chat audio must be inline base64 | Send PDFs as HTTPS or `data:` URLs and audio as base64 |
| `X-Request-Id` | No uniqueness rule | Unique per account; reusing a recorded ID returns `400` | Omit it, or send a new value on every call, including retries |
| Browser requests | Allowed | Third-party origins get `403 cors_forbidden` | Call the API from your server |

## Routing

| Behavior | OpenRouter | Numes | What to do |
| --- | --- | --- | --- |
| `max_price` units | Dollars per million tokens | Per unit (per token for text) in the key's currency; also caps separately priced reasoning and cache-write rates | Divide OpenRouter values by 1,000,000; use satoshis for BTC keys |
| Default routing | Price-weighted load balancing | Balances price, reliability and performance across eligible endpoints | Use `provider.sort` or `order` for a deterministic order |
| `provider.sort` | `price`, `throughput`, `latency`, or an object | The three strings plus `quality`; no object form | Use the string form |
| Data residency | Separate EU base URL | `provider.regions` filters on endpoint-declared countries and regions | Send `provider.regions` |
| Other `provider` fields | Percentile performance preferences, distillation filters and more | Accepted: `order`, `allow_fallbacks`, `require_parameters`, `data_collection`, `zdr`, `only`, `ignore`, `quantizations`, `regions`, `sort` and `max_price`. Others return `400` | Remove the rest |

## Pricing and currency

| Behavior | OpenRouter | Numes | What to do |
| --- | --- | --- | --- |
| Currency | USD credits | Each key has a fixed currency, `USD` or `BTC` (in satoshis), for prices, costs and limits. No conversion | Pick the currency when you create the key |
| Creating keys | `POST /keys` without currency | `billing_currency` is required and can't be changed | Add `billing_currency` to create calls |
| Upstream cost | Aggregate plus prompt and completion split | Aggregate `cost_details.upstream_inference_cost` only | Use `GET /generation` for detail |
| Media usage | Some media counted as tokens | Image counts and audio seconds are separate usage fields | Read `input_images`, `output_images`, `input_audio_seconds`, `output_audio_seconds` |
| Prompt caching | Provider cache pricing | Cache rates never exceed the prompt rate | No action needed |
| Money in JSON | Numbers | Numbers written from exact decimals; prices are decimal strings | Parse as decimals when you add amounts |

## Billing and delivery

| Behavior | OpenRouter | Numes | What to do |
| --- | --- | --- | --- |
| What you pay for | Varies by request type | Output delivered to you; `GET /generation` is the authoritative record | No action needed |
| Usage opt-in | `usage.include`, `stream_options.include_usage` | Usage is always returned; both fields are accepted and ignored | Read `usage` from every response |
| Final usage chunk | Can include a choice | `choices: []` with `usage` | Skip chunks with an empty `choices` array when reading content |
| Fallback | Across providers and models | Across endpoints of the requested model, only before output reaches you | Treat mid-stream errors as final |
| Stream keep-alives | `: OPENROUTER PROCESSING` comments | None | Use a read timeout of several minutes |
| `GET /generation` fields | OpenRouter's generation stats | Outcome, delivery state, exact costs and a breakdown of each endpoint tried; never content | See [Usage accounting](/docs/account/usage-accounting) |

## Model discovery

| Behavior | OpenRouter | Numes | What to do |
| --- | --- | --- | --- |
| Which models are listed | The whole catalogue | Only models with an available endpoint priced in your key's currency | Expect different lists for USD and BTC keys |
| One model row | Model-level summary | Describes one representative endpoint | Use the endpoints API for every endpoint |
| Filters and sorting | Many filters and search | `output_modalities`, `supported_parameters` and four sorts; no pagination or search | Filter client-side |
| Prices | Rates you pay | Provider rates, which are what requests are charged | Expect `usage.cost` to equal `upstream_inference_cost` |

## Media

| Behavior | OpenRouter | Numes | What to do |
| --- | --- | --- | --- |
| PDFs | Parsed for any model | Routed only to endpoints that accept file input | Pick a model whose endpoints list `file` input |
| Generated images | URL or base64 | Base64 only | Decode `b64_json` |
| Transcription | Several formats | `json` and `verbose_json`; `prompt` accepted and ignored; no word or segment timestamps | Use `json` |
| Image or audio output from chat | Supported on some models | Only from endpoints that declare the output; otherwise `503` | Use `/images` and `/audio/speech` |

## Headers, errors and keys

| Behavior | OpenRouter | Numes | What to do |
| --- | --- | --- | --- |
| `X-OpenRouter-Metadata` | `enabled` | `enabled`, or `true`; chat only; never on error bodies | Send `enabled` |
| `HTTP-Referer`, `X-Title` | App attribution and rankings | `HTTP-Referer` groups your own [Activity](/docs/account/activity); `X-Title` is ignored; neither reaches providers | No action needed |
| Error metadata | Can include provider name and raw upstream error | `error_type` only; upstream messages are never relayed | Branch on status and `error_type` |
| Key metadata | Includes `is_free_tier`, `is_provisioning_key`, `rate_limit` and BYOK fields | Those are omitted; `billing_currency` is added | Don't depend on the omitted fields |
| Management keys | Created through the API | Created and revoked in the dashboard | Create them in [Keys](/dashboard/keys) |
| Structured outputs | Provider-enforced | Schema adherence depends on the model and endpoint | Validate the parsed result |

## Unsupported OpenRouter features

Unimplemented endpoints return `404`. Chat rejects settings that would bypass gateway execution or privacy controls.

- **Other API shapes:** the Responses API, the Anthropic Messages API, legacy `/completions` and video generation.
- **Account features:** bring-your-own-key, presets, OAuth sign-in for third-party apps, the analytics API, workspaces, guardrails and customer webhooks.
- **Model shortcuts:** variants such as `:free`, `:nitro`, `:floor`, `:online` or `:thinking`; `~author/model` aliases; multi-model fallback through `models` or `route`.
- **Plugins and transforms:** the `plugins` field, response caching and `transforms`. For web search, use the hosted `web_search` tool; see [Hosted tools](/docs/features/hosted-tools).
- **Search and choices:** `web_search_options` and requesting more than one choice with `n` return `400`.
- **Other OpenAI fields:** `store: true`, `service_tier` other than `default`, and legacy `functions` and `function_call` (use `tools` and `tool_choice`).

Penalty samplers (`frequency_penalty`, `presence_penalty`, `repetition_penalty`) and `min_p` or `top_a` are forwarded as numeric hints. Chat also forwards ordinary hints such as `logit_bias`, `logprobs`, `top_logprobs`, `prediction` and `verbosity` to compatible sources. Token log probabilities are not returned in the public response. Use `require_parameters` if the endpoint must declare the hints you send.

## Additive fields

Responses carry some fields OpenRouter doesn't define. Clients that ignore unknown fields are unaffected.

| Field | Where | Meaning |
| --- | --- | --- |
| `billing_currency` | Models, endpoints, key metadata | The currency of every price and amount for this key |
| `usage_meter` | Models, endpoints, usage | Identifies the usage contract that prices the offer; treat it as opaque |
| `token_count_basis` | Usage | `provider_native` when token counts come from the provider, `gateway_estimate` otherwise |
| `native_tokens` | Usage | The provider's token counts, including reasoning and cache classifications |
| `gateway_billing_units` | Usage | Billing quantities on some older requests; absent on current offers |
| `hosted_tool_usage` | Usage | Quantities of each hosted tool used |
| `maximum_output_length`, `embeddings`, `supported_hosted_tools`, `reasoning` | Models, endpoints | Output ceiling, embedding support, hosted tools and reasoning capabilities |
| `access` | Models | Present on models shared with one of your organizations |
| `pricing_basis`, `reference`, `usd_rates` | Model pricing | Present on BTC prices that track a USD price |
| `provider.regions`, `provider.sort: "quality"` | Chat request | Routing extensions; see [Parameters](/docs/api-reference/parameters#routing) |

## Migrate from OpenRouter

:::steps
### Change the base URL and key

Point your client at `https://api.numes.ai/api/v1` and use an inference key from [Keys](/dashboard/keys). With the OpenAI SDK, only the base URL and API key change.

### Pick the key currency

Choose `USD` or `BTC` when you create the key. Prices, `usage.cost` and limits come back in that currency, and BTC amounts are satoshis. Fund the matching balance in [Billing](/dashboard/billing).

### Use one model ID per request

Check IDs against [`GET /models`](/docs/api-reference/models/list-models). Remove variant suffixes and `~` aliases, and replace multi-model `models` arrays with one model. For model fallback, catch `503 provider_unavailable` and retry with your next model.

### Remove unsupported fields

Remove blocked settings such as `plugins` and `web_search_options`. For reasoning, send `reasoning: {effort}`. A forwarded hint can still be refused by the endpoint; check the field named in the error against [Parameters](/docs/api-reference/parameters).

### Convert routing preferences

Divide `max_price` values by 1,000,000, use string `sort` values, and replace EU base-URL routing with `provider.regions`. Selectors for `order`, `only` and `ignore` come from the `tag` field of the endpoints API.

### Update keys, balances and retries

Add `billing_currency` to `POST /keys` calls, create management keys in the dashboard, and add `currency` to `GET /credits` calls. Send a fresh `X-Request-Id` on every retry, or none, and retry only before a stream starts; see [Retry strategy](/docs/api-reference/errors#retry-strategy).
:::

## Next steps

:::cards
- {plug} [OpenAI SDK](/docs/integrations/openai-sdk) Configure the official Python and TypeScript SDKs.
- {terminal} [Coding agents](/docs/integrations/coding-agents) Use OpenCode, Pi and OMP.
- {braces} [Parameters](/docs/api-reference/parameters) The exact set of accepted request fields.
- {network} [Provider routing](/docs/routing/provider-routing) Routing controls and the default strategy.
:::
