API keys, members, webhooks & usage
- Written for
- + Written for
- Deprecated
- + Deprecated
- Applies to
- + Applies to
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
scopeslist) can never call/v1/members,/v1/keys, or/v1/webhooks— every request gets403 { "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 viaPOST /v1/keysbelow) can reach this section.GET /v1/meandGET /v1/usageare the exception: they need no scope at all, so any valid key can call them.
Members
List members
GET /v1/memberscurl -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/membersField | Type | Required | Description |
|---|---|---|---|
| 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). |
|
| yes | Role to grant. |
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 |
|---|---|---|---|
|
| 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: 400 — userId 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/keysDisplay 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/keysTakes 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:*, andreceipt:readare all assignable to a scoped key from the dashboard. The banner-library routes (/v1/banners/*) are checked againstbanners:read/banners:writein the request path, but those two scope names aren't yet in the dashboard's assignable-scope list — in practice, reach/v1/bannerstoday with an unscoped key.
const { key, prefix } = await client.keys.issue();Usage
GET /v1/usageCurrent 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 |
|---|---|
| Number of registered sites in the org. |
| Number of members in the org. |
| 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/webhooksThe 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/webhooksField | Type | Required | Description |
|---|---|---|---|
| string | yes | Endpoint to deliver events to. |
| string[] | yes | Non-empty subset of the known event list above. |
| 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.