API reference
Consent records
The consent log: search it, read one record, export it.
GET
/v1/consent/eventsConsent-decision audit log (proof-of-consent receipts)
Needs: A signed-in panel session, sent as Authorization: Bearer <access token>.
| Name | Where | Type | What it is |
|---|---|---|---|
| domainId | query | string (uuid) | |
| action | query | "accept_all" | "reject_all" | "custom" | Decision type (impressions are always excluded from the log). |
| source | query | "banner" | "gpc" | Where the decision came from: a banner, or the browser's Global Privacy Control signal. Filterable because the two answer different questions. "How many people chose this?" is about banners; "how many browsers told us to stop?" is about GPC, and a log that mixes them silently answers neither. |
| country | query | string | ISO-3166-1 alpha-2 country. |
| from | query | string | Only events at/after this ISO timestamp. |
| to | query | string | Only events at/before this ISO timestamp. |
| device | query | "desktop" | "mobile" | "tablet" | Device class, derived from the stored user-agent. Filtered in the database rather than in the panel. The browser used to classify only the rows it had already fetched, so asking for "Mobile" searched the newest 500 decisions instead of the log, and answered "no results" for a workspace with plenty of them further back. |
| status | query | "valid" | "expired" | Whether the decision is still within the ~12 months a consent is treated as current. Same cutoff the receipt's own status is derived from, applied as a date range so the two can never disagree. |
| q | query | string | Free-text search: a consent id (a UUID, matched exactly) or a visitor hash (matched as a prefix/substring). Those are the only two identifiers a receipt has — there is no name or email in a consent record, by design. |
| limit | query | number | Page size for the log. Exports ignore the default and take everything up to EXPORT_MAX_ROWS — an audit trail that stops at a page boundary is not one. |
| Status | Body | When |
|---|---|---|
| 200 | ConsentReceiptDto[] |
| Field | Type | What it is |
|---|---|---|
| id required | string | |
| domainId required | string | null | Null once the domain has been deleted; the record is kept until its retention ends. |
| hostname required | string | The site the record was collected on — kept even after the domain is deleted. |
| domainDeleted required | boolean | True when the domain has been deleted since: the record outlives it. |
| action required | string | accept_all | reject_all | custom (impressions are excluded from the log). |
| source required | string | banner when a person pressed a button, gpc when their browser signalled. A GPC opt-out is a binding decision in California, Colorado and Connecticut and belongs in the log beside the others — but it is different evidence, with no wording snapshot and no impression behind it, and the receipt says so rather than letting a reader assume somebody read a banner. |
| analytics required | boolean | |
| marketing required | boolean | |
| preferences required | boolean | |
| country required | string | null | ISO-3166-1 alpha-2, or null when the region is unknown. |
| ipHash required | string | null | Salted hash of the visitor IP (never the raw IP), or null. |
| userAgent required | string | null | |
| createdAt required | string | |
| status required | "valid" | "expired" | valid or expired. A decision expires after about 12 months, the same time the tag keeps the decision cookie, so a record and the browser state it describes expire together. |
| bannerVersion required | number | null | The banner's publish counter at the moment of this decision, or null for a decision recorded before this was captured. |
| policyUrl required | string | null | The privacy/cookie policy URL the visitor was shown, or null if none was set or this decision predates capturing it. |
| bannerTitle required | string | null | The banner's title/description exactly as shown, or null if this decision predates capturing it. |
| bannerDescription required | string | null | |
| bannerLocale required | string | null | The language the banner was shown in — a BCP-47 tag, or null for a decision made before the banner had more than one, which means the default language. Part of the proof, not decoration: bannerTitle and bannerDescription above are only demonstrably the text this visitor could read if the record also says which language they are in. |
| device required | "desktop" | "mobile" | "tablet" | Device class (desktop, mobile or tablet), worked out on the server from userAgent. |
GET
/v1/consent/events/exportExport the consent audit log as CSV
Needs: A signed-in panel session, sent as Authorization: Bearer <access token>.
| Name | Where | Type | What it is |
|---|---|---|---|
| domainId | query | string (uuid) | |
| action | query | "accept_all" | "reject_all" | "custom" | Decision type (impressions are always excluded from the log). |
| source | query | "banner" | "gpc" | Where the decision came from: a banner, or the browser's Global Privacy Control signal. Filterable because the two answer different questions. "How many people chose this?" is about banners; "how many browsers told us to stop?" is about GPC, and a log that mixes them silently answers neither. |
| country | query | string | ISO-3166-1 alpha-2 country. |
| from | query | string | Only events at/after this ISO timestamp. |
| to | query | string | Only events at/before this ISO timestamp. |
| device | query | "desktop" | "mobile" | "tablet" | Device class, derived from the stored user-agent. Filtered in the database rather than in the panel. The browser used to classify only the rows it had already fetched, so asking for "Mobile" searched the newest 500 decisions instead of the log, and answered "no results" for a workspace with plenty of them further back. |
| status | query | "valid" | "expired" | Whether the decision is still within the ~12 months a consent is treated as current. Same cutoff the receipt's own status is derived from, applied as a date range so the two can never disagree. |
| q | query | string | Free-text search: a consent id (a UUID, matched exactly) or a visitor hash (matched as a prefix/substring). Those are the only two identifiers a receipt has — there is no name or email in a consent record, by design. |
| limit | query | number | Page size for the log. Exports ignore the default and take everything up to EXPORT_MAX_ROWS — an audit trail that stops at a page boundary is not one. |
| Status | Body | When |
|---|---|---|
| 200 | string |
GET
/v1/consent/events/export.pdfExport the consent audit log as PDF
Needs: A signed-in panel session, sent as Authorization: Bearer <access token>.
| Name | Where | Type | What it is |
|---|---|---|---|
| domainId | query | string (uuid) | |
| action | query | "accept_all" | "reject_all" | "custom" | Decision type (impressions are always excluded from the log). |
| source | query | "banner" | "gpc" | Where the decision came from: a banner, or the browser's Global Privacy Control signal. Filterable because the two answer different questions. "How many people chose this?" is about banners; "how many browsers told us to stop?" is about GPC, and a log that mixes them silently answers neither. |
| country | query | string | ISO-3166-1 alpha-2 country. |
| from | query | string | Only events at/after this ISO timestamp. |
| to | query | string | Only events at/before this ISO timestamp. |
| device | query | "desktop" | "mobile" | "tablet" | Device class, derived from the stored user-agent. Filtered in the database rather than in the panel. The browser used to classify only the rows it had already fetched, so asking for "Mobile" searched the newest 500 decisions instead of the log, and answered "no results" for a workspace with plenty of them further back. |
| status | query | "valid" | "expired" | Whether the decision is still within the ~12 months a consent is treated as current. Same cutoff the receipt's own status is derived from, applied as a date range so the two can never disagree. |
| q | query | string | Free-text search: a consent id (a UUID, matched exactly) or a visitor hash (matched as a prefix/substring). Those are the only two identifiers a receipt has — there is no name or email in a consent record, by design. |
| limit | query | number | Page size for the log. Exports ignore the default and take everything up to EXPORT_MAX_ROWS — an audit trail that stops at a page boundary is not one. |
| Status | Body | When |
|---|---|---|
| 200 | — |
GET
/v1/consent/events/facetsWhat is in the log, for building filter options
Counted over the whole log rather than the caller's current filters, so the options do not disappear as they are used.
Needs: A signed-in panel session, sent as Authorization: Bearer <access token>.
| Status | Body | When |
|---|---|---|
| 200 | ConsentFacetsDto |
| Field | Type | What it is |
|---|---|---|
| countries required | ConsentCountryFacetDto[] | |
| countries[].code required | string | null | ISO-3166-1 alpha-2, or null for decisions whose region was never resolved. |
| countries[].count required | number |
Next groupCompliance