Widget Endpoints
Public API endpoints called by the embedded consent widget.
These endpoints are called by the widget running on customer websites. They require no authentication and support CORS from any origin.
Two config paths exist. Gateway endpoints take an opaque public token and are
what the dashboard hands out; consent endpoints take the raw site UUID and
are the legacy path, still used by the npm SDK. Both reject inactive sites. When
a request carries an Origin or Referer header, its hostname must match the
site's registered domain (exact or subdomain match); requests with neither
header, such as server-to-server calls, are allowed.
Rate limits
Widget endpoints are rate limited per IP. Exceeding the limit returns 429
with a RATE_LIMITED error code. Every response carries X-RateLimit-Limit,
X-RateLimit-Remaining, and X-RateLimit-Reset headers.
Get Config by Token
GET /api/gateway?token=ct_...The recommended way to fetch widget config. Responds with the same body as
GET /api/consent/config/:siteId below.
Query parameters
| Name | Required | Description |
|---|---|---|
token | Yes | Public token, ct_ followed by 64 hex characters |
Errors
| Status | Meaning |
|---|---|
400 | Missing token, or token does not match ct_[a-f0-9]{64} |
404 | No site matches the token |
403 | Site is not active, or Origin is not allowed |
Successful responses are cached: public, max-age=300, s-maxage=3600, stale-while-revalidate=86400.
Same 400 / 403 / 404 cases as the config endpoint above.
Get Config by Site ID (legacy)
GET /api/consent/config/:siteId| Parameter | Type | Location | Description |
|---|---|---|---|
siteId | UUID | Path | The site identifier |
200 OK
{
"siteId": "550e8400-e29b-41d4-a716-446655440000",
"siteName": "My Website",
"layout": { "position": "bottom-right", "mode": "bar" },
"colors": { "theme": "auto", "accent": "#3b82f6" },
"content": { "privacyPolicyUrl": "https://example.com/privacy" },
"buttons": { "acceptAll": "Accept", "rejectAll": "Reject" },
"config": {
"layout": { "position": "bottom-right", "mode": "bar" },
"colors": { "theme": "auto", "accent": "#3b82f6" },
"content": { "privacyPolicyUrl": "https://example.com/privacy" },
"buttons": { "acceptAll": "Accept", "rejectAll": "Reject" }
},
"categories": [
{
"key": "necessary",
"name": "Necessary",
"description": "Essential cookies required for the website to function.",
"required": true,
"default": true
},
{
"key": "analytics",
"name": "Analytics",
"description": "Help us understand how visitors interact with our website.",
"required": false,
"default": false
}
]
}The site's configuration is a free-form object written by the dashboard customizer; treat it as opaque. Its fields are spread across the top level of the response (where the widget reads them) and also returned as the nested config key. siteId, siteName, and categories always win over any same-named key in the configuration.
Errors: 400 Invalid UUID, 403 Site not active or origin not allowed, 404 Site not found
Record Consent Event
POST /api/consent/eventsRequest Body
{
"siteId": "550e8400-e29b-41d4-a716-446655440000",
"action": "accept_all",
"categories": {
"necessary": true,
"analytics": true,
"marketing": false
},
"visitorHash": "anon_7f3a9b2c4d5e"
}| Field | Type | Required | Description |
|---|---|---|---|
siteId | UUID | Yes | The site identifier |
action | string | Yes | accept_all, reject_all, or customize |
categories | object | No | Map of category keys to boolean consent values |
visitorHash | string | Yes | Anonymous visitor identifier |
200 OK
{
"success": true,
"consentId": "generated-uuid"
}Errors: 400 Invalid JSON / missing fields / invalid action / invalid UUID, 403 Site not active, 404 Site not found
SDK Endpoints
@consentify/core in cloud mode (createConsentify({ siteId })) uses its own
config format, served under /api/sdk. Point it here with endpoints:
const consentify = await createConsentify({
siteId: 'your-site-id',
endpoints: {
config: 'https://consentify.dev/api/sdk',
ingest: 'https://consentify.dev/api/sdk',
},
});Current policy pointer
GET /api/sdk/config/:siteId/latest.json{ "current": "a1b2c3d4" }Short-lived cache: this pointer must change promptly when you edit categories.
Policy body
GET /api/sdk/config/:siteId/<hash>.json{
"categories": ["analytics", "marketing"],
"policyIdentifier": "a1b2c3d4",
"mode": "opt-out",
"consentMaxAgeDays": 180
}Content-addressed, so the current hash is cached hard. categories lists only
what the visitor chooses — required categories are implicit. mode and
consentMaxAgeDays appear only when set on the site.
Record an event
POST /api/sdk/v1/eventsIdentical body and behaviour to Record Consent Event above — the SDK just expects it at this path.