Marketing preferences
Manage marketing preference topics, read and update a person's subscriptions, erase their history, and issue hosted preference-centre links.
The marketing preference endpoints keep track of what each person wants to receive from you — per topic (for example newsletter or product-updates) and per channel (e-mail, SMS, post…). Every change is kept as an audit-trail entry, and you can send people a link to a hosted page where they change their own preferences.
These endpoints share the availability rules of form consent: they answer HTTP 404 feature_disabled until the feature is switched on for your deployment, and they need a plan that includes API access. Erasure keeps working after a downgrade.
People are identified the same way as for form consent: send subject as { "type": "email" | "phone" | "external_id", "value": "…" }. The value is normalised and stored only as a team-scoped keyed hash, and responses identify the person by subjectRef. Endpoints that take a subject use POST, PUT or DELETE so the identifier stays out of URLs.
Channels are email, sms, phone, post, push and in_app.
List topics
GET /api/v1/preferences/topics
Requires preferences:read. Test keys work. Inactive topics are left out unless you pass includeInactive=true.
curl "https://cookiebeam.com/api/v1/preferences/topics?includeInactive=true" \
-H "Authorization: Bearer $COOKIEBEAM_API_KEY"
{
"data": {
"topics": [
{
"key": "newsletter",
"label": "Monthly newsletter",
"description": "Product news and tips, once a month.",
"channels": ["email"],
"isActive": true,
"updatedAt": "2026-10-01T08:00:00.000Z"
}
]
},
"version": "v1"
}
Replace the topics
PUT /api/v1/preferences/topics
Requires preferences:write and a live key. Accepts an Idempotency-Key. Creates or updates every topic in the body. Topics you leave out are deactivated — hidden from the preference page — never deleted, because the history refers to their keys. Returns the whole catalogue after the change, inactive topics included.
| Field | Type | Required | Notes |
|---|---|---|---|
topics | array | yes | Up to 50 topics |
topics[].key | string | yes | Stable key, 1–64 characters: lower-case letters, digits, _, ., -, starting with a letter or digit. Must be unique |
topics[].label | string | yes | 1–200 characters. Shown on the preference page and kept in each history entry |
topics[].description | string or null | no | Up to 1,000 characters |
topics[].channels | string[] | yes | The channels this topic is offered on; at least one |
topics[].isActive | boolean | no | Default true |
curl -X PUT https://cookiebeam.com/api/v1/preferences/topics \
-H "Authorization: Bearer $COOKIEBEAM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"topics": [
{ "key": "newsletter", "label": "Monthly newsletter", "description": "Product news and tips, once a month.", "channels": ["email"] },
{ "key": "offers", "label": "Special offers", "channels": ["email", "sms"] }
]
}'
Read a person's preferences
POST /api/v1/preferences/subjects/state
Requires preferences:read. Test keys work. Body: subject, and optionally includeHistory: true to add the audit trail (the 100 most recent entries, newest first).
curl -X POST https://cookiebeam.com/api/v1/preferences/subjects/state \
-H "Authorization: Bearer $COOKIEBEAM_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "subject": { "type": "email", "value": "alice@example.com" }, "includeHistory": true }'
{
"data": {
"subjectRef": "k1:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"topics": [
{
"key": "newsletter",
"label": "Monthly newsletter",
"description": "Product news and tips, once a month.",
"channels": [
{ "channel": "email", "status": "subscribed", "updatedAt": "2026-10-22T09:15:00.000Z" }
]
},
{
"key": "offers",
"label": "Special offers",
"description": null,
"channels": [
{ "channel": "email", "status": null, "updatedAt": null },
{ "channel": "sms", "status": null, "updatedAt": null }
]
}
],
"history": [
{
"id": "4e5f6a7b-8c9d-4e0f-a1b2-c3d4e5f6a7b8",
"topicKey": "newsletter",
"topicLabel": "Monthly newsletter",
"channel": "email",
"status": "subscribed",
"source": "api",
"createdAt": "2026-10-22T09:15:00.000Z"
}
]
},
"version": "v1"
}
A status of null means the person never set that preference; treat it as not subscribed. source in the history is api, link (the hosted page) or dashboard.
Update a person's preferences
PUT /api/v1/preferences/subjects/state
Requires preferences:write and a live key. Accepts an Idempotency-Key.
| Field | Type | Notes |
|---|---|---|
subject | object | { type, value } |
changes | array | 1–300 entries of { topicKey, channel, status }, where status is subscribed or unsubscribed |
curl -X PUT https://cookiebeam.com/api/v1/preferences/subjects/state \
-H "Authorization: Bearer $COOKIEBEAM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"subject": { "type": "email", "value": "alice@example.com" },
"changes": [
{ "topicKey": "newsletter", "channel": "email", "status": "unsubscribed" },
{ "topicKey": "offers", "channel": "sms", "status": "subscribed" }
]
}'
The response has the person's topics after the change (same shape as the read) and applied: the changes that differed from the current state and were recorded. A change that matches the current state is not recorded again. Unknown or inactive topics, and channels a topic does not offer, are refused with HTTP 400 invalid_request. An update that records at least one change emits one preferences.updated webhook event listing those changes.
Erase a person's history
DELETE /api/v1/preferences/subjects/state
Requires preferences:erase and a live key. Body: { "subject": { "type", "value" } }. Deletes the person's audit trail under every hashing key the team has used and revokes their open preference links. Returns subjectRef and deleted, the number of history entries removed.
Issue a preference-centre link
POST /api/v1/preferences/links
Requires preferences:write and a live key. Returns a link to a hosted page where the person sees and changes their own preferences. Holding the link is the authentication, like an unsubscribe link, so send it only to that person — for example in the footer of an e-mail to that address.
| Field | Type | Notes |
|---|---|---|
subject | object | { type, value } |
ttlDays | integer | Optional lifetime, 1–90 days. Default 30 |
{
"data": {
"id": "2a3b4c5d-6e7f-4a8b-9c0d-1e2f3a4b5c6d",
"url": "https://cookiebeam.com/privacy-portal/preferences/Zx81…",
"expiresAt": "2026-11-21T09:15:00.000Z",
"subjectRef": "k1:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
},
"version": "v1"
}
The token in the URL is shown once and stored only as a hash. Changes made on the hosted page are recorded with source: "link".
Errors
| HTTP status | Code | Cause |
|---|---|---|
| 400 | invalid_request | Validation failed, an unknown or inactive topic, a channel the topic does not offer, duplicate topic keys, or no changes |
| 403 | plan_required | The plan does not include this feature |
| 403 | missing_scope, test_key_read_only | See Authentication |
| 404 | feature_disabled | Marketing preferences are not switched on for this deployment |
| 409, 422 | idempotency_in_progress, idempotency_key_reused | See Safe retries |
| 503 | identity_unavailable | Identifier hashing is temporarily unavailable; retry later |