Skip to content

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-Key value 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. Use requestId (also echoed on the x-request-id response 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.