---
title: "Authentication"
description: "Create and use CookieBeam API keys, choose scopes, and understand live vs test keys."
url: "https://cookiebeam.com/docs/api-reference/authentication"
lang: "en"
---

# Authentication

Create and use CookieBeam API keys, choose scopes, and understand live vs test keys.

The public API authenticates requests with API keys. API access requires a plan that includes the API feature; on a plan without it, a valid key is rejected with a `plan_required` error (HTTP 403).

## Create a key

Team owners create, rotate and revoke keys in the dashboard under **Settings > API keys**. Each key has a name, an environment, a list of scopes, and an expiry. Keys always expire: the default is 90 days for live keys and 30 days for test keys, and you can choose any lifetime from 1 to 365 days. A team can have at most 20 active keys.

The full key is displayed exactly once at creation. CookieBeam stores only a SHA-256 hash of the key's validator segment, so a lost key cannot be recovered — revoke it and create a new one.

## Key format

Keys are prefixed by environment followed by 64 lowercase hexadecimal characters:

```text
cb_live_<64 hex chars>
cb_test_<64 hex chars>
```

A key that does not match this format, or whose prefix does not match the environment it was created in, is rejected as `invalid_key`.

## Send the key

Send the key as a bearer token in the `Authorization` header:

```text
Authorization: Bearer cb_live_your_key_here
```

Keys are team-level principals. A key acts as the team, not as the member who created it, and per-member banner access lists do not restrict key-authenticated requests. Treat keys with the same care as team administrator credentials.

## Live vs test keys

Test keys are read-only. Any endpoint that requires a scope ending in `:write` or `:publish` rejects a test key with HTTP 403 and the error code `test_key_read_only`. Use test keys to explore read endpoints without touching production data, and live keys for mutations.

## Scopes

Each key carries an explicit allow-list of scopes. An endpoint declares the scopes it requires; if the key is missing one, the request fails with HTTP 403 and the error code `missing_scope`, with the missing scopes named in the message.

Scopes are grouped by resource. **Active** scopes are required by at least one endpoint or MCP tool. **Reserved** scopes are part of the scope vocabulary but no endpoint uses them yet.

| Scope | Status | Grants |
| ----- | ------ | ------ |
| `banners:read` | Active | List and read banners and their embed code (`GET /api/v1/banners`, MCP banner tools) |
| `banners:write` | Active | Create banners (`POST /api/v1/banners`) |
| `banners:publish` | Reserved | Publish banner configurations |
| `consent:read` | Active | Resolve cross-device consent subjects |
| `consent:write` | Active | Log consent and write or delete cross-device consent state |
| `consent:export` | Reserved | Export consent records |
| `scanner:read` | Active | Read scans, scan results and the discovery inventory (MCP tools) |
| `scanner:write` | Reserved | Start and manage scans |
| `analytics:read` | Active | Read consent analytics (MCP tool) |
| `team:read` | Reserved | Read team members |
| `team:write` | Reserved | Manage team members |
| `webhooks:read` | Reserved | Read webhook configuration |
| `webhooks:write` | Reserved | Manage webhook configuration |
| `audit:read` | Reserved | Read the audit log |

Rules when you create or rotate a key:

- **New keys can only request active scopes.** Requesting a reserved scope is rejected (`BAD_REQUEST`) and the message names the reserved scopes. When an endpoint ships for a reserved scope, the scope becomes active.
- **Existing keys keep their scopes.** A key that already holds a reserved scope keeps authenticating, and rotating it copies its scopes unchanged.
- **A key cannot exceed its issuer.** Every scope maps to a set of dashboard permissions; you can only grant a scope whose permissions you hold yourself. This is checked on create and on rotate (`FORBIDDEN` otherwise), and the dashboard disables scopes you cannot grant.

Grant only the scopes an integration actually uses. A reporting job needs `banners:read` and nothing else; keep write scopes off keys that ship to CI or shared environments.

## Banner and environment restrictions

A key can optionally be limited to specific banners and to specific banner environments (for example `production` or `staging`). Leave both empty and the key covers every banner and environment of the team, which is how all keys created before restrictions existed behave.

- **Banners.** A banner outside the key's list behaves exactly like a banner of another team: single-banner requests return HTTP 404 `not_found` (MCP tools report `Banner not found`), and lists such as `GET /api/v1/banners` and the MCP `list_banners` and `list_scans` tools only include the allowed banners. A banner-restricted key cannot create banners (HTTP 403, code `key_restricted`), because the new banner would sit outside its own list.
- **Form consent proofs.** A banner-restricted key only sees form consent proofs recorded for its banners; proofs recorded without a banner are hidden from it. It must pass a `bannerId` from its list when it records a proof, and it cannot erase a person's proofs, because erasure covers every banner of the team (both HTTP 403, code `key_restricted`).
- **Environments.** Environment-specific results, such as the per-environment script URLs from the MCP `get_embed_code` tool, only include the allowed environments. The install snippet, which serves the production environment, is only returned when `production` is allowed.
- **Who can set them.** You can only restrict a key to banners you can access yourself; every banner must belong to your team and every environment must exist on one of the selected banners. Restrictions can be changed on an existing key from the dashboard without re-issuing it, and rotating a key keeps them.

## Authentication errors

All authentication errors use the standard [error envelope](https://cookiebeam.com/docs/api-reference/errors-and-limits).

| HTTP status | Code | Cause |
| ----------- | ---- | ----- |
| 401 | `invalid_key` | The key is malformed, unknown, or its prefix does not match its environment |
| 401 | `revoked_key` | The key has been revoked |
| 401 | `expired_key` | The key has passed its expiry date |
| 401 | `unauthorized` | The endpoint requires an API key and none was sent |
| 403 | `plan_required` | The team's plan does not include API access |
| 403 | `missing_scope` | The key lacks a scope the endpoint requires |
| 403 | `test_key_read_only` | A test key was used for a write or publish operation |
| 403 | `key_restricted` | A key restricted to specific banners was used to create a banner, to record a form consent proof without one of its banners, or to erase form consent proofs |

Failed authentication attempts are limited to 10 per minute per client IP. Past that budget, further attempts return HTTP 429 with the code `rate_limited` and a `Retry-After` header, even if the credentials are wrong. This slows down key guessing; if you see it during legitimate troubleshooting, wait for the window to pass.
