Authentication and scopes
Every protected endpoint of the IRIS read contract is gated by an API key. This guide
covers how to present the key, the two scopes, the key lifecycle, and the exact 401 / 403 /
429 semantics — all matching the live handlers.
The X-API-Key header
Send your key in the X-API-Key request header on every call to a protected route:
curl -sS "$IRIS_BASE_URL/v1/brands?limit=1" -H "X-API-Key: $IRIS_API_KEY"
The key is an opaque string issued by the operator (placeholder iris_xxx in these docs). The
server verifies it on every request; the raw key is never logged.
The MCP server uses the same X-API-Key header, sent per request over the Streamable HTTP
transport — see the MCP guide. (For the stdio transport, the key is read from
the IRIS_API_KEY environment variable instead.)
Public routes that need no key
/health, /docs, and /metrics are the only paths that work without a key. Everything
under /v1 requires one.
Scopes
A key carries a set of scopes that determine which parts of the contract it may read:
| Scope | Grants access to |
|---|---|
brands:read |
The brand contract: GET /v1/brands, GET /v1/brands/:nroSolicitud. |
insights:read |
The operational contract: GET /v1/freshness, GET /v1/sync-runs. |
An empty scopes array is unrestricted — a key issued with no scopes is granted all read
scopes. A key with a non-empty scopes array is granted only the scopes listed; calling a
route whose required scope is absent is rejected with 403 forbidden.
The MCP tools follow the same rule: both search_brands and get_brand_detail require
brands:read (or an unrestricted, empty-scope key).
Key lifecycle (operator-managed)
Keys are created, rotated, revoked, and expired by the operator via the admin CLI. As an integrator you do not manage keys yourself; you request changes from your operator.
- Rotate — the operator issues you a new key and decommissions the old one. Switch the
X-API-Keyvalue your client sends; no code change beyond the secret. - Revoke — a revoked key stops working immediately: subsequent calls return
401 unauthorized, exactly as for an unknown key. - Expiry — a key may carry an expiry date. Once past expiry it is treated as invalid and
returns
401 unauthorized. Ask your operator to reissue before expiry to avoid downtime.
Each key also belongs to a consumer and carries a monthly quota (see below).
Status semantics
These are the exact outcomes the auth and quota layers produce:
401 unauthorized
Returned when the key is missing, malformed, unknown, or revoked/expired.
{ "error": { "code": "unauthorized", "message": "Missing or invalid API key", "requestId": "..." } }
403 forbidden
Returned when the key is valid and live, but lacks the scope the route requires. The message names the missing scope:
{ "error": { "code": "forbidden", "message": "API key lacks required scope: insights:read", "requestId": "..." } }
429 quota_exceeded
Returned when the key's monthly quota is exhausted. Carries a Retry-After header with the
seconds until the quota window resets:
HTTP/1.1 429 Too Many Requests
Retry-After: 1209600
{ "error": { "code": "quota_exceeded", "message": "Monthly quota exceeded", "requestId": "..." } }
A separate short-term burst limit (default 100 requests/minute per key) also returns 429. Both
the burst limit and the monthly quota apply identically on REST and on the MCP, since both
transports authenticate against the same keys and share the same quota counter.
The uniform error envelope
{ error: { code, message, requestId } }is described in the getting-started guide. UserequestId(also echoed on thex-request-idresponse header) when reporting an issue to your operator.
See also
- Getting started — base URL, first call, pagination, error envelope.
- Connect your AI agent (MCP) — same key, same scopes, over MCP.
- The live OpenAPI reference at
/docs— per-endpoint security and schema.