Migrate from OpenRouter
Onlist speaks the same OpenAI- and OpenRouter-compatible /v1 surface you already
call. Migrating is a two-line change: point your SDK at https://onlist.io and
swap in an Onlist token. Your request bodies, response shapes, and the official SDKs
stay the same.
This page shows the swap, lists what carries over unchanged, and documents every way Onlist behaves differently from OpenRouter. The differences are real and specific; we list them all rather than imply parity we do not deliver.
The swap
Change two values. Everything else in your code is untouched.
from openai import OpenAI
client = OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key="sk-or-v1-...",
)
resp = client.chat.completions.create(
model="openai/gpt-4o",
messages=[{"role": "user", "content": "Hello"}],
)
print(resp.choices[0].message.content)Only base_url and api_key changed. The Onlist token is a plain bearer token issued
in the buyer dashboard: there is no sk-or- prefix (a leading sk- is tolerated and
stripped). See Authentication for header normalization across the
OpenAI, Anthropic, and Gemini SDKs.
What stays the same
These work on Onlist exactly as they do on OpenRouter, with no code changes:
- Official SDKs. There is no bespoke Onlist SDK. Use the official OpenAI, Anthropic,
and Gemini SDKs pointed at Onlist via
base_url. - Request and response bodies. The OpenAI chat/completions, Anthropic Messages, and
Gemini
generateContentschemas are unchanged. Modeled fields are forwarded upstream. - The
providerobject. The OpenRouter-compatibleproviderrouting object is read on JSON endpoints, with the same field names. - The
models[]fallback list. The top-levelmodelsarray is honored as a try-in-order fallback (the bodymodelis rewritten per attempt), gated byprovider.allow_fallbacks. - Streaming.
stream: truereturns an SSE stream of chunks terminated bydata: [DONE]. See Streaming. - Tools and structured outputs. Tool/function calling and JSON/structured-output request fields pass through unchanged.
What is different
Every documented divergence from OpenRouter. Each row pairs the OpenRouter behavior with what Onlist does instead.
| Area | OpenRouter | Onlist |
|---|---|---|
| Token prefix | Tokens are prefixed sk-or-... | Plain bearer token, no sk-or- prefix (a leading sk- is tolerated and stripped) |
Unknown provider fields | Validated; bad fields can error | Accepted and silently ignored (not 400), then reported in X-Onlist-Warnings |
provider.only[] | Multi-provider allowlist | Single pin: only only[0] is honored; the rest are dropped and reported as ignored: provider.only[1:N]. Use provider.allow for a multi-provider allowlist |
onlist/auto / openrouter/auto | Per-prompt smart model selection | One static platform-default model, not per-prompt selection (422 on endpoints that do not support it) |
usage.cost | Returned in the response body | Not in the response body. Cost is out of band: read per-request cost from the usage log, which also names the provider that served it |
stream_options.include_usage | Optional; you set it | Forced to true when streaming (a terminal usage chunk is required for platform settlement) |
Privacy (zdr, data_collection) | Routes to matching providers | Routes to matching listings, same as OpenRouter, on the seller's own declaration (not platform-verified). Nothing eligible returns 503 no_zdr_provider_available / no_provider_without_data_collection; a bad data_collection value returns 422 |
| Rate-limit headers | X-RateLimit-* headers | No X-RateLimit-* headers. Only Retry-After on 429/503 |
/v1/moderations | Available | Not yet available (returns 502) |
/v1/audio/transcriptions, /v1/audio/translations | Available | Not yet available (return 501) |
plugins and the :online model suffix | Web search / file parsing | Not supported. Plugins are a no-op; :online is stripped from the id and reported in X-Onlist-Warnings |
min_p, top_a, repetition_penalty | Forwarded as sampling extensions | Dropped. These (and other unknown top-level body keys) are silently removed, with no X-Onlist-Warnings entry for those top-level drops |
require_parameters and quantizations are likewise accepted but not implemented
(silently ignored, with a warning). For the full provider field reference and how to
read X-Onlist-Warnings, see Provider routing.
Account and key endpoints
The management surface is OpenRouter-compatible too: same paths, same envelopes, so a script written against OpenRouter works after changing the host.
| Endpoint | OpenRouter | Onlist |
|---|---|---|
GET /api/v1/key | Inference or management key | Same |
GET /api/v1/credits | Management key | Same |
| `GET | POST /api/v1/keys` | Management key |
| `GET | PATCH | DELETE /api/v1/keys/{hash}` |
GET /api/v1/generation | id = x-generation-id | id = the X-Oneapi-Request-Id response header |
GET /api/v1/activity | Management key | Same, last 30 complete UTC days (today excluded) |
POST /api/v1/auth/keys/code | Session | Same. data.id is the authorization code itself |
POST /api/v1/auth/keys | Unauthenticated PKCE exchange | Same. user_id in the response is always null |
POST /api/v1/credits/coinbase | Crypto top-up | Not implemented. Top up in the dashboard |
Divergences worth knowing before you port a script:
| Field | OpenRouter | Onlist |
|---|---|---|
| Management key prefix | Created in the OR account | mgmt_..., created under Management keys |
limit_reset: "monthly" | Supported | 400. Onlist has no calendar-month window and will not approximate a money cap |
limit_reset: "weekly" | Resets Monday 00:00 UTC | Resets Thursday 00:00 UTC (the rolling 7-day budget window) |
usage_weekly | — | ISO week (Monday, UTC) — it is a log aggregate, not the billing counter |
rate_limit on a key | Deprecated placeholder | { requests: -1, interval: "", note: "deprecated" } — Onlist has no per-key request-rate limit |
byok_*, external_user, creator_user_id, workspace_id | Meaningful | Present for compatibility, always 0 / null / "default" |
| Reachable hosts | Global | https://onlist.io only — api.onlist.net relays inference, not management |
See Management API for the full reference.
The three that change behavior most
Three differences will surprise you if you assume OpenRouter behavior:
provider.onlyis a single pin. Only the first slug is routed to; additional entries are dropped and reported inX-Onlist-Warnings. If you relied on a multi-provider allowlist, switch toprovider.allow. See Provider routing.autois a static default, not smart selection.onlist/autoandopenrouter/autoresolve to one fixed platform-default model. If your code expects per-prompt model picking, name a specific model instead. See Models.usage.costis out of band. Cost is not in the response body. Read per-request cost from the usage log, which also names the provider that served the request. See Usage & cost and Errors.
Next steps
- Provider routing: Every provider field, single-pin semantics, and how to read X-Onlist-Warnings.
- Privacy & data handling: The provider self-attestation model, and what zdr and data_collection actually do.
- Usage & cost: Token usage fields, and reading out-of-band cost and provider from the usage log.
- Errors: The error envelope, string code slugs, and the code-to-HTTP table.
- Management API: Management keys, the key CRUD endpoints, activity, and OAuth PKCE.