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.
How the banner records a consent choice
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.
Cross-device consent state
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:
| Field | Type | Notes |
|---|---|---|
bannerId | string (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 |
namespace | string | Optional partitioning label, 1–64 characters; defaults to default |
subjectId | string | Your 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:
| Field | Type | Notes |
|---|---|---|
baseVersion | integer | The version you last read, or 0 for a first write. A mismatch means another writer updated the record first |
consentState | object | The canonical consent state (below) |
expiresAt | string (ISO 8601) | Optional expiry for the stored record |
protocolVersion | integer | Optional; currently 1 |
The canonical consentState object:
| Field | Type | Notes |
|---|---|---|
schemaVersion | integer | Must be 2 |
categories | string[] | Accepted category names; at most 50 entries, 1–100 characters each |
revision | integer | Configuration revision the choice was made against, 0–1,000,000 |
acceptType | string | all, custom, or necessary |
consentId | string (UUID) | Identifier of the consent record |
consentTimestamp | string (ISO 8601) | When the first choice was made |
lastConsentTimestamp | string (ISO 8601) | When the latest choice was made; cannot precede consentTimestamp |
languageCode | string | 1–20 characters |
services | object | Optional map of category names to accepted service names |
jurisdictions | string[] | Optional jurisdiction tags |
gpcRespected | boolean | Optional; records whether a Global Privacy Control signal was honored |
usOptOutChoices | object | Optional booleans: saleSharing, targetedAdvertising, sensitiveData, profiling |
isPending | boolean | Optional; marks a pending opt-in default |
isDefaultState | boolean | Optional; marks a jurisdiction default rather than an explicit choice |
collectedVia | string | Optional; 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 status | Code | Cause |
|---|---|---|
| 400 | invalid_request | The body failed validation or is not valid JSON |
| 403 | plan_required | The team's plan does not include cross-device consent |
| 404 | not_found | No active banner with that public Banner ID on this team |
| 409 | version_conflict | baseVersion did not match the stored version (write only) |
| 413 | invalid_request | The body exceeded 20 KB |
| 500 | internal_error | Unexpected server error |
| 503 | identity_unavailable | Identity hashing is temporarily unavailable; retry later |