Docs

API keys, members, webhooks & usage

AdminUpdated Sep 15, 2026

API keys, members, webhooks & usage

Org-administration endpoints: invite and manage members, issue and list API keys, subscribe to webhooks, and read plan usage. Every route requires an API key; the org is always derived from the key you authenticate with — nothing in this section accepts an orgId parameter, so a key can only ever act on its own org.

Admin-only surface. A scoped key (issued with an explicit scopes list) can never call /v1/members, /v1/keys, or /v1/webhooks — every request gets 403 { "error": "insufficient_scope", "message": "this key may not use admin endpoints; manage them in the dashboard" } regardless of which scopes it holds. Only a legacy, unscoped key (scopes: null — the default before scoped keys existed, and always the case for a key issued via POST /v1/keys below) can reach this section. GET /v1/me and GET /v1/usage are the exception: they need no scope at all, so any valid key can call them.

Members

List members

GET /v1/members
curl -H "Authorization: Bearer $COOKIEMUNCH_API_KEY" \
  https://api.cookiemunch.net/v1/members
[
  { "userId": "usr_1", "email": "[email protected]", "role": "owner" },
  { "userId": "usr_2", "email": "[email protected]", "role": "admin" }
]
const members = await client.members.list();

Invite a member

POST /v1/members

Field

Type

Required

Description

email

string

yes

Email of the person to invite. If they're not yet a user, one is created with no usable password (SSO/invite-only).

role

admin | member | viewer

yes

Role to grant. owner can't be granted through the API.

curl -X POST https://api.cookiemunch.net/v1/members \
  -H "Authorization: Bearer $COOKIEMUNCH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "email": "[email protected]", "role": "admin" }'

201 Created: { "member": { "userId": "usr_2", "email": "[email protected]", "role": "admin" } }.

Errors: 400 — missing email, or role not one of admin/member/viewer. 409 — the email is already a member of this org, or the org's plan has a seat limit and adding one more member would exceed it ({ "error": "seat limit reached for the <plan> plan (max <n>)" }).

Inviting a member grants durable human access: that person keeps dashboard access independently of the key that invited them, indefinitely. Treat any key capable of this call like an admin credential.

const { member } = await client.members.invite('[email protected]', 'admin');

Change a member's role

PATCH /v1/members/{userId}

Field

Type

Required

Description

role

admin | member | viewer

yes

New role.

curl -X PATCH https://api.cookiemunch.net/v1/members/usr_2 \
  -H "Authorization: Bearer $COOKIEMUNCH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "role": "viewer" }'
{ "member": { "userId": "usr_2", "email": "[email protected]", "role": "viewer" } }

Errors: 400 — invalid role, or { "error": "the owner's role cannot be changed" } when userId is the org owner. 404 — no such member.

const { member } = await client.members.setRole('usr_2', 'viewer');

Remove a member

DELETE /v1/members/{userId}
curl -X DELETE https://api.cookiemunch.net/v1/members/usr_2 \
  -H "Authorization: Bearer $COOKIEMUNCH_API_KEY"

200 { "ok": true } on success. Errors: 400userId is the owner, or the org's last remaining member. 404 — no such member.

await client.members.remove('usr_2');

API keys

List key prefixes

GET /v1/keys

Display metadata only (prefix, createdAt) — the secret is never retrievable after issuance, only its first 8 characters for identification.

curl -H "Authorization: Bearer $COOKIEMUNCH_API_KEY" \
  https://api.cookiemunch.net/v1/keys
[{ "prefix": "fck_a1b2c3d4", "createdAt": 1730332800000 }]

Errors: 501 { "error": "key listing not configured" } on a self-host without key metadata storage wired.

const keys = await client.keys.list();

Issue a new key

POST /v1/keys

Takes no body over this route — a key minted here is always unscoped (full access), since the route itself is admin-only and thus only reachable by an already-unscoped key. key is returned exactly once; it can't be recovered later, only revoked and replaced.

curl -X POST https://api.cookiemunch.net/v1/keys \
  -H "Authorization: Bearer $COOKIEMUNCH_API_KEY"

