Cookies, scan & verify
- Written for
- + Written for
- Deprecated
- + Deprecated
- Applies to
- + Applies to
Cookies, scan & verify
This page covers three related capabilities on a single site (cbid): reading its categorized cookie declaration, triggering the crawler that builds that declaration, and proving domain ownership so domain-gated features (consent export, signed receipts) unlock. All three live under /v1/sites/:cbid/..., require an API key (Authorization: Bearer fck_… or X-API-Key: fck_…), and are checked against the sites scope — sites:read for the GETs below, sites:write for the POSTs. None of them require consent:*, even though "cookies" sounds consent-adjacent: this is declaration/crawl metadata, not consent-decision data. A cbid that doesn't belong to your org behaves exactly like a nonexistent one (404), never a 403 or 409 — the API never confirms another org's resource exists.
Get the cookie declaration
GET /v1/sites/{cbid}/cookiesScope: sites:read
Returns the latest categorized cookie snapshot — the same data a cookieTable banner element renders and what a generated cookie-policy page lists. This isn't purely scan-driven: the embed also passively reports cookie names (never values) and third-party script hosts it observes on real visitor traffic, merging them into this snapshot between explicit scans.
Param | In | Type | Description |
|---|---|---|---|
| path | string | The site identifier. |
curl https://api.cookiemunch.net/v1/sites/acme-com-9f3k/cookies \
-H "Authorization: Bearer $COOKIEMUNCH_API_KEY"{
"updatedAt": 1719858421000,
"cookies": [
{
"name": "_ga",
"domain": ".acme.com",
"category": "statistics",
"purpose": "Registers a unique ID for statistical purposes.",
"provider": "Google Analytics",
"expiry": "2 years"
},
{ "name": "session_id", "category": "necessary" }
]
}category is one of necessary, preferences, statistics, marketing, or unclassified (observed but not yet matched to a known tracker signature). domain, purpose, provider, and expiry are omitted when unknown. updatedAt is 0 (epoch) if nothing has ever been scanned or observed.
Errors: 404 — site not found (or not yours). 501 — cookie declarations aren't configured on this deployment.
const decl = await client.sites.cookies('acme-com-9f3k');
// decl.updatedAt, decl.cookies[]Start a scan
POST /v1/sites/{cbid}/scanScope: sites:write
Kicks off an asynchronous Playwright crawl of the site's homepage to (re)build its cookie declaration. The call returns immediately; the crawl runs in the background and results land in GET /v1/sites/{cbid}/cookies once it finishes. Before launching a real browser at the domain, the server SSRF-screens the resolved address (a domain that resolves to a private, loopback, link-local, or cloud-metadata address is refused, silently, rather than crawled).
Param | In | Type | Description |
|---|---|---|---|
| path | string | The site identifier. |
curl -X POST https://api.cookiemunch.net/v1/sites/acme-com-9f3k/scan \
-H "Authorization: Bearer $COOKIEMUNCH_API_KEY"202 Accepted:
{ "status": "scanning" }If a scan for this cbid is already in flight, the request is rejected rather than queued a second time:
409 Conflict:
{ "status": "already-scanning" }Errors: 404 — site not found. 409 — a scan is already running for this site. 501 — cookie scanning isn't configured on this deployment (the crawler is an optional server component; a self-host without Playwright wired up returns this).
const { status } = await client.sites.scan('acme-com-9f3k'); // 'scanning'Get scan status
GET /v1/sites/{cbid}/scanScope: sites:read
Poll this after starting a scan to know when results are ready.
curl https://api.cookiemunch.net/v1/sites/acme-com-9f3k/scan \
-H "Authorization: Bearer $COOKIEMUNCH_API_KEY"{ "status": "idle", "lastScannedAt": 1719858421000 }Field | Type | Description |
|---|---|---|
|
| Whether a crawl is currently in flight for this site. |
| epoch-ms | | When the most recent completed scan finished; |
Finishing a scan (whether or not the cookie set changed) fires a scan.completed webhook; if the set of cookie names differs from the previous crawl, scan.cookies_changed also fires (see webhooks).
Errors: 404 — site not found. 501 — cookie scanning not configured.
const status = await client.sites.scanStatus('acme-com-9f3k');Verify domain ownership
POST /v1/sites/{cbid}/verifyScope: sites:write
Proves you control the site's domain, which unlocks domain-gated routes: consent CSV export and machine-fetched signed receipts (see the consent/sites reference). The token to place is on the site resource itself (verifyToken, from GET /v1/sites/{cbid}); it's a stable per-site string in the form fcv_ + 24 hex characters.
Field | Type | Required | Description |
|---|---|---|---|
|
| yes | Which challenge to check. |
Method | Check performed |
|---|---|
| A TXT record |
| The homepage serves |
|
|
curl -X POST https://api.cookiemunch.net/v1/sites/acme-com-9f3k/verify \
-H "Authorization: Bearer $COOKIEMUNCH_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "method": "dns" }'Success (also flips site.verified to true server-side):
{ "verified": true, "method": "dns" }Failure — the site's verification state is left unchanged, so you can retry after fixing DNS/meta/file:
{ "verified": false, "reason": "TXT record not found or did not match" }Note that a failed check is still a 200 — verified: false is a normal outcome, not an error. Only malformed requests and infrastructure gaps are non-2xx.
Errors: 400 — method missing or not one of dns/meta/file. 404 — site not found. 501 — domain verification isn't configured on this deployment (no verify network/DNS dependency injected).
const result = await client.sites.verify('acme-com-9f3k', 'dns');
if (result.verified) { /* domain-gated routes now unlocked */ }Next: banner library to manage what the banner actually shows, or API keys, members, webhooks & usage for the scan.* webhook event catalogue.