Skip to documentation content

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.

FieldTypeRequiredNotes
topicsarrayyesUp to 50 topics
topics[].keystringyesStable key, 1–64 characters: lower-case letters, digits, _, ., -, starting with a letter or digit. Must be unique
topics[].labelstringyes1–200 characters. Shown on the preference page and kept in each history entry
topics[].descriptionstring or nullnoUp to 1,000 characters
topics[].channelsstring[]yesThe channels this topic is offered on; at least one
topics[].isActivebooleannoDefault 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.

FieldTypeNotes
subjectobject{ type, value }
changesarray1–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.

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.

FieldTypeNotes
subjectobject{ type, value }
ttlDaysintegerOptional 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 statusCodeCause
400invalid_requestValidation failed, an unknown or inactive topic, a channel the topic does not offer, duplicate topic keys, or no changes
403plan_requiredThe plan does not include this feature
403missing_scope, test_key_read_onlySee Authentication
404feature_disabledMarketing preferences are not switched on for this deployment
409, 422idempotency_in_progress, idempotency_key_reusedSee Safe retries
503identity_unavailableIdentifier hashing is temporarily unavailable; retry later