201 Created:

{ "key": "fck_9e8d7c6b5a4f3e2d1c0b", "prefix": "fck_9e8d7c6b" }

To mint a scoped, least-privilege key instead (consent:read, dsar:write, etc.), use the dashboard's API Keys settings page rather than this endpoint — scoped-key issuance isn't exposed on /v1.

Scopes recap: sites:*, consent:*, dsar:*, vendors:*, ropa:*, and receipt:read are all assignable to a scoped key from the dashboard. The banner-library routes (/v1/banners/*) are checked against banners:read/banners:write in the request path, but those two scope names aren't yet in the dashboard's assignable-scope list — in practice, reach /v1/banners today with an unscoped key.

const { key, prefix } = await client.keys.issue();

Usage

GET /v1/usage

Current resource usage for the org, relative to its plan. Needs no scope — any valid key, scoped or not, can call it.

curl -H "Authorization: Bearer $COOKIEMUNCH_API_KEY" \
  https://api.cookiemunch.net/v1/usage
{ "domains": 4, "seats": 3, "monthlyEvents": 128430 }

Field

Description

domains

Number of registered sites in the org.

seats

Number of members in the org.

monthlyEvents

Consent events recorded across all sites since the start of the current UTC month.

Errors: 501 { "error": "usage reporting not configured" } on a self-host without usage metering wired.

const usage = await client.usage();

Webhooks

Outbound event delivery — the platform POSTs a JSON payload to your url whenever a subscribed event fires, optionally HMAC-SHA256-signed. This section is the CRUD reference; see webhooks & events for the full payload shape and signature verification.

Known events: consent.recorded, scan.completed, scan.cookies_changed, dsar.created, dsar.updated, banner.published.

List subscriptions

GET /v1/webhooks

The signing secret is never included in list responses.

curl -H "Authorization: Bearer $COOKIEMUNCH_API_KEY" \
  https://api.cookiemunch.net/v1/webhooks
[
  {
    "id": "whk_1",
    "orgId": "org_9f01",
    "url": "https://example.com/hooks/cookiemunch",
    "events": ["consent.recorded", "dsar.created"],
    "cbid": null,
    "active": true,
    "createdAt": 1730332800000
  }
]
const subs = await client.webhooks.list();

Create a subscription

POST /v1/webhooks

Field

Type

Required

Description

url

string

yes

Endpoint to deliver events to.

events

string[]

yes

Non-empty subset of the known event list above.

cbid

string

no

Restrict delivery to one site. Omit for every site in the org. Must belong to your org.

curl -X POST https://api.cookiemunch.net/v1/webhooks \
  -H "Authorization: Bearer $COOKIEMUNCH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://example.com/hooks/cookiemunch", "events": ["consent.recorded", "dsar.created"] }'

201 Created — includes the signing secret, shown only this once:

{
  "id": "whk_1",
  "orgId": "org_9f01",
  "url": "https://example.com/hooks/cookiemunch",
  "secret": "whsec_3f9a...",
  "events": ["consent.recorded", "dsar.created"],
  "cbid": null,
  "active": true,
  "createdAt": 1730332800000
}

Errors: 400 { "error": "url required" }, or { "error": "events must be a non-empty array of: consent.recorded, scan.completed, scan.cookies_changed, dsar.created, dsar.updated, banner.published" }. 404 { "error": "unknown site" } if cbid is set but isn't one of your sites.

const sub = await client.webhooks.create({
  url: 'https://example.com/hooks/cookiemunch',
  events: ['consent.recorded', 'dsar.created'],
});

Delete a subscription

DELETE /v1/webhooks/{id}
curl -X DELETE https://api.cookiemunch.net/v1/webhooks/whk_1 \
  -H "Authorization: Bearer $COOKIEMUNCH_API_KEY"

204 No Content on success (also when id doesn't exist — deletion is idempotent).

await client.webhooks.delete('whk_1');

Next: DSAR, preferences, ROPA & vendors for the resources most of these keys are scoped to, or cookies, scan & verify for the scan.* events referenced above.

Was this page helpful?