Authentication
One key, two headers that both work, and the same rules on REST and MCP.
The header
Either of these authenticates a request. They are equivalent; pick whichever your HTTP client makes easier.
x-api-key: nai_...
Authorization: Bearer nai_...
The same two work on https://api.unzoi.com/mcp. MCP clients that let you set arbitrary headers usually use
x-api-key; ones that only offer a bearer field use Authorization.
curl -s "https://api.unzoi.com/search?q=inflation" -H "x-api-key: $UNZOI_KEY"
curl -s "https://api.unzoi.com/search?q=inflation" -H "Authorization: Bearer $UNZOI_KEY" What does not need a key
Four endpoints are public, deliberately. An agent deciding whether this API is worth signing up for has to be able to read what it does before it has a key:
/openapi.json— the full OpenAPI 3.1 document/mcp/tools.json— the MCP tool schemas/llms.txt— the whole surface as plain text/docs/search-recipes.md— worked examples, written for a model
Everything else answers 401 without a key.
Handling keys
A key is shown once, at creation. Only its hash is stored, so a lost key is rotated, not recovered — rotation issues the new key before revoking the old one, so there is no window where neither works.
- Never put a key in a browser. There is no CORS-credentialed mode and no per-origin restriction: a key in front-end JavaScript is a key anyone can read and spend. Proxy through your own server.
- One key per deployment, not per developer. Every key on a tenant shares the plan, quota and rate limit, so separate keys buy you revocation granularity and usage attribution, not separate budgets.
- Short-lived keys exist. The console can issue a key with a TTL of up to 24 hours — useful for a demo, a CI run, or handing an agent a credential you do not intend it to keep.
How a request is refused
Four things can turn a request away, and they mean different things.
| Status | Meaning |
|---|---|
| 200 | Success |
| 401 | Missing or invalid API key |
| 402 | Account suspended (subscription canceled or payment failed) |
| 429 | Rate limit exceeded, or the monthly quota is exhausted on a key that stops at its quota rather than billing overage. `retry-after` says how long to wait. |
Order matters: the key is checked first, then suspension, then the per-minute rate, then the
monthly quota. A 429 therefore never means "your key is wrong", and a 401 never means
"you are over budget".
On MCP, two of these move
An MCP client that receives an HTTP error status for a tool call concludes the server has gone away, and stops. That is the correct reading for a missing key — a client with no credential cannot open a session at all — but it is the wrong reading for an account that simply needs paying.
So the MCP endpoint splits them:
| Condition | REST | MCP |
|---|---|---|
| Missing or invalid key | 401 | 401 — the session never opens |
| Rate limit exceeded | 429 + retry-after | 429 + retry-after — transient, retry the same call |
| Account suspended | 402 | A tool error on an open session |
| Monthly quota exhausted | 429 | A tool error on an open session |
In the last two cases initialize and tools/list still succeed, and the refusal arrives on
the call that actually costs something, carrying a machine-readable code the agent can act on.
MCP errors shows the exact shape.
What every response tells you
Authenticated responses carry your standing as headers, so backing off correctly never needs a second request.
| Header | Meaning |
|---|---|
| x-plan-history-days | How far back this plan may query. Absent means the full archive. |
| x-quota-limit | Included requests this calendar month. Absent when unlimited. |
| x-quota-remaining | Requests left in the monthly allowance. |
| x-quota-reset | Seconds until the quota period resets. |
| x-quota-used | Requests used this month, from the durable counters. |
| x-ratelimit-limit | Requests per minute allowed by this plan. |
| x-ratelimit-remaining | Requests left in the current minute. |
| x-ratelimit-reset | Seconds the bucket refills over. |
Limits and plans covers what to do with each of them, and
GET /account returns the same figures as a body.
Keys are issued and revoked at console.unzoi.com.