Consent log, stats & export
- Written for
- + Written for
- Deprecated
- + Deprecated
- Applies to
- + Applies to
Consent log, stats & export
Every decision a visitor makes — accept, decline, or a granular save — is written to a tamper-evident, hash-chained consent log per site (the anonymized IP and, when encryption is configured, the choices themselves are stored so a subject's records can later be crypto-erased). These endpoints read that log in four shapes: chain-integrity check, aggregated stats, raw rows, and a CSV audit export — plus signed per-decision receipts and subject-level erasure/export for GDPR/CCPA requests.
All /v1/sites/:cbid/consent/... and /v1/sites/:cbid/{subject-export,erase-consent} routes require the consent scope: consent:read for GETs, consent:write for the POST that erases. This applies to subject-export and erase-consent as well — even though a sites-scoped key manages everything else about a site, it deliberately cannot read or destroy consent PII; you need a consent:read/consent:write grant for those two specifically. receipt is the one exception, with its own narrower scope — see the note at the end of this page.
GET /v1/sites/:cbid/consent/verify
Recomputes the hash chain for this site's consent log and reports whether it's intact — the tamper-evidence check itself, exposed as an API so you can alert on it instead of eyeballing the dashboard.
curl https://api.cookiemunch.net/v1/sites/acme-com-9f3k/consent/verify \
-H "Authorization: Bearer fck_…" \
-H "User-Agent: Mozilla/5.0"{ "valid": true }valid: false means a record's stored hash no longer matches what's computed from its content and the previous link — investigate immediately; this should never happen absent a bug or tampering.
GET /v1/sites/:cbid/consent/stats
Aggregated per-day totals — the fastest way to build a dashboard chart.
Query param | Type | Description |
|---|---|---|
| integer | Epoch-ms lower bound (inclusive). Defaults to |
| integer | Epoch-ms upper bound (inclusive). Defaults to "now" ( |
curl "https://api.cookiemunch.net/v1/sites/acme-com-9f3k/consent/stats?from=1719792000000&to=1719878400000" \
-H "Authorization: Bearer fck_…" \
-H "User-Agent: Mozilla/5.0"[
{
"Date": "2024-07-01",
"OptIn": 842,
"OptOut": 219,
"OptInImplied": 0,
"OptInStrict": 842,
"TypeOptInPref": 601,
"TypeOptInStat": 780,
"TypeOptInMark": 512,
"Impressions": 1150,
"Countries": { "US": 640, "DE": 201, "GB": 220 }
}
]OptIn/OptOut count records where at least one non-necessary category was granted/declined; OptInImplied/OptInStrict split that by capture method; TypeOptInPref/Stat/Mark count per-category grants regardless of the overall decision. One object per UTC day with at least one record in range.
import { createCookieMunch } from '@cookiemunch/sdk';
const fc = createCookieMunch({ apiKey: process.env.FC_KEY, baseUrl: 'https://api.cookiemunch.net' });
const days = await fc.consent.stats('acme-com-9f3k', { from: 1719792000000, to: 1719878400000 });GET /v1/sites/:cbid/consent/log
Recent anonymized consent records, most recent first.
Query param | Type | Description |
|---|---|---|
| integer | Epoch-ms range, same defaults as |
| integer | Max rows to return. Default |
curl "https://api.cookiemunch.net/v1/sites/acme-com-9f3k/consent/log?limit=50" \
-H "Authorization: Bearer fck_…" \
-H "User-Agent: Mozilla/5.0"[
{
"stamp": "a1b2c3d4e5f6",
"receivedAt": 1719858421000,
"region": "de",
"method": "explicit",
"choices": { "preferences": true, "statistics": true, "marketing": false },
"anonIp": "203.0.113.0",
"url": "https://acme.com/pricing"
}
]stamp is the visitor's consent-receipt id — the same value CookieMunch.consentId() returns client-side — and the key you pass to the receipt, subject-export, and erase-consent endpoints below. method is "explicit" or "implied".
const rows = await fc.consent.log('acme-com-9f3k', { limit: 50 });GET /v1/sites/:cbid/consent/export
The full audit trail as CSV — the artifact regulators ask for. Same from/to query params as stats/log (no limit; export is unbounded).
curl "https://api.cookiemunch.net/v1/sites/acme-com-9f3k/consent/export?from=1719792000000" \
-H "Authorization: Bearer fck_…" \
-H "User-Agent: Mozilla/5.0" \
-o consent-log.csvConsent ID,Date/Time (UTC),Anonymized IP,User Agent,URL,Region,Necessary,Preferences,Statistics,Marketing,Method,Version
a1b2c3d4e5f6,2024-07-01T18:07:01.000Z,203.0.113.0,Mozilla/5.0...,https://acme.com/pricing,de,true,true,true,false,explicit,1Necessary is always true (strictly-necessary storage doesn't require consent). Response headers set Content-Type: text/csv and a Content-Disposition: attachment filename of consent-log.csv.
Note:
exportadditionally requires the site to be domain-verified (verified: trueon the site record) and returns403with{ "error": "domain not verified" }otherwise.verify,stats, andloghave no such requirement.
const csv = await fc.consent.export('acme-com-9f3k', { from: 1719792000000 });GET /v1/sites/:cbid/receipt/:stamp
A signed consent receipt for one decision — portable proof of what was consented to, when, and under what notice version. Requires the site to be verified and receiptSecret configured on the server.
curl "https://api.cookiemunch.net/v1/sites/acme-com-9f3k/receipt/a1b2c3d4e5f6?format=json" \
-H "Authorization: Bearer fck_…" \
-H "User-Agent: Mozilla/5.0"{
"receipt": {
"version": "1.0",
"collector": "acme-com-9f3k",
"consentTimestamp": "2024-07-01T18:07:01.000Z",
"pii": false,
"proofId": "a1b2c3d4e5f6",
"region": "de",
"purposes": [
{ "category": "necessary", "purpose": "Strictly necessary cookies and storage", "consentType": "explicit" },
{ "category": "statistics", "purpose": "Statistics and analytics measurement", "consentType": "explicit" }
]
},
"signature": "sha256:9f2a8b1e..."
}Named purposes with a matching RoPA entry are enriched with lawful basis, data categories, recipients, and retention when the deployment has RoPA wired up; if an asymmetric signing key is configured, the response also carries a third-party-verifiable publicSignature/keyId. The format query parameter also accepts html (a printable document) and pdf (downloadable, also negotiable via Accept: application/pdf) for handing a visitor a document instead of raw JSON. 404 if there's no consent record for that stamp; 403 if the site isn't verified; 501 if receipts aren't configured on this deployment.
const { receipt, signature } = await fc.consent.receipt('acme-com-9f3k', 'a1b2c3d4e5f6');Scope note: unlike the rest of this page,
receiptaccepts eitherreceipt:readorconsent:read— reading one receipt by its knownstampis a much narrower capability than exporting the whole log, so it has its own scope for an integration that should only ever render receipts.
Subject-level erasure & export
Two endpoints operate on every record for one stamp — the pattern for handling a GDPR/CCPA data-subject request for a specific visitor without going through the DSAR workflow manually. Both require the consent scope (read for export, write for erasure) — see the scope callout at the top of this page.
# Export a subject's own records (portability/access)
curl "https://api.cookiemunch.net/v1/sites/acme-com-9f3k/subject-export?stamp=a1b2c3d4e5f6" \
-H "Authorization: Bearer fck_…" \
-H "User-Agent: Mozilla/5.0"{ "cbid": "acme-com-9f3k", "stamp": "a1b2c3d4e5f6", "records": [ /* ... */ ], "count": 3 }# Crypto-erase a subject's records (irreversible)
curl -X POST https://api.cookiemunch.net/v1/sites/acme-com-9f3k/erase-consent \
-H "Authorization: Bearer fck_…" \
-H "User-Agent: Mozilla/5.0" \
-H "Content-Type: application/json" \
-d '{ "stamp": "a1b2c3d4e5f6" }'{ "erased": 3, "encryptionEnabled": true }erased is the number of records affected. When encryptionEnabled is true, erasure destroys the record's data-encryption key (the hash chain still verifies afterward, since it's computed over ciphertext); when false, this deployment has no consent encryption configured, and "erasure" is best-effort at the storage layer. Both endpoints 400 if stamp is missing.
const { records, count } = await fc.consent.exportSubject('acme-com-9f3k', 'a1b2c3d4e5f6');
const { erased } = await fc.consent.eraseSubject('acme-com-9f3k', 'a1b2c3d4e5f6');GET /v1/subjects/:subjectId/consent
Cross-surface aggregation: a subject's consent decisions across all of the org's sites, correlated by an app-supplied stable subjectId (set at ingest time via ConsentIngestInput.subjectId — see fc.consent.ingest() — so web, mobile, and desktop surfaces for the same end user resolve to one history). Requires consent:read. Only sites owned by the key's org are scanned.
curl https://api.cookiemunch.net/v1/subjects/user-4471/consent \
-H "Authorization: Bearer fck_…" \
-H "User-Agent: Mozilla/5.0"{
"subjectId": "user-4471",
"records": [ /* ConsentLogRow[], flattened across sites */ ],
"count": 5,
"sites": ["acme-com-9f3k", "acme-ios-2b7c"]
}sites lists only the cbids that actually had at least one matching record.
Errors
404 for an unknown-or-foreign cbid; 403 insufficient_scope when the key lacks the required consent:read/consent:write (or receipt:read); 403 with { "error": "domain not verified" } for export/receipt on an unverified site; 501 when receipts aren't configured. See conventions & errors for the full status code table.
Next: sites for domain verification (required by export and receipt), or back to site config.