REST API
Manage domains, scans and consent records over HTTPS, export proof of consent and receive signed webhooks. Authentication, pagination, errors and limits.
Last updated October 29, 2026
The REST API lets you automate everything you can do in the dashboard: add domains, start scans, search and export consent records, and receive webhooks when something changes. It is available on Pro and Enterprise plans.
Authentication
Create an API key under Settings → API keys. Keys are scoped to one workspace and are either read-only or read-write. Send the key as a bearer token:
curl https://api.cookiesh.eu/v1/domains \
-H "Authorization: Bearer ck_sk_live_..."
Secret keys start with ck_sk_. Never use them in browser code. The script tag only needs your public site ID.
All requests use HTTPS and JSON. The base URL is https://api.cookiesh.eu/v1. Timestamps are ISO 8601 strings in UTC.
Endpoints
| Method | Path | Description |
|---|---|---|
GET |
/domains |
List domains in the workspace |
POST |
/domains |
Add a domain |
GET |
/domains/{id} |
Get a domain and its settings |
DELETE |
/domains/{id} |
Remove a domain |
POST |
/domains/{id}/scans |
Start a cookie scan |
GET |
/domains/{id}/scans/{scan_id} |
Get a scan’s status and results |
GET |
/domains/{id}/cookies |
List classified cookies and trackers |
GET |
/consents |
Search consent records |
GET |
/consents/{consent_id} |
Get one consent record with its hash-chain proof |
POST |
/exports |
Start a CSV or JSON export of consent records |
GET |
/exports/{id} |
Get an export’s status and download URL |
DELETE |
/subjects/{user_id} |
Delete all consent records linked to a user ID |
Example: find the consent records for one visitor.
curl "https://api.cookiesh.eu/v1/consents?domain_id=dom_29xk&consent_id=cns_8Hq2Lw" \
-H "Authorization: Bearer ck_sk_live_..."
{
"data": [
{
"id": "cns_8Hq2Lw",
"domain_id": "dom_29xk",
"created_at": "2026-10-12T09:41:07Z",
"region": "DE",
"method": "banner",
"banner_version": "v14",
"categories": { "necessary": true, "preferences": false, "analytics": true, "marketing": false },
"hash": "9f2c…e71a"
}
],
"next_cursor": null
}
Pagination
List endpoints return up to 100 items. Pass limit to ask for fewer. If more results exist, the response includes a next_cursor. Pass it back as cursor to get the next page. Cursors stay valid for 24 hours.
Errors
Errors use standard HTTP status codes and a consistent body:
{
"error": {
"type": "invalid_request",
"message": "domain_id is required",
"request_id": "req_4Kd92"
}
}
| Status | Meaning |
|---|---|
400 |
The request is invalid. The message explains why. |
401 |
The API key is missing or invalid. |
403 |
The key is read-only, or your plan does not include this feature. |
404 |
The resource does not exist in this workspace. |
429 |
You hit the rate limit. Retry after the number of seconds in Retry-After. |
5xx |
Something went wrong on our side. Retry with exponential backoff. |
Include the request_id when you contact support.
Rate limits
Pro workspaces can make 600 requests per minute and Enterprise workspaces 3,000. Every response includes RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset headers. Exports do not count against the limit while they run.
Webhooks
Add an endpoint under Settings → Webhooks and choose the events to receive:
| Event | Sent when |
|---|---|
consent.created |
A visitor saves a new choice |
consent.updated |
A visitor changes an earlier choice |
scan.completed |
A cookie scan finishes |
scan.new_items |
A scan finds cookies or trackers that need review |
usage.threshold |
The workspace reaches 80% or 100% of its monthly consent logs |
Each delivery is a POST with a JSON body and a Cookiesh-Signature header. The header holds a timestamp and an HMAC-SHA256 signature of timestamp.body, made with your endpoint’s signing secret. Verify it before trusting the payload:
import crypto from 'node:crypto';
export function verify(rawBody: string, header: string, secret: string): boolean {
const { t, v1 } = Object.fromEntries(header.split(',').map((part) => part.split('=')));
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false; // reject old deliveries
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}
Respond with a 2xx status within 10 seconds. Failed deliveries are retried with exponential backoff for up to 24 hours. Events can arrive more than once, so use the event id to ignore duplicates.