REST API Reference

Exact routes, scopes, pagination, response families and write boundaries.

Content and code last checked 2026-10-06 by ConsentEase product documentation. This is a review of the written content and code, not a test on a live third-party platform.

Base URL and authentication

The public API origin follows the existing banner/public-endpoint contract:

https://consentease.io/api/v1

Send one API-key header on every request. Use a backend environment variable; the example does not contain a real key.

curl --fail-with-body 'https://consentease.io/api/v1/sites?limit=20' \
  -H "Authorization: Bearer $CONSENTEASE_API_KEY" \
  -H 'Accept: application/json'

Treat the response as JSON only after checking status/content type. Unrecognised API paths must not be accepted as a successful result merely because a SPA returned HTML 200.

Read endpoints (sites:read)

Method / path Parameters and response contract
GET /sites limit (default 20, cap 100), cursor; optional exact domain and status filter; {data: website[], nextCursor}
GET /sites/:id Website object, not a generic data/meta wrapper
GET /sites/:id/cookies Cursor pagination; {data: cookie[], nextCursor}
GET /sites/:id/categories {data: category[]}; each item has id, name, displayName, isRequired and isEnabled
GET /sites/:id/embed {publicId, scriptUrl, snippet}; canonical full snippet
GET /sites/:id/analytics period=7d,30d,90d (default 30d); {data:{daily,summary},meta}
GET /sites/:id/analytics/geographic Same period; {data:{countries},meta}
GET /sites/:id/analytics/devices Same period; {data:{devices},meta}; browser can be null
GET /sites/:id/consent-log limit (default 100, cap 1000), offset; {data:{records,total},meta}; dashboard filters are not all available here
GET /sites/:id/status {data:{signals,consentRate,lastScan,lastDiagnostic},meta}; priorBlocking can be null
GET /sites/:id/compliance Operational snapshot: websiteId, domain, status, lastScan, bannerConfigured, scan, cookies, categories, warnings
GET /sites/:id/consent-stats Required from/to, optional tz; explicit-range aggregate contract in the linked guide
GET or POST /sites/:id/installation-check One live first-visit check of the installed snippet; setupScore 0–100, checks and openItems (see Installation check below)

Use the internal website id returned by /sites for :id; it is not the banner publicId. Follow nextCursor until null for complete cursor-paginated lists. Consent-log uses offset instead; continue until records are exhausted and reconcile total.

Analytics response fields

summary includes impressions, consentRate, rejectionRate and noActionRate. Geographic rows include country, impressions, accepted, rejected, custom and consentRate. Device rows include device, browser, impressions and consentRate. These are reported event aggregates, not unique people. meta contains website, period and generated.

Write endpoints

Method / path Scope, body and result
POST /sites sites:write; {domain}; 201 website; creates defaults, starts a scan and syncs per-site subscription quantity
POST /sites/setup sites:write; {domain}; 201 created or 200 existing, with the install snippet and next steps (see Site setup below)
POST /sites/:id/cookies sites:write; name and categoryId required, category must belong to site; optional inventory fields such as provider/purpose; 201 cookie
POST /sites/:id/scan sites:scan; 202 {status:"scanning"} starts a background rescan, not an immediate completed report; daily cap applies
POST /consent consent:write; siteId, visitorId, action and consentChoices required; metered consent record

Website and cookie duplicates return 409; do not create repeatedly to recover an uncertain response. POST /sites/setup is the exception: for your own existing domain it returns that site with 200. Scan requests have a separate daily cap (currently 20 per account), apart from request rate limits.

Site setup

POST /sites/setup (scope sites:write) is idempotent onboarding for one domain. The body is {"domain": "example.com"}; the domain is normalised like POST /sites (protocol, www and path removed).

Result Meaning
201, status "created" New site with banner configuration, default categories and a first scan. It adds a paid website to your account: billing is per website and the subscription quantity is updated, as with POST /sites.
200, status "existing" You already have a site for this normalised domain. Nothing is created and billing does not change, so repeating the call is safe. A never-scanned existing site gets one first scan within the daily scan cap.

The response contains status, created, siteId, domain, publicId, scriptUrl, snippet (the exact install code), scanStatus, scanStartedNow, nextSteps (plain-language instructions), installationCheck (the follow-up MCP tool and REST path) and site (the website object). scanStatus is scanning, complete (scan complete), needs_attention or not_started. The call does not wait for the scan; poll GET /sites/:id. Sites are matched per account: you never receive another account's site, id or snippet. POST /sites itself is unchanged and still returns 409 for a duplicate.

Installation check

