# Consent API

The Consent area publishes 15 REST endpoints. Paths are relative to `https://api.lessly.com`; see [Authentication](/reference/openapi#authentication) for the required header.

### `PATCH` `/consent/config`

Configures the always-available "Cookie settings" entry point that lets a visitor reopen their consent preferences after they have made a choice. By default Lessly renders a floating icon in a page corner; you can move it, or bind the entry point to an element the site already has (for example a "Cookie settings" link in the footer) instead of showing the icon. Fields (under `floatingIcon`): enabled — whether to render Lessly's built-in floating icon; position — which corner the icon sits in ("bottom-right" / "bottom-left" / "top-right" / "top-left"); customSelector — a CSS selector for an existing element on the page (e.g. "#footer-cookie-link") whose click opens consent settings, or null to rely on the built-in icon only. To hide the built-in icon (enabled: false) you must provide a customSelector, so the visitor always keeps a way to reopen settings.

MCP tool: `consent_config_floating_icon_upsert`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| floatingIcon | body | object | • | — |

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| floatingIcon | object | • | — |

### `PATCH` `/consent/config/cookie-domain`

Sets an explicit cookie Domain for the consent cookie (lessly_consent). By default the banner derives the widest owned boundary automatically: the registrable domain (eTLD+1) on customer sites, or the deploy host on Lessly platform hosts — so consent given on site.com also covers docs.site.com. Set cookieDomain only to NARROW that perimeter, e.g. when several Lessly products live on subdomains of one domain and each must keep its own consent (cookieDomain: "docs.site.com" on the docs product). The value must be a suffix of the page hostname at runtime, otherwise the banner ignores it and falls back to auto-derivation. Platform deploy suffixes (lessly.dev / lessly.com / lessly.run / lessly.tech / v2.*) are rejected — a cookie on those would leak consent across tenants. Pass null or an empty string to remove the override.

MCP tool: `consent_config_cookie_domain_upsert`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| cookieDomain | body | string | • | — |

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| cookieDomain | string | • | — |

### `PATCH` `/consent/config/customization`

Fine-tunes the banner beyond brand tokens, when the theme alone is not enough. logoUrl / coverUrl — https URLs to images hosted on the client's side (we store nothing; an empty string removes the asset; an external URL exposes the visitor's IP to that host); coverUrl is a header strip across the top of the banner. position — where the banner sits on the page ("bottom" / "top" / "bottom-left" / "bottom-right" / "center-modal"); this is the banner's placement, distinct from the reopen-icon corner in consent_config_floating_icon_upsert. logoAlign — "start" / "center" / "end"; contentAlign — "start" / "center". All fields are optional and patch-merge onto the previous customization. This layer does not change button equality ("Accept all" / "Reject all") or the provider list.

MCP tool: `consent_config_customization_upsert`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| logoUrl | body | string | — | — |
| coverUrl | body | string | — | — |
| position | body | string | — | — |
| logoAlign | body | string | — | — |
| contentAlign | body | string | — | — |

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| customization | object | • | — |
| customizationInput | object | • | — |

### `PATCH` `/consent/config/theme`

Adapts the consent banner's appearance to the site's brand. Pass what you observe on the client's site: brandColor — the site's primary brand color (any CSS color); surface — whether the site is "light" or "dark"; corners — button corner shape ("sharp" / "rounded" / "pill"); font — the site's font, given as a font name, a full CSS font stack, or "inherit" to adopt the host page's font. If the brand tokens are not enough, fine-tune `overrides`: backgroundColor, textColor, borderColor, focusColor, dangerColor, successColor, warningColor. All fields are optional and patch-merge onto the previous theme. To identify the site: use a URL from the request; otherwise the latest decision `pageUrl`; otherwise ask the user once. This layer is cosmetic only — after applying, "Accept all" and "Reject all" stay equally prominent and the provider list is unchanged.

MCP tool: `consent_config_theme_upsert`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| brandColor | body | string | — | — |
| surface | body | string | — | — |
| corners | body | string | — | — |
| font | body | string | — | — |
| overrides | body | object | — | — |

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| theme | object | • | — |
| themeInput | object | • | — |

### `GET` `/consent/consent/dashboard/config`

Returns consent config for the current product: theme — the editable brand-theme intent previously set via consent_config_theme_upsert; customization — the editable escape-hatch intent (logo/cover URLs, position, alignment) set via consent_config_customization_upsert (empty object if none set yet) — and floatingIcon (trigger settings). When no consent_config row exists, returns defaults; never 404s.

MCP tool: `consent_dashboard_config_get`

**Parameters**

No parameters.

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| theme | object | • | — |
| floatingIcon | object | • | — |
| customization | object | • | — |

### `GET` `/consent/consent/dashboard/decisions`

Returns the latest consent decisions for the current product — each with its HMAC signature (`stamp`) and a computed accept/deny/custom/withdrawal label. Read-only; useful to confirm decisions are still being signed.

MCP tool: `consent_dashboard_decisions_list`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| limit | query | integer | — | Maximum number of records to return (1..10, default 10) |
| offset | query | integer | — | Pagination offset (>=0, default 0) |

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| items | array | • | — |
| limit | integer | • | — |
| offset | integer | • | — |

### `GET` `/consent/consent/dashboard/embed-snippet`

Returns the consent banner embed <script> tag and public edge URL for the current product. Read-only. Insert script_tag VERBATIM, including its leading HTML comment, as the FIRST script in <head> — before any tracker, tag manager or inline loader. Never add async or defer: banner.js must execute before the parser reaches the first tracker, otherwise trackers run before consent is known.

MCP tool: `consent_dashboard_embed_snippet_get`

