OpenRouter Access Setup (OpenAI-compatible endpoint)
Configure access to models that are gated by waitlists at their native providers (Moonshot, DeepSeek, Zhipu) through OpenRouter's OpenAI-compatible API. One key unlocks all listed models — no per-provider waitlists.
OpenRouter exposes a drop-in OpenAI Chat Completions endpoint:
BASE_URL = https://openrouter.ai/api/v1
AUTH = Bearer $OPENROUTER_API_KEY
Any OpenAI-compatible client works by pointing base_url at OpenRouter and
reading the key from the environment.
Target models
| OpenRouter slug | Native provider | Notes |
|---|---|---|
moonshotai/kimi-k3 |
Moonshot (Kimi) | Kimi-K3, often waitlisted at platform.moonshot.cn |
deepseek/deepseek-v4-pro, deepseek/deepseek-v4-flash |
DeepSeek | V4 ships as separate pro/flash slugs — plain deepseek/deepseek-v4 does not exist and returns 404 |
z-ai/glm-5.2, z-ai/glm-4.6 |
Zhipu (GLM/Z.AI) | GLM family; z-ai/* is the OpenRouter namespace for Zhipu |
Slugs evolve, and a retired one fails only at request time. Check against the live catalogue first — it is public and needs no key:
curl -sS https://openrouter.ai/api/v1/models \
| python3 -c 'import json,sys; print("\n".join(sorted(m["id"] for m in json.load(sys.stdin)["data"] if sys.argv[1] in m["id"])))' moonshotai/
Pass the vendor prefix you care about (moonshotai/, deepseek/, z-ai/) — or
a full slug to confirm one exists. Empty output means the slug is gone.
Pricing is in the same response (pricing field) and on https://openrouter.ai/models.
Security contract (hard rule)
- The key lives ONLY in an environment variable:
OPENROUTER_API_KEY. - Never write the key into a repo file, a committed
.env, a command, or chat output. Do NOTecho "$OPENROUTER_API_KEY". - Persist it in the user's shell profile (
~/.zshrc/~/.bashrc) or a secret manager that exports it into the shell environment — never inside the project tree (the project tree is git-tracked and would leak the secret). - This skill never creates a per-project key file (unlike
.gh-account/.gcloud-account). There is exactly one OpenRouter key, and it is global.
Input
$ARGUMENTS
Optional model slug to smoke-test (e.g. z-ai/glm-4.6). If omitted, the smoke
test uses moonshotai/kimi-k3.
Workflow
Step 1: Check whether the key is already in the environment
if [ -n "$OPENROUTER_API_KEY" ]; then
echo "OPENROUTER_API_KEY is set (length ${#OPENROUTER_API_KEY})"
else
echo "OPENROUTER_API_KEY is NOT set"
fi
Report only whether it is set and its length — never the value.
Step 2: Obtain a key (user does this interactively) if missing
Direct the user to:
- Sign in at https://openrouter.ai
- Open Keys → Create Key
- Copy the key (starts with
sk-or-v1-…)
The agent cannot create the key on the user's behalf — it is an interactive, account-bound action.
Step 3: Persist the key in the shell profile (env, not code)
Tell the user to add to ~/.zshrc (or ~/.bashrc):
export OPENROUTER_API_KEY='sk-or-v1-…'
Then source ~/.zshrc (or start a new shell). The agent must not edit the
user's shell profile without explicit confirmation, and must never write the key
into any file under the project tree.
Verify it landed (new shell or after source):
[ -n "$OPENROUTER_API_KEY" ] && echo "set (length ${#OPENROUTER_API_KEY})" || echo "missing"
Step 4: Smoke-test the endpoint
Resolve the model to test: use the argument if given, otherwise default to
moonshotai/kimi-k3.
: "${OPENROUTER_API_KEY:?OPENROUTER_API_KEY is not set — see Step 3}"
curl -sS https://openrouter.ai/api/v1/chat/completions \
-H "Authorization: Bearer $OPENROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "<MODEL_SLUG>",
"max_tokens": 16,
"messages": [{"role": "user", "content": "ping"}]
}'
A 200 with a JSON choices array means access works. A 401 means the key is
wrong/revoked; 402 means insufficient credits; 404 means the slug is wrong
or unavailable (verify on openrouter.ai/models).
Step 5: Wire into OpenAI-compatible clients
The key is read from env at runtime; nothing is hardcoded.
Python (openai SDK):
from openai import OpenAI
client = OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key=__import__("os").environ["OPENROUTER_API_KEY"], # env, not code
)
resp = client.chat.completions.create(
model="z-ai/glm-4.6",
messages=[{"role": "user", "content": "ping"}],
)
curl / any HTTP client: Authorization: Bearer $OPENROUTER_API_KEY against
https://openrouter.ai/api/v1/chat/completions (Step 4 command is the template).
opencode / Codex / other agent runtimes: configure a provider with
base_url: https://openrouter.ai/api/v1 and api_key resolved from the
OPENROUTER_API_KEY env var (not a literal). Runtime-specific config lives in
~/.config/..., outside this repo.
Step 6: Confirm
Show summary:
OPENROUTER_API_KEY: set (length N) / missing- Smoke test (
<MODEL_SLUG>): OK / failed (status) - Endpoint:
https://openrouter.ai/api/v1(OpenAI-compatible) - Key stored: env only (shell profile) — not in any repo file
Common errors
| Error | Fix |
|---|---|
401 Invalid API key |
Key wrong/revoked — recreate at openrouter.ai/keys |
402 You have insufficient credits |
Top up credits at openrouter.ai; keys are prepaid-balance, not unlimited |
404 model not found |
Slug wrong/retired — confirm at openrouter.ai/models; slugs change (e.g. -latest suffixes) |
429 Rate limit |
Slow down; check openrouter.ai dashboard for per-model RPM limits |
OPENROUTER_API_KEY: parameter null or not set |
Key not exported into the current shell — source ~/.zshrc or start a new shell |
| Key accidentally committed | Rotate it immediately at openrouter.ai/keys (old key is compromised); never re-paste into repo files |
When NOT to use
- You already have direct API access to the provider (Moonshot/DeepSeek/Zhipu native keys) and don't mind waitlists — native endpoints are fine.
- Cost-sensitive batch jobs — OpenRouter adds a margin; native provider keys are cheaper at volume.
- Streaming/low-latency prod workloads with strict SLAs — OpenRouter is a router and may add latency / failover hops; pin a native endpoint instead.
Notes
- OpenRouter keys are prepaid-balance (credits), not subscriptions. Monitor spend at https://openrouter.ai/credits.
- Optional headers (
HTTP-Referer,X-Title) make the app recognizable in openrouter.ai/logs but are not required.