Self-hostAPI reference
API reference for React
Every option of createConsentBackend(), every route, and the request body, generated from the source.
The tables on this page are generated from @cookieyes/backend's source and its OpenAPI document. A running backend serves the same document at /openapi.json, and a readable version at /docs.
createConsentBackend(options)
| Name | Type | Default | Description |
|---|---|---|---|
databaserequired | ConsentStore | None | Where records are kept: postgresStore(client), sqliteStore(db), d1Store(env.DB) or memoryStore(). |
trustedOriginsrequired | readonly string[] | None | Origins whose pages may send records, like https://shop.example.com. https://*.example.com matches subdomains. A browser on any other origin gets 403. |
basePathoptional | string | the root | The path the handler is mounted at. Point the SDK's apiUrl at the same path. |
apiKeysoptional | readonly string[] | none: owner routes answer 401 | Keys for the owner routes (lookup, export, delete, prune), sent as Authorization: Bearer <key>. At least 32 characters each. |
retentionMonthsoptional | number | 24 | How long a record is kept before prune() deletes it. At least 12, the lifetime of the consent cookie. |
readBackoptional | boolean | true | Serve GET /consents/:consentId/current. Set false for a write-only backend. |
ipAddressoptional | object | not stored | Store a reduced form of the visitor's IP address. Leave it out to store none. |
ipAddress.storerequired | "truncated" | "hashed" | None | "truncated" keeps the network only (a /24 or /48). "hashed" keeps a keyed hash. A raw address is never stored. |
ipAddress.secretoptional | string | None | The key for "hashed", at least 32 characters. Keep it out of the database. |
ipAddress.headersoptional | readonly string[] | cf-connecting-ip, x-real-ip, x-forwarded-for | Request headers that carry the visitor's address, first match wins. |
rateLimitoptional | false | object | on | Limits per visitor and per client address. false turns limiting off. |
rateLimit.perVisitoroptional | number | 10 | Saves one consent id may make in each window. |
rateLimit.perClientoptional | number | 120 | Requests one client address may make in each window. |
rateLimit.windowMsoptional | number | 60000 | Length of a window, in milliseconds. |
rateLimit.limiteroptional | RateLimiter | in this process | A shared limiter, such as one backed by Redis or Cloudflare KV, for a backend that runs as several instances. |
logoptional | ((entry: LogEntry) => void) | the console | Receives log entries. They say what happened, never who it happened to: no consent id, payload or address. |
It returns:
| Property | What it is |
|---|---|
handler | (request: Request) => Promise<Response>. Mount it on every method under basePath. |
routes | { GET, POST, DELETE, OPTIONS }, each the same handler, for frameworks that export one function per method. |
migrate(options?) | Creates or upgrades the tables. { dryRun: true } only reports what would run. |
prune() | Deletes records older than retentionMonths and resolves to how many. |
Routes
Paths are relative to basePath.
| Route | Access | What it does |
|---|---|---|
POST / | Public | Save a consent decision. Stores the decision once per recordId. Sending it again is safe and answers 200. Only trusted origins may call it from a browser. |
GET /consents/{consentId}/current | Public | Read a visitor's latest decision. Public, like the save route, and can be turned off with readBack: false. Never includes the IP address. |
GET /consents/{consentId} | Owner key | Every record for one visitor. For answering a request to see one's data. Send Accept: text/csv, or ?format=csv, for a spreadsheet. |
DELETE /consents/{consentId} | Owner key | Delete every record for one visitor. Real deletion: nothing is kept or flagged. |
POST /prune | Owner key | Delete records older than the retention period |
GET /status | Public | Check the server is live and its database ready |
GET /openapi.json | Public | This document |
GET /docs | Public | This document as a web page |
Request body
What the SDK sends to the save route. Unknown fields are ignored and never stored.
| Field | Type | Personal data | Meaning |
|---|---|---|---|
recordId | string | no | Id of this decision. The same decision always has the same id, so a retry is stored once. |
consentId | string | pseudonymous | Random id of the visitor, kept in their consent cookie. |
categories | Record<string, boolean> | no | Each category id, and whether the visitor granted it. |
regulation | "GDPR" | "CCPA" | "DEFAULT" | no | The regulation the banner applied. |
domain | string | no | Hostname of the site where the visitor decided. |
decidedAt | string | no | When the visitor decided, in UTC. Not when the record was sent. |
taxonomyHash | string | no | Fingerprint of the category list the visitor was shown. |
action | "accept_all" | "reject_all" | "accept_selected" | "save" | no | What the visitor did. |
source | "banner" | "preferences" | "optout" | "api" | no | Where the visitor decided. api is a call from the site's own code. |
region | string | coarse location | Region the SDK detected, like US-CA. Sent only with region detection on. |
sdkVersion | string | no | Version of @cookieyes/core that sent the record. |
gpc | boolean | no | Whether the browser sent the Global Privacy Control signal. |
Errors
Every error answer has the same shape, and never includes database details:
{ "error": { "code": "invalid_payload", "message": "regulation must be one of GDPR, CCPA, DEFAULT." } }| Status | code | When |
|---|---|---|
| 400 | invalid_json | The body is not JSON. |
| 401 | unauthorized, owner_routes_disabled | The owner key is wrong or missing, or no apiKeys are configured. |
| 403 | origin_not_allowed | A browser on an origin not in trustedOrigins. |
| 404 | not_found | No such route, or no decision for the visitor. |
| 405 | method_not_allowed | A known route with another method. The Allow header lists the right ones. |
| 409 | record_conflict | A different decision under a recordId already stored. |
| 413 | payload_too_large | The body is over 4 KB. |
| 415 | unsupported_media_type | The body is not sent as application/json. |
| 422 | invalid_payload, record_id_required | The body breaks the contract, or comes from an SDK before 0.9.0, which sends no recordId. |
| 429 | rate_limited | Too many requests. Wait the seconds in Retry-After. |
| 500 | internal_error | The database failed. The cause is in your server log. |