Skip to content

Authentication and scopes

Every protected endpoint of the Tarno 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 "$TARNO_BASE_URL/v1/brands?limit=1" -H "X-API-Key: $TARNO_API_KEY"

The key is an opaque string issued by the operator (placeholder <API_KEY> 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 TARNO_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.
watch:read Watchlist reads: GET /v1/watch, GET /v1/watch/:id, GET /v1/watch/:id/hits.
watch:write Watchlist writes: POST /v1/watch, PATCH /v1/watch/:id, DELETE /v1/watch/:id.

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 watch:* scopes additionally require the key to have an owning org (consumerId); see the Watchlists guide for tenancy, dual delivery, and webhook signing.

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": "..." } }

429 rate_limited

There are two rate limits, both additive to the monthly quota, and both return 429 with a Retry-After header (seconds to wait):

  • Per-key burst (code: rate_limited) — a second tier keyed on the key hash that caps a single tenant: by default 300 requests every 60 s. It applies identically on REST and on the MCP (both transports share the same counter), is independent of the client IP (so a rotating IP pool cannot dodge it), and does not change the /v1 contract schema — it is a purely additive new 429 condition.

```text HTTP/1.1 429 Too Many Requests Retry-After: 42

{ "error": { "code": "rate_limited", "message": "Too many requests (per-key burst limit)", "requestId": "..." } } ```

  • Per-IP flood defense (code: rate_limited) — a limit keyed on the client IP (default 100 requests/minute) that blunts a pre-auth flood (rotating keys). It is independent of the per-key tier.

Consumer action: honor the Retry-After and retry after that many seconds. The monthly quota (quota_exceeded) and both rate limits (rate_limited) apply identically on REST and on the MCP, since both transports authenticate against the same keys and share the same counters.

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.

CORS and browser access

IRIS is server-side-only by default: the X-API-Key is a server-side credential and must never be embedded in a browser client. Therefore, by default the API emits no Access-Control-Allow-Origin header — a third-party browser fetch cannot read the responses. This is the default posture and changes nothing relative to prior versions.

Cross-origin browser access is opt-in per deploy via the IRIS_CORS_ALLOWLIST environment variable (a comma-separated list of exact origins):

IRIS_CORS_ALLOWLIST=https://app.tarno.cl,https://docs.tarno.cl
  • Empty / unset → no CORS (no Access-Control-Allow-Origin header) — the default no-op.
  • With origins → only those exact origins receive the Access-Control-Allow-Origin header (never a wildcard, never arbitrary Origin reflection); no credentials are sent (credentials:false). A preflight OPTIONS from those origins is answered with the allow headers.

The MCP server does not use CORS. MCP is a server-to-server transport (AI agents call it over the wire, not a browser fetch), so CORS — a browser safeguard — does not apply there.

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.