I use OpenRouter every day. It is one OpenAI-compatible API in front of hundreds of models from many providers, which makes it the first thing I reach for when I want to compare two models on the same prompt without juggling five dashboards.
Nango is an open-source integrations platform: you describe a provider once in providers.yaml, and Nango handles the connect flow, credential storage and a proxy. OpenRouter was not in the catalog. So I opened NangoHQ/nango#7532 to add it.
The provider config itself is short. API key auth, sent as Authorization: Bearer ${apiKey}, base URL https://openrouter.ai/api, a credential pattern ^sk-or-v1-[a-zA-Z0-9]+$, docs pages, the official logo. The interesting part was one field: verification.
What verification is for
When a user pastes an API key into a connect form, Nango can call one endpoint of the provider to check that the key actually works before saving the connection. If the check passes on garbage, the user leaves the form happy and finds out the key is wrong at 2 a.m., when the first real sync fails.
So the verification endpoint has exactly one job: say yes to a real key and no to a fake one.
The obvious choice was wrong
The obvious candidate for an OpenAI-compatible API is GET /v1/models. Almost every provider has it, it is cheap, it does not spend tokens. Most integration catalogs use it for exactly this.
Before writing it into the YAML, I asked the endpoint directly:
curl -s -o /dev/null -w '%{http_code}' \
-H 'Authorization: Bearer sk-or-fake' \
https://openrouter.ai/api/v1/models
# 200
A fake key. 200. I tried again with a key that matches the real sk-or-v1- shape: 200. With no Authorization header at all: 200, and a JSON body listing 446 models when I checked today.
This is not a bug on OpenRouter's side. Their model list is public on purpose: you should be able to browse models and prices before you sign up. But it means /v1/models tells you nothing about the key. As a verification endpoint it would pass every single input, including an empty one.
The endpoint that actually checks the key
OpenRouter documents GET /v1/key, which returns information about the key that made the request: its limits and usage. That endpoint cannot answer without knowing who is asking.
curl -s -o /dev/null -w '%{http_code}' \
-H 'Authorization: Bearer sk-or-v1-0000fake' \
https://openrouter.ai/api/v1/key
# 401
curl -s -o /dev/null -w '%{http_code}' https://openrouter.ai/api/v1/key
# 401
With my real key the same call returns 200. Fake key 401, no key 401, real key 200. That is the whole contract a verification endpoint needs, so the PR uses /v1/key, and the PR description says why /v1/models was not used, so the next person does not "simplify" it back.
How I checked the rest
-
npx tsx scripts/validation/providers/validate.tspasses. I also deleted the logo on purpose and confirmed the validator fails withopenrouter SVG file not found, so I know the check is real and not just green. -
prettier --checkonproviders.yamlpasses. - What I did not run, and said so in the PR: a full local
docker compose upend-to-end connection and Mintlify's broken-link check.
In the same session I also opened #7533, adding Lambda Cloud. Both PRs are open as of today.
The takeaway
For any API-key integration, do not pick the verification endpoint by name. Pick it by behaviour: send a fake key, send no key, send a real key, and look at the three status codes. If the first two do not fail, the endpoint is not verifying anything. It takes three curls and about thirty seconds, which is less time than the 2 a.m. debugging session it prevents.
If you maintain an integrations catalog, it might be worth running that same three-curl test against every provider that verifies through a /models endpoint. I would be curious how many of them are public.
— Anton Dziatkovskii · github.com/tonydzi
Top comments (0)