Skip to documentation content

Consent

Record consent choices and sync consent state across devices with the CookieBeam REST API.

Two groups of endpoints handle consent: the unversioned ingestion endpoint that the banner runtime posts to when a visitor makes a choice, and key-authenticated endpoints that read and write a subject's consent state across devices.

POST /api/consent/log

This unversioned route is what the generated banner script calls. It is not part of the key-authenticated v1 API: requests carry no API key, are rate limited per client IP (1,000 requests per minute), and are accepted cross-origin so the banner can post from your domain. Each request is validated against the banner's authorized domain list — a post from a domain the banner is not configured for is rejected with HTTP 403.

The payload uses the public Banner ID (the one in the loader script URL), a preferences object of category booleans, a method (consent_given, consent_changed, consent_default, accept_all, or accept_necessary), and an ISO 8601 timestamp. Optional fields cover geo resolution (country, region, matchedRuleId), the privacy signal detected at choice time, and US opt-out purposeChoices.

Server-side, the route resolves the public ID to the banner, clamps implausible client timestamps, and stores a privacy-minimized record: raw IP and user agent are never written, and the referrer is redacted to the banner's configured telemetry level. A successful write returns HTTP 200 with success: true, the stored consentLogId, and — for explicit visitor choices — a short-lived managePreferences URL the banner can offer for later changes. Explicit choices also trigger the consent.given webhook event; machine-written default-state rows do not.

You normally never call this route yourself — the published banner script handles it, including the exact payload shape, which evolves with the runtime. It is documented here so you can recognize and troubleshoot the banner's network traffic.

The subject endpoints store a visitor's consent state server-side so it can follow them across devices. They identify a subject by three fields, sent in every request body:

FieldTypeNotes
bannerIdstring (UUID)The banner's public Banner ID — the one that appears in the loader script URL, not the internal ID returned by the banners endpoints
namespacestringOptional partitioning label, 1–64 characters; defaults to default
subjectIdstringYour own opaque identifier for the visitor, 1–256 characters. Do not use an email address; the request is rejected if the value contains @

Request bodies are limited to 20 KB. These endpoints require a plan that includes cross-device consent; other plans receive a plan_required error (HTTP 403).

Read a subject's state

POST /api/v1/consent/subjects/resolve

Requires the consent:read scope.

{
  "bannerId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "namespace": "default",
  "subjectId": "account-81f3c2"
}

Returns the stored snapshot, or found: false when none exists:

{
  "data": {
    "found": true,
    "consentState": { "schemaVersion": 2, "categories": ["necessary", "analytics"] },
    "protocolVersion": 1,
    "version": 3,
    "clientUpdatedAt": "2026-08-13T11:58:00.000Z",
    "updatedAt": "2026-08-13T11:58:01.000Z",
    "expiresAt": "2026-11-13T11:58:01.000Z"
  },
  "version": "v1"
}

Write a subject's state

PUT /api/v1/consent/subjects/state

Requires the consent:write scope and a live key. The body adds three fields to the subject reference:

FieldTypeNotes
baseVersionintegerThe version you last read, or 0 for a first write. A mismatch means another writer updated the record first
consentStateobjectThe canonical consent state (below)
expiresAtstring (ISO 8601)Optional expiry for the stored record
protocolVersionintegerOptional; currently 1

The canonical consentState object:

FieldTypeNotes
schemaVersionintegerMust be 2
categoriesstring[]Accepted category names; at most 50 entries, 1–100 characters each
revisionintegerConfiguration revision the choice was made against, 0–1,000,000
acceptTypestringall, custom, or necessary
consentIdstring (UUID)Identifier of the consent record
consentTimestampstring (ISO 8601)When the first choice was made
lastConsentTimestampstring (ISO 8601)When the latest choice was made; cannot precede consentTimestamp
languageCodestring1–20 characters
servicesobjectOptional map of category names to accepted service names
jurisdictionsstring[]Optional jurisdiction tags
gpcRespectedbooleanOptional; records whether a Global Privacy Control signal was honored
usOptOutChoicesobjectOptional booleans: saleSharing, targetedAdvertising, sensitiveData, profiling
isPendingbooleanOptional; marks a pending opt-in default
isDefaultStatebooleanOptional; marks a jurisdiction default rather than an explicit choice
collectedViastringOptional; one of banner, preferences, api, default_optin, default_optout, default_notice, hidden_autogrant

A successful write returns HTTP 200 with success: true plus the stored snapshot in the same shape as the resolve response.

If baseVersion does not match the stored version, the write is rejected with HTTP 409 and the code version_conflict; the response includes the current snapshot so you can merge and retry:

{
  "data": {
    "error": "Consent version conflict",
    "code": "version_conflict",
    "current": {
      "consentState": { "schemaVersion": 2, "categories": ["necessary"] },
      "protocolVersion": 1,
      "version": 4,
      "clientUpdatedAt": "2026-08-13T11:59:00.000Z",
      "updatedAt": "2026-08-13T11:59:01.000Z",
      "expiresAt": "2026-11-13T11:59:01.000Z"
    }
  },
  "version": "v1"
}

Delete a subject's state

DELETE /api/v1/consent/subjects/state

Requires the consent:write scope and a live key. Send only the subject reference fields (bannerId, namespace, subjectId). The response reports how many records were deleted:

{
  "data": {
    "success": true,
    "deleted": 1
  },
  "version": "v1"
}

Use this to honor a visitor's request to forget their stored consent state.

Subject endpoint errors

HTTP statusCodeCause
400invalid_requestThe body failed validation or is not valid JSON
403plan_requiredThe team's plan does not include cross-device consent
404not_foundNo active banner with that public Banner ID on this team
409version_conflictbaseVersion did not match the stored version (write only)
413invalid_requestThe body exceeded 20 KB
500internal_errorUnexpected server error
503identity_unavailableIdentity hashing is temporarily unavailable; retry later