Public endpoints
What the tag calls from a visitor's browser, and the free audit: no credentials, rate-limited.
/v1/public/auditsAudit one domain, once, without an account
Visits up to three pages of the domain three times each — no choice, after reject, after accept — and reports what it proves. Rate-limited per requester and per domain. A domain gets one unverified audit; a second requires proving control (409 carries the challenge). Results exist for 24 hours and are readable only with the returned token.
Needs: Nothing. This route is public.
| Field | Type | What it is |
|---|---|---|
| hostname required | string | A hostname or URL. Normalised server-side. Example: "example.com" |
| verify | "dns_txt" | "meta_tag" | "file" | Only for a domain that has already had its one unverified audit: how you have published the challenge from the previous 409, so we can check it. |
| Status | Body | When |
|---|---|---|
| 202 | PublicAuditCreatedDto | |
| 409 | — | verification_required — the body carries challenge |
| 429 | — | A limit was reached; retryAfterSeconds says when |
| Field | Type | What it is |
|---|---|---|
| id required | string | |
| token required | string | Shown once. The only way to read or delete this audit — keep it for the session. It is stored as a hash; we cannot show it again. |
| hostname required | string | |
| status required | "queued" | |
| pageBudget required | number | |
| expiresAt required | string | When the result stops existing. |
/v1/public/audits/{id}Delete your audit now, rather than in 24 hours
Needs: Nothing. This route is public.
| Name | Where | Type | What it is |
|---|---|---|---|
| id required | path | string | |
| X-Audit-Token required | header | string |
| Status | Body | When |
|---|---|---|
| 204 | — |
/v1/public/audits/{id}Progress and result of your audit
Needs: Nothing. This route is public.
| Name | Where | Type | What it is |
|---|---|---|---|
| id required | path | string | |
| X-Audit-Token required | header | string | The token POST returned |
| Status | Body | When |
|---|---|---|
| 200 | PublicAuditDto | |
| 404 | — | Unknown id, wrong token, or older than 24 hours — indistinguishable on purpose |
| Field | Type | What it is |
|---|---|---|
| id required | string | |
| hostname required | string | |
| status required | "queued" | "running" | "completed" | "blocked" | "failed" | |
| verdict required | "assessed" | "undetermined" | "blocked" | "not_run" | null | Only assessed established anything. Read it before any count. |
| reason required | string | null | Why nothing could be established. |
| pageBudget required | number | |
| pagesVisited required | number | |
| ahead required | number | null | Audits ahead of this one, while queued. |
| findings required | PublicFindingDto[] | |
| findings[].type required | "consent_state_mismatch" | "leak" | "failed_to_fire" | "fired_correctly" | "cookieless_ping" | |
| findings[].severity required | "critical" | "warning" | "info" | |
| findings[].vendor required | string | |
| findings[].host required | string | |
| findings[].pageUrl required | string | |
| findings[].passKind required | "no_interaction" | "reject" | "accept" | |
| findings[].title required | string | |
| findings[].detail required | string | |
| findings[].evidence required | PublicEvidenceDto[] | |
| choices required | AuditChoicesDto | null | Whether "after reject" and "after accept" could be reached. A choice that was not reached was not checked — say "could not reach a decision", never "fine". |
| choices.reject required | ChoiceResultDto | |
| choices.accept required | ChoiceResultDto | |
| createdAt required | string | |
| finishedAt required | string | null | |
| expiresAt required | string |
/v1/public/consentRecord a banner view or a consent decision (sent by the tag)
Needs: Nothing. This route is public.
| Field | Type | What it is |
|---|---|---|
| siteId | string | |
| event | "shown" | "decision" | |
| state | ConsentStateDto | |
| state.necessary | boolean | Always true: necessary cookies cannot be declined. |
| state.analytics | boolean | |
| state.marketing | boolean | |
| state.preferences | boolean | |
| act | "accept" | "reject" | "save" | Which control the visitor actually used. Sent by current scripts; older cached ones send only state, for which the action is derived from the state against the categories the banner offers. |
| source | "banner" | "gpc" | Where the decision came from: a person pressing a button, or the browser's Global Privacy Control signal. gpc is a real, legally binding opt-out in California, Colorado and Connecticut, to be honoured exactly as a click would be. It is recorded distinctly because it is not the same evidence: nobody was shown a banner, so there is no wording to snapshot and no impression it belongs to. Absent means banner, which is what every script cached in a browser today sends. |
| locale | string | The language the banner was shown in, as returned in the config's locale. The receipt records the wording in this language, which is what the visitor could read. Absent means the default language. |
| ts | number |
| Status | Body | When |
|---|---|---|
| 204 | — |
/v1/public/scannerHow the scanner identifies itself, and the addresses it connects from
Needs: Nothing. This route is public.
| Status | Body | When |
|---|---|---|
| 200 | ScannerIdentityDto |
| Field | Type | What it is |
|---|---|---|
| userAgent required | string | |
| egressAddresses required | string[] | IPs or CIDR ranges; empty when the deployment has not published them. |
| regions required | string[] | Where scans run from: eu, us. |
/v1/public/sites/{siteId}/configThe banner a visitor should see on a site
What the tag fetches to show the banner: its wording in the visitor’s language (lang), and the rules for their country. 404 when the site has no published banner.
Needs: Nothing. This route is public.
| Name | Where | Type | What it is |
|---|---|---|---|
| siteId required | path | string | |
| lang | query | any |
| Status | Body | When |
|---|---|---|
| 200 | SiteConfigDto | |
| 404 | — | No published banner for this site id — it was deleted, never existed, or has never been published. The tag treats this as "show nothing". |
| Field | Type | What it is |
|---|---|---|
| siteId required | string | |
| cookieDomain | string | Root domain for cross-subdomain cookie scoping (optional; script infers otherwise). |
| optOut | SiteOptOutDto | The US opt-out notice's wording. Present only for visitors under the US opt-out rules. In the banner's language when that translation has its own notice, and in the default language otherwise. |
| optOut.title required | string | |
| optOut.message required | string | |
| optOut.optOut required | string | The prominent action: deny every sale/share category at once. |
| optOut.dismiss required | string | Dismiss without changing anything. Processing continues, lawfully. |
| optOut.link required | string | The persistent link that outlives the notice — the part the law requires. |
| notice | SiteNoticeDto | The button label of the rest-of-world notice. Present only when mode is notice; the notice's title and text are the banner's own. |
| notice.dismiss required | string | |
| gpc | boolean | The visitor sent Sec-GPC: 1 with this request. For integrations that fetch this config on a server, where there is no navigator.globalPrivacyControl to read. The tag reads the browser API itself and ignores this. Present only when the header was sent. |
| mode required | "grant" | "banner" | "opt_out" | "notice" | Geo directive: banner = show the consent banner; grant = the visitor's region doesn't require consent, so imply consent without showing a banner. |
| jurisdiction required | "eea_uk" | "us" | "row" | Which rules apply to this visitor: eea_uk, us or row (rest of world). Worked out on the server from the visitor's country. |
| usState | string | Their US state, when the CDN gave us one. Only present for US visitors. |
| usStateHasLaw | boolean | True when that state has a comprehensive privacy law in force. |
| vitals | boolean | Measure real-user performance on this page view. Absent unless switched on — its absence is what stops the tag fetching the measurement bundle. |
| region required | string | null | The visitor's resolved country (ISO-3166-1 alpha-2), or null if unknown. |
| locale required | string | The language this response is written in — a BCP-47 tag. Resolved server-side from the lang the tag asked for against the languages the banner actually has, so it is not necessarily what was requested: a visitor asking for de-AT on a banner translated into de gets de here. The tag sets it on the banner's lang attribute (screen readers need it to pronounce the text) and sends it back on the consent beacon, so the receipt records the wording the visitor actually agreed to rather than whatever the default language happened to be. |
| blocking | object[] | |
| blocking[].match required | string | |
| blocking[].category required | "marketing" | "preferences" | "analytics" | |
| banner required | BannerConfigDto | |
| banner.content required | BannerContentDto | |
| banner.style required | BannerStyleDto | |
| banner.targeting required | BannerTargetingDto | |
| banner.behavior required | BannerBehaviorDto | |
| banner.categories required | BannerCategoryDto[] | |
| banner.cookieDomain | string | Root domain for sharing the consent cookie across subdomains, e.g. .example.com. Must be a hostname. |
| banner.hideBranding | boolean | Hide the "Powered by CMPStack" mark. Requires a plan that includes it; saving it on another plan returns 409. |
| banner.optOut | BannerOptOutDto | The US opt-out notice's wording, shown only to visitors under the US opt-out rules. When it is absent, default wording is used. |
| banner.notice | BannerNoticeDto | The rest-of-world notice's button. Optional; defaults to "OK". |
| banner.customCss | string | Custom CSS for the banner. Requires a plan that includes custom CSS. Its length and content are checked when it is saved and again when it is served. |
| banner.blocking | BlockingRuleDto[] | Third-party scripts held back until their category is allowed. At most 60. |
| banner.vitals | boolean | Measure real-user performance on this site: LCP, INP and CLS per page view. Off unless switched on, because it loads one more small file on the visitor's page. |
| banner.defaultLocale | string | The language content and categories are written in, and the one shown to any visitor whose language has no translation. Absent means en. |
| banner.translations | BannerTranslationDto[] | The same banner in other languages, at most 40. The default language is not repeated here: it is content, and a translation for it is refused. |
| layout required | "bar" | "modal" | "popup" | Where the banner sits: bar pinned to the bottom edge, modal centred over a dimmed page, popup in a corner. |
| categories required | CategoryConfigDto[] | |
| categories[].id required | "marketing" | "preferences" | "analytics" | "necessary" | |
| categories[].label required | string | |
| categories[].description required | string | |
| categories[].required | boolean | |
| categories[].sensitive | boolean | Sensitive data: stays opt-in even under an opt-out regime, because US state laws carve it out of the "run until told to stop" default. |
| style required | StyleConfigDto | |
| style.background required | string | |
| style.foreground required | string | |
| style.accent required | string | |
| style.font required | string | |
| style.titleSize required | number | |
| style.bodySize required | number | |
| style.radius required | number | |
| style.buttonRadius required | number | |
| style.shadow required | "none" | "sm" | "md" | "lg" | |
| behavior required | BehaviorConfigDto | |
| behavior.delaySeconds required | number | Seconds to wait before showing the banner. |
| behavior.scrollTrigger required | boolean | Hold the banner back until the visitor has scrolled a quarter of the page. |
| behavior.exitIntent required | boolean | Show when the cursor leaves the viewport. |
| behavior.blockScroll required | boolean | Lock page scrolling until a choice is made. Modal layout only. |
| behavior.autoAccept required | boolean | Treat a scroll as acceptance. Never sent to a visitor who is covered by GDPR — the server strips it rather than trusting the script to apply the exemption. |
| behavior.consentExpiryMonths required | number | How long a decision is honoured before the visitor is asked again. 12 unless the site says otherwise; the tag writes it as the cookie's max-age. |
| behavior.rejectionMemoryMonths required | number | Six-month rejection memory: a visitor who rejected is not re-asked for this many months even if consent expires sooner. 0 (off, the default) or 6. |
| theme | ThemeConfigDto | |
| theme.primary | string | |
| hideBranding | boolean | Suppress the "Powered by CMPStack" mark. Only ever present when the workspace's plan includes it — the script shows the mark whenever this is absent, so a failed or unreadable config falls back to branded. |
| customCss | string | The banner's custom CSS, already checked and made safe. Present only on plans that include custom CSS. |
/v1/public/tag-errorsReport a banner error (sent by the tag)
The banner reporting that it failed on a customer’s page. Carries the site id, the error’s message and its stack and nothing else: no identifier, no IP, no user agent, no page address, and every address in the text is reduced to its origin and file. Refused unless the site has a published banner and the Origin matches its domain, and rate-limited per site and overall.
Needs: Nothing. This route is public.
| Status | Body | When |
|---|---|---|
| 204 | — |
/v1/public/vitalsRecord one page view’s performance (sent by the tag)
One page view’s LCP, INP and CLS, from the sites that switched real-user measurement on. Carries a path and three rounded numbers and nothing else: no identifier, no IP, no user agent, no referrer. Refused unless the domain has vitals enabled in its published banner and the Origin matches that domain.
Needs: Nothing. This route is public.
| Status | Body | When |
|---|---|---|
| 204 | — |
Next groupDomains