Skip to documentation content

Errors and rate limits

Error codes, HTTP statuses, request ids, rate-limit tiers, and retry guidance for the CookieBeam REST API.

Error envelope

Errors use the same envelope as successful responses. The data object carries a human-readable error message, a machine-readable code, an optional details value, and the requestId of the call:

{
  "data": {
    "error": "API key is missing required scope(s): banners:write",
    "code": "missing_scope",
    "requestId": "9b2e5f0c-8d7a-4e0b-9c1d-2f3a4b5c6d7e"
  },
  "version": "v1"
}

Match on code; the wording of error may change. Validation errors add details describing each invalid field. Some endpoints add their own fields (a consent state conflict returns current).

Request ids

Every /api/v1 response carries an X-Request-Id header, and every error repeats it as data.requestId. Send your own X-Request-Id (a UUID) to correlate a call with your logs; any other value is ignored and a new id is generated. Quote the id when you contact support.

Machine-readable description

The OpenAPI 3.1 document at /api/v1/openapi.json is generated from the same contracts the endpoints validate against. It lists every operation with its scopes, request and response schemas, and the error codes each status can carry.

Error codes

Authentication and scope failures (see Authentication):

HTTP statusCodeMeaning
400invalid_requestThe request body failed validation, or is not JSON
401invalid_keyThe API key is malformed, unknown, or has the wrong environment prefix
401revoked_keyThe API key has been revoked
401expired_keyThe API key has expired
401unauthorizedThe endpoint requires an API key and none was sent
403plan_requiredThe team's plan does not include the required feature, or a plan limit was reached
403missing_scopeThe key lacks a required scope; the message names the missing scopes
403test_key_read_onlyA test key was used for a write or publish operation
403key_restrictedA key restricted to specific banners was used to create a banner, to record a form consent proof without one of its banners, or to erase form consent proofs
404not_foundThe referenced banner or consent subject was not found, or is outside the key's banner restriction
409version_conflictA consent state write carried a stale baseVersion; the response includes the current snapshot
409idempotency_in_progressA request with the same Idempotency-Key is still running; retry after Retry-After seconds
422idempotency_key_reusedThe Idempotency-Key was already used with a different request
413payload_too_largeThe request body exceeded 64 KB (consent log)
413invalid_requestThe request body exceeded 20 KB (consent subject endpoints)
429rate_limitedA rate limit was exhausted; see below
500internal_errorUnexpected server error
503identity_unavailableConsent identity hashing is temporarily unavailable; retry later

Rate limits for API keys

API-key requests are limited per key over a fixed 15-minute window. The limit depends on the team's plan:

PlanRequests per 15 minutes
Free50
Starter200
Professional500
Business2,000

A plan the server cannot map to a tier falls back to the Free limit.

Responses that pass key authentication and reach the rate limiter carry these headers:

  • X-RateLimit-Limit — the key's limit for the window.
  • X-RateLimit-Remaining — requests left in the current window.
  • X-RateLimit-Reset — when the window resets, as a Unix timestamp in milliseconds.
  • RateLimit — the same state as an IETF structured field: "default";r=<remaining>;t=<seconds until reset>.
  • RateLimit-Policy — the quota and window: "default";q=<limit>;w=<window seconds>.

This includes HTTP 429 rejections. It does not include requests rejected before rate limiting runs — entitlement, scope, and test-key failures (and malformed, revoked, or expired keys) return without these headers — and some server-error paths also skip them. Treat the headers as present on rate-limiter outcomes, not as a universal invariant.

When the limit is exhausted the API returns HTTP 429:

{
  "data": {
    "error": "Rate limit exceeded",
    "code": "rate_limited",
    "requestId": "9b2e5f0c-8d7a-4e0b-9c1d-2f3a4b5c6d7e"
  },
  "version": "v1"
}

The response includes a Retry-After header with the number of seconds to wait before the next request.

Other rate limits

Two limits apply independently of API keys:

  • Requests to the banner endpoints that arrive without an API key (dashboard sessions and other browser traffic) are limited to 100 requests per 15 minutes per client IP.
  • POST /api/consent/log (the unversioned banner ingestion route) is limited to 1,000 requests per minute per client IP.
  • Failed authentication attempts are limited to 10 per minute per client IP; beyond that, further attempts return HTTP 429 with rate_limited and a Retry-After header.

Retry guidance

  • On HTTP 429, wait at least the number of seconds in Retry-After before retrying. Do not retry in a tight loop — repeated bursts just consume the next window.
  • On HTTP 5xx, retry with exponential backoff.
  • Reads (GET and the consent resolve endpoint) are safe to retry as-is.
  • For consent state writes, retry a 409 version_conflict by re-reading the subject, merging against the returned current snapshot, and writing again with the new version as baseVersion.