Docs

Conventions & errors

AdminUpdated Sep 15, 2026

Conventions & errors

Base URL and versioning

https://api.cookiemunch.net/v1

A self-hosted deployment uses its own domain instead — the /v1 prefix is unchanged. The SDK takes the host as baseUrl and appends /v1 for you.

Everything under /v1 is additive-stable: new optional fields and new endpoints can appear without a version bump. A breaking change ships under a new prefix (/v2) rather than changing what /v1 means out from under existing integrations. There is currently no /v2 — the whole Developer API is /v1.

Requests

  • JSON in, JSON out. Send Content-Type: application/json with a JSON body on POST/PUT requests that take one (a handful, like DELETE, take none).

  • Path parameters (:cbid, :id) are always resolved against the key's own org first. A cbid that belongs to a different org behaves exactly like one that doesn't exist — a plain 404, never a 403. This is intentional: the API never confirms that another org's resource even exists.

  • OPTIONS is handled generically for CORS preflight on every route and returns 204 with no auth required.

curl -X PUT https://api.cookiemunch.net/v1/sites/acme-com-9f3k/config \
  -H "Authorization: Bearer fck_…" \
  -H "Content-Type: application/json" \
  -d '{ "blocking": { "mode": "auto" } }'

Pagination

There is no pagination on this API. Every list endpoint — GET /v1/sites, GET /v1/dsar, GET /v1/vendors, GET /v1/ropa, GET /v1/members, GET /v1/webhooks, GET /v1/keys, GET /v1/preferences, GET /v1/brand-kits, GET /v1/banners — returns the entire collection in one response body. There is no limit/offset/cursor parameter and no Link header on any of them.

The consent log is the exception and is cursor-paginated. It takes from/to epoch-ms parameters to select a time range, a limit (capped at 1000 per page), and a before cursor (a receivedAt epoch-ms). Each page returns the newest limit records older than the cursor; when more remain, the response carries an X-Next-Cursor header and a Link: <…?before=…>; rel="next" header — follow them to walk the entire history past the 1000/page cap. See Consent log, stats & export.

If you're integrating against an org with a large number of sites, DSARs, or vendors, plan for a single large JSON array in the response rather than a loop over pages.

Idempotency

There is no idempotency-key mechanism. No endpoint reads an Idempotency-Key header or deduplicates by a client-supplied token. A POST that creates a resource (a site, a DSAR, a webhook, an API key) will create a second one if you retry it after a timeout without knowing whether the first attempt landed — check with a GET before retrying a create you're unsure about, rather than retrying blindly. PUT endpoints (like site config) are naturally idempotent because they replace state wholesale rather than append to it.

Rate limiting

A global, per-client-IP token-bucket limiter sits in front of every route on the server, /v1 included — default 240 requests burst capacity, refilling at 40/sec (RATE_CAPACITY / RATE_REFILL_PER_SEC env vars on self-host). It's keyed by IP address, not by API key, so it's a blunt, infrastructure-level backstop rather than a per-key quota. Exceeding it returns:

Every non-preflight response (both 2xx and 429) carries X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset (seconds until a token frees up) so you can self-throttle before hitting the wall. A 429 also includes Retry-After:

HTTP/1.1 429 Too Many Requests
Retry-After: 1
X-RateLimit-Limit: 240
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1
{ "error": "rate limit exceeded", "code": "rate_limited" }

There's a second, much stricter limiter (~5 requests/minute/IP) but it only applies to the dashboard's /auth/* login/register/SSO routes, not to /v1. CORS preflight (OPTIONS) requests are never counted against either limiter. Beyond this IP-level backstop, there is no separate per-key or per-plan rate limit on the Developer API today.

Responses & status codes

Status

Meaning

200

Success (read, or a write that returns the updated resource).

201

Created (site, DSAR, vendor, RoPA entry, webhook, banner, brand kit, API key).

202

Accepted — an async job (a cookie scan) was started; poll for status.

204

Success, no body (delete, OPTIONS preflight, brand-kit delete, webhook delete).

400

Malformed request — a missing or invalid field. Body includes error.

401

Missing or invalid API key.

403

Authenticated, but forbidden — insufficient scope, an admin-only surface, or a plan/verification gate (e.g. exporting an unverified domain's consent log).

404

Not found, or it belongs to another org — indistinguishable by design (see above).

409

Conflict — a cbid already claimed, a scan already running, an illegal DSAR status transition.

429

Rate limited (see above).

501

The endpoint exists in this API version but isn't wired up on this deployment (e.g. self-host without cookie scanning or usage reporting configured).

Some routes also return 200 { "ok": false, "issues": [...] } for a domain-level validation failure (e.g. an invalid v2 flow config) rather than a 4xx — that's deliberate, so an automation (or an AI agent via the MCP server) can read the issues and retry with a fix, reserving 4xx for auth/not-found/ malformed-request classes of error. See Sites for where this shows up.

Every error body carries a human error string and a stable machine code you can branch on (never parse the prose):

{ "error": "site not found", "code": "not_found" }

Codes track the status — bad_request (400), unauthorized (401), forbidden (403), not_found (404), conflict (409), rate_limited (429), not_implemented (501), internal (5xx), and so on. Scope failures carry a more specific code plus a human message (see Authentication & scopes):

{ "error": "insufficient_scope", "message": "missing required scope: consent:write", "code": "insufficient_scope" }

Using the SDK, every non-2xx response is thrown as a CookieMunchApiError with .status, .message, and an optional .code:

import { createCookieMunch, CookieMunchApiError } from '@cookiemunch/sdk';

const fc = createCookieMunch({ apiKey: process.env.FC_KEY, baseUrl: 'https://api.cookiemunch.net' });

try {
  await fc.sites.get('unknown-cbid');
} catch (e) {
  if (e instanceof CookieMunchApiError && e.status === 404) {
    console.log('no such site (or not yours)');
  }
}

Identity & usage

Two meta endpoints work with any key, scoped or not — useful for health checks and usage dashboards without needing a broad grant.

curl https://api.cookiemunch.net/v1/me -H "Authorization: Bearer fck_…"
# { "orgId": "org_9k2m", "plan": "pro", "keyPrefix": "fck_8f2a91c0" }

curl https://api.cookiemunch.net/v1/usage -H "Authorization: Bearer fck_…"
# { "domains": 12, "seats": 4, "monthlyEvents": 812304 }

GET /v1/usage returns 501 on a deployment that hasn't wired up usage reporting — a minimal self-host is not required to implement it.

Next: Sites for the first real resource, or OpenAPI to generate a client in another language.

Was this page helpful?