OpenRouter API Key: How to Create and Secure It
TL;DR
How to create an OpenRouter API key, call it from curl, Python or JS, cap and rotate it safely, and decode 401, 402 and 403 errors.
I have issued more OpenRouter keys than I can count — for my own generation pipelines and for people setting up AI SEO Writer. Almost every support thread about this ends in one of two places: the secret was never saved when it was shown, or the balance quietly hit zero and every call started failing.
Below: what the key gives you, how to create one, how to call it from curl, Python and JavaScript, the limits attached to it, and what each authentication error code means.
What an OpenRouter API key gives you
OpenRouter is an aggregator. One account and one credential get you models from OpenAI, Anthropic, Google, Meta, Mistral, DeepSeek and other providers — no signing up with each vendor, no drawer full of secrets.
The docs put it plainly: keys on OpenRouter are more powerful than keys used directly against a model API, because they carry a per-key credit limit and can be issued through an OAuth flow. A [Gemini key](/en/blog/api-klyuch-gemini) is a credential for one vendor's models; an OpenRouter key is a budgeted credential for a whole catalogue, and you can hand a separate one to every app you run.
The second advantage is protocol compatibility. OpenRouter speaks the OpenAI API shape, so the official OpenAI SDK works against it with no code rewrite — only the base URL and the key change.
How to create an OpenRouter API key
The key itself is free to issue and no card is required to get one.
1. **Sign up** at openrouter.ai. Google, GitHub and plain email all work. 2. **Open the keys page.** It lives at `openrouter.ai/keys`; the settings route `openrouter.ai/settings/keys` reaches the same list. Some builds of the interface label the menu item "API Keys" — same page. 3. **Click Create Key and give it a name.** The name is only for you. I name mine after where they run: `prod-backend`, `local-test`, `seo-pipeline`. 4. **Set the optional credit limit.** Do this every time, even on a personal account. A key capped at five dollars costs you five dollars if it leaks, not your whole balance. 5. **Copy the secret immediately.** The plaintext value is returned exactly once, at creation. After that the list shows only the name, the date, a masked label such as `sk-or-v1-au7...890`, and the spend.
There is no separate "free key" and "paid key". You create one key; whether a call costs money depends entirely on which model you name in the request.
If you lose the secret, nothing is broken — delete that key and create a new one.
Calling the API with your key
Authentication is a Bearer token in the `Authorization` header. The bare curl version, with no libraries at all:
curl https://openrouter.ai/api/v1/chat/completions \
-H "Authorization: Bearer $OPENROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-5.2",
"messages": [{"role": "user", "content": "Hello!"}]
}'
Python, using the official OpenAI SDK. Two lines differ from a plain OpenAI setup — the base URL and the key:
import os
from openai import OpenAI
client = OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key=os.environ["OPENROUTER_API_KEY"],
)
response = client.chat.completions.create(
model="openai/gpt-5.2",
messages=[{"role": "user", "content": "Hello!"}],
)
JavaScript is the same idea:
import OpenAI from 'openai';
const openai = new OpenAI({
baseURL: 'https://openrouter.ai/api/v1',
apiKey: process.env.OPENROUTER_API_KEY,
});
There is also a first-party SDK, `@openrouter/sdk`, which reads `OPENROUTER_API_KEY` from the environment on its own. Both work; the OpenAI-compatible path is the one to pick when migrating an existing app.
A wrong base URL is a classic silent failure here. Send an OpenRouter key to `api.openai.com` and the key is perfectly valid — just not for that host.
The attribution headers: HTTP-Referer and X-OpenRouter-Title
Two optional headers control whether your app shows up in OpenRouter's public rankings and analytics:
- `HTTP-Referer` — your app's URL. This one is required for attribution; without it no app page is created and your usage stays anonymous.
- `X-OpenRouter-Title` — the display name shown in rankings. The older `X-Title` is still accepted for backwards compatibility. On its own it does nothing; it has to be paired with `HTTP-Referer`.
Neither header affects billing or access. If you would rather stay invisible, simply do not send them.
Inspecting a key: GET /api/v1/key
You can ask the API what a given key is allowed to do. One request, no libraries:
curl https://openrouter.ai/api/v1/key \
-H "Authorization: Bearer $OPENROUTER_API_KEY"
The response describes the key, not your whole account: `label` (masked), `limit` and `limit_remaining` for the per-key credit cap, `limit_reset` if the cap refills on a schedule, `usage` by day, week and month, and `is_free_tier`. The payload also carries a legacy `rate_limit` object the docs flag as deprecated and safe to ignore.
I wire this call into monitoring on anything that runs unattended. Watching `limit_remaining` drift toward zero beats discovering it through a wall of failed jobs.
Limits attached to the key
OpenRouter enforces two distinct kinds of limit, and confusing them sends you fixing the wrong thing.
**Credit limits** govern how much you can spend. They come from your account balance and from the optional per-key cap you set at creation. Exceeding either returns `402`. A negative account balance produces `402` even on free model variants.
**Rate limits** govern how many requests you make. Free model variants — IDs ending in `:free` — carry platform request caps, and Cloudflare DDoS protection blocks traffic that dramatically exceeds reasonable usage. Exceeding these returns `429`. The free-tier numbers get revised periodically, so read them off the limits page rather than off any article, this one included.
Worth knowing before you try to engineer around it: extra keys and extra accounts do not raise your rate limits — capacity is governed globally. Different models do carry different limits, so spreading load across models is what actually works.
When OpenRouter itself rate-limits you, the error response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset`, and often a standard `Retry-After`. Successful responses carry none of those. Honour `Retry-After` and back off exponentially — the OpenAI, Anthropic and OpenRouter SDKs already do.
Storing and rotating keys
Treat the string like a password, because that is what it is. Once it lands in a public repository, a screenshot or a chat message, consider it compromised — deleting the commit afterwards changes nothing.
OPENROUTER_API_KEY=sk-or-v1-xxxxxxxxxxxxxxxxxxxxxxxx
Environment variables, a password manager, or a proper secret manager if the project runs in the cloud. Add `.env` to `.gitignore` before the first secret goes into it, not after.
OpenRouter is a GitHub secret scanning partner and will email you if it detects an exposed key. If that happens, delete the compromised key from the keys page and create a replacement — revocation takes effect immediately.
To issue or rotate keys programmatically — a SaaS provisioning a key per customer, or a compliance rule forcing regular rotation — there is a separate **Management API key**, created at `openrouter.ai/settings/management-keys`. It authenticates the `/api/v1/keys` endpoints for listing, creating, updating and deleting keys, and deliberately cannot call the completion endpoints. Keys made this way can carry a credit limit and a `limit_reset` schedule such as `daily` or `monthly`.
My baseline habits: one key per project, a credit cap on each, an occasional look at the spend. A key burning credits at three in the morning, when nothing of mine is running, is reason enough to rotate it.
Authentication errors: 401, 402, 403
Each failure code points at a different problem, so read the number before you start guessing.
**401 — invalid credentials.** The key is missing, malformed, disabled, or the OAuth session expired. In practice this is usually a copy-paste artefact: a leading space or a line break makes the server see a different string. It also fires when the value is not an OpenRouter key at all — the string must start with `sk-or-v1-`. The same [401 debugging logic applies to OpenAI keys](/en/blog/invalid-api-key-openai), and the checklist transfers cleanly.
**402 — insufficient credits.** Either the account balance is at or below zero, or the per-key cap is exhausted; `limit_remaining` from `GET /api/v1/key` tells those two apart. A paid model bills from the very first request, so this can appear immediately on a fresh account.
**403 — forbidden.** Usually not a key problem at all. It means insufficient permissions, a guardrail block, or input flagged by moderation; the `error.metadata` object says which, down to the flagged text segment.
**429 — rate limited.** The 402-versus-429 distinction is the one that matters most: 402 is money, 429 is frequency. A 429 can also originate upstream, from the provider serving your request rather than from OpenRouter, in which case `error.metadata.provider_code` carries their original code.
Billing itself is in US dollars and the payment methods available to you depend on your region, so check the credits page for what applies where you are.
Where this fits if you are not building from scratch
If you use OpenRouter through a product rather than your own code, the key normally goes into a settings field once and is stored encrypted from then on. That is how AI SEO Writer handles it: you paste the key into account settings, and the [SEO article generator](/en/generator-seo-statej) uses it without you touching it again.
Bringing your own key makes sense when you want to pay the provider directly, pick your own models, or move past shared request caps. The sensible order either way: create the key, cap it, store it in an environment variable, and verify it with one `GET /api/v1/key` call before wiring it into anything real.
Automate SEO publishing with SEO Writer
AI writes articles, publishes to CMS, fills meta tags — without your involvement
Start for free →Read also
Invalid API Key OpenAI: Fix the 401 Error
Getting a 401 invalid_api_key from OpenAI? Here is the fast checklist, the real causes, a curl test, and how 401 differs from 403 and 429.
OpenRouter Free Models: Limits and Trade-offs
What OpenRouter free models really give you: current rate limits, what your prompts pay for, how to pick one, and when to move to paid.
How to Get an OpenAI API Key (ChatGPT API)
Create an OpenAI API key step by step: project vs user keys, billing tiers, curl and Python calls, safe storage, and spend limits.
SEO Writer integrations