GET or POST /sites/:id/installation-check (scope sites:read) opens the live homepage once, as a first visit without banner interaction, in the scanner's bounded browser. Private and internal addresses are refused, for the page and for its subrequests. A check takes about 10–25 seconds.

Check id Passes when
snippet_in_head This site's ConsentEase script is in the page head, or the ConsentEase Google Tag Manager template loads the banner
snippet_before_gtm Consent Mode defaults are set before Google Tag Manager or gtag starts (also passes when neither is present)
consent_mode_defaults All seven Consent Mode v2 defaults are set: six denied, security_storage granted
script_loads The page requests this site's ConsentEase script and it returns HTTP 200
no_trackers_before_consent No tracking cookies, tracker scripts or Google measurement without denied consent before any banner interaction

Each check is worth 20 points, so setupScore runs from 0 to 100. A check that could not be measured counts as not passed, with a "Not measured" detail. The response contains siteId, domain, checkedUrl, status, reason, setupScore, checks (id, passed, detail), openItems (what to fix, in plain language), checkedAt, note and installationWarnings.

installationWarnings is not part of setupScore. It lists late loads that visitors' browsers reported on any page in the last 14 days: the ConsentEase script ran after Google Tag Manager or gtag.js had started, so Google tags may have run before the Consent Mode defaults. Each entry has code (late_load), loader (gtm, gtag or unknown), firstSeenAt, lastSeenAt, reports, samplePaths (up to 10 page paths, no query strings) and message. A report from before your fix can remain for up to 14 days.

status Meaning
complete The page loaded and was evaluated
partial The page loaded, but its HTML could not be read
unreachable The page could not be loaded; reason explains why

The setup score is a technical check of one first visit. It is not a legal assessment. Limits: one running check per account, three at a time across the service, and 30 per account per day (resets at midnight UTC). A rejected request returns 429 with a hint. Neither endpoint is metered as a billable API unit; only a newly created site is billed, as a website.

action is accept_all, reject_all or custom. consentChoices is a string (up to 8192 characters) or a supported keyed object. Optional bannerVersion, policyVersion, userAgent, country and region have bounded lengths. This is a privileged server integration, not permission to invent visitor consent or expose your key in a banner.

Optional consentTiming reports the timing actually applied by your integration: { "decidedAt": "2024-02-29T12:00:00.000Z", "renewalDays": 365 }. decidedAt must be an ISO timestamp with timezone; renewalDays must be an integer from 1 to 3650 (the dashboard offers 30–730). The server records expiresAt as that decision time plus that many 24-hour days. Timing is sender-reported, not independently verified. Without consentTiming, it uses the site's current banner period from server receipt time, or 365 days if unavailable/invalid. Capture timing when the decision is made, not when retrying delivery. The same optional contract is used by the public banner log endpoint.

expiresAt is a scheduled renewal date, not permission to delete evidence or a guarantee of legal validity. ConsentEase's default retention keeps a record until expiresAt + 24 months, never shorter than its timestamp + 365 days + 24 months (also when expiresAt is null); then it becomes eligible for removal by a daily cleanup. Historical rows and idempotent replays retain their original dates; changing banner settings does not rewrite them.

Use an optional Idempotency-Key (1–255 characters from A–Z, a–z, 0–9, underscore, dot, colon and hyphen) for each distinct record operation. A first success returns 201 {success:true,deduped:false,limited:false}; a deduplicated replay returns 200 {success:true,deduped:true}. Current deduplication does not hash/compare the full request body; never reuse a key for different records or assume modified payloads are compared.

Without consentTiming, REST-record expiry metadata uses the fallback above; expiresAt alone never triggers deletion.

Errors and retries

The REST error body is {error, code, hint?}.

HTTP code Meaning
400 bad_request Invalid parameters/body; correct before retry
401 unauthorized Missing, invalid, expired or revoked key
403 forbidden Missing scope or disallowed operation
404 not_found Missing or inaccessible website; deliberately does not reveal ownership
409 conflict Duplicate resource or incompatible operation
429 rate_limited Request/daily cap; honour Retry-After when provided
500 internal Unexpected service failure; details not exposed

Use bounded backoff for transient read failures. Preserve operation identity for eligible retries; inspect uncertain write outcomes before sending a new operation. Successful reads can be audited but are not billable consent writes.

Status semantics

Stored compliant means a scan completed, not legal compliance. Inspect lastScan and warnings. scriptsFound may be null because it is not reliably persisted; do not turn unknown into zero. Warnings include never_scanned, scan_stale, scan_zero_with_inventory, banner_not_configured and late_load_detected (details in installationWarnings); these are bounded operational indications.

Related articles

Still need help?

Contact the ConsentEase team and include the website and steps you tried.