---
title: "Marketing preferences"
description: "Manage marketing preference topics, read and update a person's subscriptions, erase their history, and issue hosted preference-centre links."
url: "https://cookiebeam.com/docs/api-reference/preferences"
lang: "en"
---

# 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](https://cookiebeam.com/docs/api-reference/form-consent#availability): 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

```text
GET /api/v1/preferences/topics
```

Requires `preferences:read`. Test keys work. Inactive topics are left out unless you pass `includeInactive=true`.

```bash
curl "https://cookiebeam.com/api/v1/preferences/topics?includeInactive=true" \
  -H "Authorization: Bearer $COOKIEBEAM_API_KEY"
```

```json
{
  "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

```text
PUT /api/v1/preferences/topics
```

Requires `preferences:write` and a live key. Accepts an [`Idempotency-Key`](https://cookiebeam.com/docs/api-reference/overview#safe-retries-with-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` |

```bash
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

```text
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).

```bash
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 }'
```

```json
{
  "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

```text
PUT /api/v1/preferences/subjects/state
```

Requires `preferences:write` and a live key. Accepts an [`Idempotency-Key`](https://cookiebeam.com/docs/api-reference/overview#safe-retries-with-idempotency-key).

| Field | Type | Notes |
| ----- | ---- | ----- |
| `subject` | object | `{ type, value }` |
| `changes` | array | 1–300 entries of `{ topicKey, channel, status }`, where `status` is `subscribed` or `unsubscribed` |

```bash
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`](https://cookiebeam.com/docs/api-reference/webhooks#event-payloads) webhook event listing those changes.

## Erase a person's history

```text
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

```text
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 |

```json
{
  "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](https://cookiebeam.com/docs/api-reference/authentication) |
| 404 | `feature_disabled` | Marketing preferences are not switched on for this deployment |
| 409, 422 | `idempotency_in_progress`, `idempotency_key_reused` | See [Safe retries](https://cookiebeam.com/docs/api-reference/overview#safe-retries-with-idempotency-key) |
| 503 | `identity_unavailable` | Identifier hashing is temporarily unavailable; retry later |