**Parameters**

No parameters.

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| script_tag | string | • | — |
| public_edge_url | string | • | — |

### `POST` `/consent/consent/dashboard/export`

Export consent records for the current product as CSV (workspace-scoped). Returns inline CSV up to `limit` rows; if more rows match the filter, also returns `download_url` pointing to the streaming HTTP endpoint with the same filters applied. For an Art. 15 (DSAR) access request, pass `record_id` (the UUID from the visitor's consent cookie) to filter the export to that visitor's full decision chain.

MCP tool: `consent_records_export`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| from | body | string | — | Inclusive lower bound for created_at (server clock), ISO-8601. Example: "2025-01-01T00:00:00Z". Omit for "from the beginning". |
| to | body | string | — | Inclusive upper bound for created_at, ISO-8601. Example: "2026-01-01T00:00:00Z". Omit for "up to now". |
| limit | body | integer | — | Maximum rows to return inline. Default 1000, hard cap 10000. If more rows match, the response sets truncated=true and includes download_url for the full dataset via HTTP. |
| record_id | body | string | — | DSAR (Art. 15) lookup: a visitor-presented record_id (the UUID from their consent cookie). When set, the export is filtered to that visitor's full decision chain (transitive closure over replaces_id), scoped to the current product. Omit for a product-wide admin export. |

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| csv | string | • | — |
| filters | object | • | — |
| truncated | boolean | • | — |
| total_count | integer | • | — |
| download_url | string | — | — |
| returned_count | integer | • | — |

### `GET` `/consent/consent/dashboard/stats`

Returns lightweight dashboard stats for the current product (currently total record count). Read-only.

MCP tool: `consent_dashboard_stats_get`

**Parameters**

No parameters.

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| total_records | integer | • | — |

### `GET` `/consent/consent/providers`

List all consent providers (and their cookies) for the current product.

MCP tool: `consent_providers_list`

**Parameters**

No parameters.

### `POST` `/consent/consent/providers`

Create a new consent provider. cookies (optional) is the full set; cookies must have unique names within the provider.

MCP tool: `consent_providers_create`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| name | body | string | • | Provider display name, e.g. "Google Analytics 4 (gtag.js)" |
| category | body | string | • | Consent category bucket — matches CHECK constraint on consent_providers.category |
| hosts | body | array | — | Hostnames where this provider loads scripts. Required (min 1) for non-necessary categories. |
| privacyUrl | body | string | — | Link to provider privacy policy |
| description | body | string | — | Short description shown in consent UI. Stored as empty string when omitted. |
| orderIndex | body | integer | — | Sort order; lower = earlier |
| cookies | body | array | — | List of cookies/storage entries this provider sets |
| catalogKey | body | string | — | Catalog key if created from picker. Null for manual creation. |

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| id | string | • | — |
| name | string | • | — |
| hosts | array | • | — |
| cookies | array | • | — |
| category | string | • | — |
| isSystem | boolean | — | — |
| createdAt | string | • | — |
| updatedAt | string | • | — |
| catalogKey | string | — | — |
| orderIndex | integer | • | — |
| privacyUrl | string | • | — |
| description | string | • | — |

### `DELETE` `/consent/consent/providers/:id`

Delete a consent provider and all its cookies (cascade).

MCP tool: `consent_providers_delete`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| id | path | string | • | Provider id |

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| id | string | • | — |
| deleted | boolean | • | — |

### `PATCH` `/consent/consent/providers/:id`

Update an existing consent provider. cookies: undefined leaves cookies unchanged, [] clears them, [...] replaces the whole set. privacyUrl: null clears the field.

MCP tool: `consent_providers_update`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| id | path | string | • | Provider id |
| name | body | string | — | — |
| category | body | string | — | Consent category bucket — matches CHECK constraint on consent_providers.category |
| hosts | body | array | — | If provided, REPLACES the hosts list. Omit to leave unchanged. |
| privacyUrl | body | string | — | null clears the field, undefined leaves it unchanged |
| description | body | string | — | Pass empty string to clear; undefined leaves unchanged |
| orderIndex | body | integer | — | — |
| cookies | body | array | — | If provided, REPLACES the whole cookies set for this provider. Omit to leave unchanged. |

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| id | string | • | — |
| name | string | • | — |
| hosts | array | • | — |
| cookies | array | • | — |
| category | string | • | — |
| isSystem | boolean | — | — |
| createdAt | string | • | — |
| updatedAt | string | • | — |
| catalogKey | string | — | — |
| orderIndex | integer | • | — |
| privacyUrl | string | • | — |
| description | string | • | — |

### `GET` `/consent/consent/stats`

Returns aggregated consent decision stats for the current product over a configurable window (default 30d, max 365d). Returns integer percentages 0..100; due to per-bucket rounding the sum may be 99..101. Read-only.

MCP tool: `consent_stats_get`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| range | query | string | — | Time window for aggregation in days, e.g. "30d". Omit to use the server default (30d). Maximum: 365d. |

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| range | object | • | — |
| total | integer | • | — |
| custom_pct | integer | • | — |
| by_category | object | • | — |
| deny_all_pct | integer | • | — |
| accept_all_pct | integer | • | — |

### `GET` `/consent/embed`

Returns the consent banner embed <script> tag for the current product. Insert script_tag VERBATIM, including its leading HTML comment, as the FIRST script in <head> — before any tracker, tag manager or inline loader. Never add async or defer: banner.js must execute before the parser reaches the first tracker, otherwise trackers run before consent is known.

MCP tool: `consent_embed_get`

**Parameters**

No parameters.

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| product_id | string | • | — |
| script_tag | string | • | — |
| public_edge_url | string | • | — |
