Skip to content
Self-hostAPI reference

API reference for React

Markdown
Loading…

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)

NameTypeDefaultDescription
databaserequiredConsentStoreNoneWhere records are kept: postgresStore(client), sqliteStore(db), d1Store(env.DB) or memoryStore().
trustedOriginsrequiredreadonly string[]NoneOrigins whose pages may send records, like https://shop.example.com. https://*.example.com matches subdomains. A browser on any other origin gets 403.
basePathoptionalstringthe rootThe path the handler is mounted at. Point the SDK's apiUrl at the same path.
apiKeysoptionalreadonly string[]none: owner routes answer 401Keys for the owner routes (lookup, export, delete, prune), sent as Authorization: Bearer <key>. At least 32 characters each.
retentionMonthsoptionalnumber24How long a record is kept before prune() deletes it. At least 12, the lifetime of the consent cookie.
readBackoptionalbooleantrueServe GET /consents/:consentId/current. Set false for a write-only backend.
ipAddressoptionalobjectnot storedStore 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.secretoptionalstringNoneThe key for "hashed", at least 32 characters. Keep it out of the database.
ipAddress.headersoptionalreadonly string[]cf-connecting-ip, x-real-ip, x-forwarded-forRequest headers that carry the visitor's address, first match wins.
rateLimitoptionalfalse | objectonLimits per visitor and per client address. false turns limiting off.
rateLimit.perVisitoroptionalnumber10Saves one consent id may make in each window.
rateLimit.perClientoptionalnumber120Requests one client address may make in each window.
rateLimit.windowMsoptionalnumber60000Length of a window, in milliseconds.
rateLimit.limiteroptionalRateLimiterin this processA shared limiter, such as one backed by Redis or Cloudflare KV, for a backend that runs as several instances.
logoptional((entry: LogEntry) => void)the consoleReceives log entries. They say what happened, never who it happened to: no consent id, payload or address.

It returns:

PropertyWhat 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.

RouteAccessWhat it does
POST /PublicSave 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}/currentPublicRead 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 keyEvery 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 keyDelete every record for one visitor. Real deletion: nothing is kept or flagged.
POST /pruneOwner keyDelete records older than the retention period
GET /statusPublicCheck the server is live and its database ready
GET /openapi.jsonPublicThis document
GET /docsPublicThis document as a web page

Request body

What the SDK sends to the save route. Unknown fields are ignored and never stored.

FieldTypePersonal dataMeaning
recordIdstringnoId of this decision. The same decision always has the same id, so a retry is stored once.
consentIdstringpseudonymousRandom id of the visitor, kept in their consent cookie.
categoriesRecord<string, boolean>noEach category id, and whether the visitor granted it.
regulation"GDPR" | "CCPA" | "DEFAULT"noThe regulation the banner applied.
domainstringnoHostname of the site where the visitor decided.
decidedAtstringnoWhen the visitor decided, in UTC. Not when the record was sent.
taxonomyHashstringnoFingerprint of the category list the visitor was shown.
action"accept_all" | "reject_all" | "accept_selected" | "save"noWhat the visitor did.
source"banner" | "preferences" | "optout" | "api"noWhere the visitor decided. api is a call from the site's own code.
regionstringcoarse locationRegion the SDK detected, like US-CA. Sent only with region detection on.
sdkVersionstringnoVersion of @cookieyes/core that sent the record.
gpcbooleannoWhether the browser sent the Global Privacy Control signal.

Errors

Every error answer has the same shape, and never includes database details:

Example error
{ "error": { "code": "invalid_payload", "message": "regulation must be one of GDPR, CCPA, DEFAULT." } }
StatuscodeWhen
400invalid_jsonThe body is not JSON.
401unauthorized, owner_routes_disabledThe owner key is wrong or missing, or no apiKeys are configured.
403origin_not_allowedA browser on an origin not in trustedOrigins.
404not_foundNo such route, or no decision for the visitor.
405method_not_allowedA known route with another method. The Allow header lists the right ones.
409record_conflictA different decision under a recordId already stored.
413payload_too_largeThe body is over 4 KB.
415unsupported_media_typeThe body is not sent as application/json.
422invalid_payload, record_id_requiredThe body breaks the contract, or comes from an SDK before 0.9.0, which sends no recordId.
429rate_limitedToo many requests. Wait the seconds in Retry-After.
500internal_errorThe database failed. The cause is in your server log.

Next steps

On this page