diff --git a/api-reference/account/update-account.mdx b/api-reference/account/update-account.mdx new file mode 100644 index 0000000..61ef52e --- /dev/null +++ b/api-reference/account/update-account.mdx @@ -0,0 +1,3 @@ +--- +openapi: patch /accounts/{id} +--- diff --git a/api-reference/authentication.mdx b/api-reference/authentication.mdx index 433df0b..9b657e3 100644 --- a/api-reference/authentication.mdx +++ b/api-reference/authentication.mdx @@ -66,7 +66,7 @@ If you exceed a limit you receive a `429 Too Many Requests` response with a `Ret ## Pagination -List endpoints support pagination with the following query parameters: +List endpoints support pagination with the following query parameters. [Banner templates](/api-reference/consent-banners/list-banner-templates) is the one exception — it returns the whole catalogue and accepts neither parameter. | Parameter | Description | Default | |-----------|-------------|---------| @@ -90,15 +90,40 @@ Resource endpoints expect the resource wrapped under its own key rather than at } ``` -The wrapper matches the resource: `category`, `storage_item`, `group`, `account_user`, `account_invitation`, and `user` for [Change password](/api-reference/auth/change-password). +The wrapper matches the resource, though not always the path — `/services` wraps in `site_service`: -[Log in](/api-reference/auth/login) is the exception — it takes `email` and `password` at the top level, unwrapped. +| Endpoint | Wrapper | +|---|---| +| `/categories` | `category` | +| `/storage-items` | `storage_item` | +| `/services` | `site_service` | +| `/consent-banners` | `consent_banner` | +| `/scans` | `scan` | +| `/integrations` | `integration` | +| `/accounts` | `account` | +| `/groups` | `group` | +| `/account-users` | `account_user` | +| `/account-invitations` | `account_invitation` | +| [Change password](/api-reference/auth/change-password) | `user` | + +Two endpoints take their body unwrapped: [Log in](/api-reference/auth/login), which takes `email` and `password` at the top level, and [Create consent banner from a template](/api-reference/consent-banners/create-consent-banner-from-template), which takes `jurisdiction_id` — a selector for which template to build from, not an attribute of the banner it creates. + +## Choosing an account + +An account API key identifies exactly one account, so there is nothing to choose. A user API key can reach several, and the endpoints that declare an `account_id` query parameter — the account's API id — let you say which one you mean. Endpoints that don't declare it act on the account the key resolves to on its own. + +Where an endpoint accepts it, reads treat it as optional — it just narrows the request — while writes require it whenever the key can reach more than one account: without it the request would act on an account it picked rather than one you named, so it is refused with a `400`. Endpoints that don't accept the parameter are unaffected either way. + +``` +POST /api/v1/consent-banners?account_id=aBcD1234 +``` ## Response Envelopes | Response | Shape | |----------|-------| | List endpoints | `{ "pagination": { ... }, "data": [ ... ] }` | +| [Banner templates](/api-reference/consent-banners/list-banner-templates) | `{ "data": [ ... ] }` — no pagination | | Single resource | `{ "data": { ... } }` | | A single group or account | The resource itself, unwrapped | | Resource-deletion endpoints | `{ "message": "..." }` | @@ -106,6 +131,8 @@ The wrapper matches the resource: `category`, `storage_item`, `group`, `account_ Group and account **listings** use the standard list envelope in the first row — only their single-resource responses are unwrapped. +Banner templates are a fixed catalogue rather than a collection of the account's own records, so that endpoint is not paginated and returns no `pagination` object. + ## Errors The API returns standard HTTP status codes. @@ -114,10 +141,11 @@ The API returns standard HTTP status codes. |-------------|-------------| | `200` | Success | | `201` | Created | -| `400` | Bad request — an invalid `page` parameter | -| `401` | Unauthorized — invalid or missing API token, or a token of the wrong type | -| `404` | Not found | -| `422` | Validation error | +| `400` | Bad request — an invalid `page` parameter, or a write from a user key that can reach more than one account without an `account_id` | +| `401` | Unauthorized — invalid or missing API token, a token of the wrong type, or, on [Log in](/api-reference/auth/login), the wrong email or password | +| `403` | Forbidden — the key resolved to a user who is not an admin of the account, on a change the dashboard reserves for admins; or the account's plan does not include the feature, as with the [compliance report](/api-reference/compliance/get-compliance-report) | +| `404` | Not found — including a referenced record that belongs to another account | +| `422` | Validation error, or a setting the account's plan does not allow | | `429` | Rate limit exceeded | Authentication, pagination and not-found errors return an `errors` array of objects with `code` and `message`: diff --git a/api-reference/compliance/get-compliance-report.mdx b/api-reference/compliance/get-compliance-report.mdx new file mode 100644 index 0000000..3cdf05e --- /dev/null +++ b/api-reference/compliance/get-compliance-report.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /compliance +--- diff --git a/api-reference/consent-banners/create-consent-banner-from-template.mdx b/api-reference/consent-banners/create-consent-banner-from-template.mdx new file mode 100644 index 0000000..3e0ef73 --- /dev/null +++ b/api-reference/consent-banners/create-consent-banner-from-template.mdx @@ -0,0 +1,3 @@ +--- +openapi: post /consent-banners/from-template +--- diff --git a/api-reference/consent-banners/create-consent-banner.mdx b/api-reference/consent-banners/create-consent-banner.mdx new file mode 100644 index 0000000..7d97a12 --- /dev/null +++ b/api-reference/consent-banners/create-consent-banner.mdx @@ -0,0 +1,3 @@ +--- +openapi: post /consent-banners +--- diff --git a/api-reference/consent-banners/delete-consent-banner.mdx b/api-reference/consent-banners/delete-consent-banner.mdx new file mode 100644 index 0000000..7c5033c --- /dev/null +++ b/api-reference/consent-banners/delete-consent-banner.mdx @@ -0,0 +1,3 @@ +--- +openapi: delete /consent-banners/{id} +--- diff --git a/api-reference/consent-banners/duplicate-consent-banner.mdx b/api-reference/consent-banners/duplicate-consent-banner.mdx new file mode 100644 index 0000000..2122b8d --- /dev/null +++ b/api-reference/consent-banners/duplicate-consent-banner.mdx @@ -0,0 +1,3 @@ +--- +openapi: post /consent-banners/{id}/duplicate +--- diff --git a/api-reference/consent-banners/get-consent-banner.mdx b/api-reference/consent-banners/get-consent-banner.mdx new file mode 100644 index 0000000..1086736 --- /dev/null +++ b/api-reference/consent-banners/get-consent-banner.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /consent-banners/{id} +--- diff --git a/api-reference/consent-banners/list-banner-templates.mdx b/api-reference/consent-banners/list-banner-templates.mdx new file mode 100644 index 0000000..160f090 --- /dev/null +++ b/api-reference/consent-banners/list-banner-templates.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /banner-templates +--- diff --git a/api-reference/consent-banners/list-consent-banners.mdx b/api-reference/consent-banners/list-consent-banners.mdx new file mode 100644 index 0000000..e098ab2 --- /dev/null +++ b/api-reference/consent-banners/list-consent-banners.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /consent-banners +--- diff --git a/api-reference/consent-banners/update-consent-banner.mdx b/api-reference/consent-banners/update-consent-banner.mdx new file mode 100644 index 0000000..c86f46e --- /dev/null +++ b/api-reference/consent-banners/update-consent-banner.mdx @@ -0,0 +1,3 @@ +--- +openapi: put /consent-banners/{id} +--- diff --git a/api-reference/integrations/get-integration.mdx b/api-reference/integrations/get-integration.mdx new file mode 100644 index 0000000..010b383 --- /dev/null +++ b/api-reference/integrations/get-integration.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /integrations/{id} +--- diff --git a/api-reference/integrations/list-integrations.mdx b/api-reference/integrations/list-integrations.mdx new file mode 100644 index 0000000..8bda06e --- /dev/null +++ b/api-reference/integrations/list-integrations.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /integrations +--- diff --git a/api-reference/integrations/update-integration.mdx b/api-reference/integrations/update-integration.mdx new file mode 100644 index 0000000..97d231b --- /dev/null +++ b/api-reference/integrations/update-integration.mdx @@ -0,0 +1,3 @@ +--- +openapi: put /integrations/{id} +--- diff --git a/api-reference/scans/create-scan.mdx b/api-reference/scans/create-scan.mdx new file mode 100644 index 0000000..4c53a99 --- /dev/null +++ b/api-reference/scans/create-scan.mdx @@ -0,0 +1,3 @@ +--- +openapi: post /scans +--- diff --git a/api-reference/scans/get-scan.mdx b/api-reference/scans/get-scan.mdx new file mode 100644 index 0000000..3c53c07 --- /dev/null +++ b/api-reference/scans/get-scan.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /scans/{id} +--- diff --git a/api-reference/scans/list-scans.mdx b/api-reference/scans/list-scans.mdx new file mode 100644 index 0000000..d7c5c07 --- /dev/null +++ b/api-reference/scans/list-scans.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /scans +--- diff --git a/api-reference/services/create-service.mdx b/api-reference/services/create-service.mdx new file mode 100644 index 0000000..feda61d --- /dev/null +++ b/api-reference/services/create-service.mdx @@ -0,0 +1,3 @@ +--- +openapi: post /services +--- diff --git a/api-reference/services/delete-service.mdx b/api-reference/services/delete-service.mdx new file mode 100644 index 0000000..3d3da3c --- /dev/null +++ b/api-reference/services/delete-service.mdx @@ -0,0 +1,3 @@ +--- +openapi: delete /services/{id} +--- diff --git a/api-reference/services/get-service.mdx b/api-reference/services/get-service.mdx new file mode 100644 index 0000000..b3affef --- /dev/null +++ b/api-reference/services/get-service.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /services/{id} +--- diff --git a/api-reference/services/list-services.mdx b/api-reference/services/list-services.mdx new file mode 100644 index 0000000..77ae155 --- /dev/null +++ b/api-reference/services/list-services.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /services +--- diff --git a/api-reference/services/update-service.mdx b/api-reference/services/update-service.mdx new file mode 100644 index 0000000..61ddf80 --- /dev/null +++ b/api-reference/services/update-service.mdx @@ -0,0 +1,3 @@ +--- +openapi: put /services/{id} +--- diff --git a/docs.json b/docs.json index b2274c4..5a94bff 100644 --- a/docs.json +++ b/docs.json @@ -14,7 +14,12 @@ "library": "lucide" }, "contextual": { - "options": ["copy", "view", "chatgpt", "claude"] + "options": [ + "copy", + "view", + "chatgpt", + "claude" + ] }, "favicon": "/favicon.png", "navigation": { @@ -163,7 +168,53 @@ "group": "Account", "pages": [ "api-reference/account/list-accounts", - "api-reference/account/get-account" + "api-reference/account/get-account", + "api-reference/account/update-account" + ] + }, + { + "group": "Consent Banners", + "pages": [ + "api-reference/consent-banners/list-consent-banners", + "api-reference/consent-banners/create-consent-banner", + "api-reference/consent-banners/list-banner-templates", + "api-reference/consent-banners/create-consent-banner-from-template", + "api-reference/consent-banners/get-consent-banner", + "api-reference/consent-banners/update-consent-banner", + "api-reference/consent-banners/duplicate-consent-banner", + "api-reference/consent-banners/delete-consent-banner" + ] + }, + { + "group": "Services", + "pages": [ + "api-reference/services/list-services", + "api-reference/services/create-service", + "api-reference/services/get-service", + "api-reference/services/update-service", + "api-reference/services/delete-service" + ] + }, + { + "group": "Compliance", + "pages": [ + "api-reference/compliance/get-compliance-report" + ] + }, + { + "group": "Scans", + "pages": [ + "api-reference/scans/list-scans", + "api-reference/scans/create-scan", + "api-reference/scans/get-scan" + ] + }, + { + "group": "Integrations", + "pages": [ + "api-reference/integrations/list-integrations", + "api-reference/integrations/get-integration", + "api-reference/integrations/update-integration" ] }, { @@ -317,4 +368,4 @@ "appId": "r4jecam1" } } -} \ No newline at end of file +} diff --git a/openapi.yaml b/openapi.yaml index dfcabe6..216f59a 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -8,13 +8,19 @@ info: Resource endpoints expect the resource wrapped under its own key, e.g. `{"category": {"name": "Analytics"}}` rather than a bare object. - `PUT /password` wraps in `user`. `POST /auth` is the exception: it takes - `email` and `password` at the top level. + `PUT /password` wraps in `user`. Two endpoints take their body unwrapped: + `POST /auth`, which takes `email` and `password` at the top level, and + `POST /consent-banners/from-template`, which takes `jurisdiction_id` — + a selector for which template to build from, not an attribute of the + banner it creates. ## Response envelopes Listing endpoints — including `GET /groups` and `GET /accounts` — return - `{"pagination": {...}, "data": [...]}`. Most single-resource endpoints + `{"pagination": {...}, "data": [...]}`. `GET /banner-templates` is the + exception: it returns `{"data": [...]}` with no pagination, being a fixed + catalogue rather than a collection of the account's records. + Most single-resource endpoints return `{"data": {...}}`; a single group or account is returned unwrapped. Resource-deletion endpoints return `{"message": "..."}`; `DELETE /auth` returns an empty object. @@ -704,6 +710,48 @@ paths: "429": $ref: "#/components/responses/RateLimited" + patch: + summary: Update account + description: | + Update an account's settings — the same fields the dashboard's account + settings screens expose. + + An account token carries the account's own authority. A user token must + belong to an admin of the account, mirroring the web UI, and a + non-admin member gets a `403`. + + `account_type` cannot be changed here, as on the web form: mobile app + accounts are only created through the admin panel. + operationId: updateAccount + tags: + - Accounts + parameters: + - $ref: "#/components/parameters/id" + requestBody: + description: Account settings to update + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/AccountWriteRequest" + responses: + "200": + description: Updated account + content: + application/json: + schema: + $ref: "#/components/schemas/Account" + "401": + $ref: "#/components/responses/Unauthorized" + "403": + $ref: "#/components/responses/AccountAdminRequired" + "404": + $ref: "#/components/responses/NotFound" + "422": + $ref: "#/components/responses/ValidationError" + "429": + $ref: "#/components/responses/RateLimited" + # account user endpoints - index, GET, PUT & DELETE /account-users: get: @@ -974,138 +1022,882 @@ paths: "429": $ref: "#/components/responses/RateLimited" -components: - securitySchemes: - authorization: - type: http - scheme: bearer - description: API token obtained from the login endpoint or the dashboard - - parameters: - id: - name: id - in: path - description: ID of the resource - required: true - schema: - type: string - page: - name: page - in: query - description: | - Page number. Must be a positive integer; anything else returns a `400`. - Requesting a page past the end returns the last page. - required: false - schema: - type: integer - minimum: 1 - default: 1 - per_page: - name: per_page - in: query - description: | - Number of records to return per page. The effective range is 1–50; any - value outside it is accepted but falls back to the default of 20. - required: false - schema: - type: integer - default: 20 - onboarding_status: - name: onboarding_status - in: query - description: | - The status of the account, where: - - `account_created`: The account has been created. - - `vendors_setup`: Categories, services or TCF vendors have been set up. - - `code_installed`: The script has been installed on the user's website. - - `inactive`: Less than 10 visitors to the user's website in the last 30 days. - - `live`: The account is live and operational. - required: false - schema: - type: string - enum: [account_created, vendors_setup, code_installed, inactive, live] - visitor_stats: - name: visitor_stats - in: query + # site service endpoints - index, GET, POST, PUT & DELETE + /services: + get: + summary: List services description: | - Set to `false` to skip resolving analytics and return `visitor_stats` as - `null`. Useful when you only need the account list, as it avoids the - analytics lookup entirely. - required: false - schema: - type: boolean - default: true + List the services configured on the current account, in name order. + Requires an account-scoped API token. + operationId: listServices + tags: + - Services + parameters: + - $ref: "#/components/parameters/account_id" + - $ref: "#/components/parameters/per_page" + - $ref: "#/components/parameters/page" + responses: + "200": + description: List of services + content: + application/json: + schema: + type: object + properties: + pagination: + $ref: "#/components/schemas/BasePagination" + data: + type: array + items: + $ref: "#/components/schemas/SiteService" + "400": + $ref: "#/components/responses/BadRequest" + "401": + $ref: "#/components/responses/AccountTokenRequired" + "429": + $ref: "#/components/responses/RateLimited" - responses: - BadRequest: - description: Invalid pagination parameter - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - example: - errors: - - code: 400 - message: "Invalid page parameter" + post: + parameters: + - $ref: "#/components/parameters/account_id" + summary: Create service + description: | + Create a service under one of the account's categories. `category_id` + is required and must belong to this account. + operationId: createService + tags: + - Services + requestBody: + description: Service to create + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/SiteServiceCreateRequest" + responses: + "201": + description: Created service + content: + application/json: + schema: + $ref: "#/components/schemas/SiteServiceResponse" + "400": + $ref: "#/components/responses/AmbiguousAccount" + "401": + $ref: "#/components/responses/AccountTokenRequired" + "403": + $ref: "#/components/responses/AccountAdminRequired" + "404": + description: The referenced `category_id` does not belong to this account + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "422": + $ref: "#/components/responses/ValidationError" + "429": + $ref: "#/components/responses/RateLimited" - Unauthorized: - description: Missing, invalid or expired API token - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - example: - errors: - - code: 401 - message: "Invalid API Key" + /services/{id}: + get: + summary: Get service + description: Get service by ID + operationId: getService + tags: + - Services + parameters: + - $ref: "#/components/parameters/account_id" + - $ref: "#/components/parameters/id" + responses: + "200": + description: Service + content: + application/json: + schema: + $ref: "#/components/schemas/SiteServiceResponse" + "401": + $ref: "#/components/responses/AccountTokenRequired" + "404": + $ref: "#/components/responses/NotFound" + "429": + $ref: "#/components/responses/RateLimited" - AccountTokenRequired: + put: + summary: Update service description: | - Missing or invalid API token, or a token that does not resolve to an - account. Account-scoped endpoints accept an account token, or a user - token whose user belongs to an account; partner tokens are rejected. - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - example: - errors: - - code: 401 - message: "API key must belong to an account" - - PartnerTokenRequired: - description: Missing or invalid API token, or a token that does not belong to a partner - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - example: - errors: - - code: 401 - message: "API key must belong to a partner" + Update a service. Passing `category_id` moves the service to that + category; omitting it leaves the service where it is. + operationId: updateService + tags: + - Services + parameters: + - $ref: "#/components/parameters/account_id" + - $ref: "#/components/parameters/id" + requestBody: + description: Service to update + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/SiteServiceUpdateRequest" + responses: + "200": + description: Updated service + content: + application/json: + schema: + $ref: "#/components/schemas/SiteServiceResponse" + "400": + $ref: "#/components/responses/AmbiguousAccount" + "401": + $ref: "#/components/responses/AccountTokenRequired" + "403": + $ref: "#/components/responses/AccountAdminRequired" + "404": + $ref: "#/components/responses/NotFound" + "422": + $ref: "#/components/responses/ValidationError" + "429": + $ref: "#/components/responses/RateLimited" - NotFound: - description: The record does not exist, or does not belong to the caller - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - example: - errors: - - code: 404 - message: "Record not found" + delete: + summary: Delete service + description: Delete a service. It is soft-deleted and stops appearing in listings. + operationId: deleteService + tags: + - Services + parameters: + - $ref: "#/components/parameters/account_id" + - $ref: "#/components/parameters/id" + responses: + "200": + description: Service deleted + content: + application/json: + schema: + $ref: "#/components/schemas/DeletedMessage" + example: + message: "Service was deleted successfully" + "400": + $ref: "#/components/responses/AmbiguousAccount" + "401": + $ref: "#/components/responses/AccountTokenRequired" + "403": + $ref: "#/components/responses/AccountAdminRequired" + "404": + $ref: "#/components/responses/NotFound" + "429": + $ref: "#/components/responses/RateLimited" - RateLimited: + # consent banner endpoints - index, GET, POST, PUT, DELETE & duplicate + /consent-banners: + get: + summary: List consent banners description: | - Too many requests. The general limit is 300 requests per 5 minutes per - API token; the auth endpoints are throttled more tightly per IP. - headers: - Retry-After: - description: Seconds until the current rate-limit window resets - schema: - type: integer - content: - application/json: + List the account's consent banners, newest first. Requires an + account-scoped API token. + operationId: listConsentBanners + tags: + - Consent Banners + parameters: + - $ref: "#/components/parameters/account_id" + - $ref: "#/components/parameters/per_page" + - $ref: "#/components/parameters/page" + responses: + "200": + description: List of consent banners + content: + application/json: + schema: + type: object + properties: + pagination: + $ref: "#/components/schemas/BasePagination" + data: + type: array + items: + $ref: "#/components/schemas/ConsentBanner" + "400": + $ref: "#/components/responses/BadRequest" + "401": + $ref: "#/components/responses/AccountTokenRequired" + "429": + $ref: "#/components/responses/RateLimited" + + post: + parameters: + - $ref: "#/components/parameters/account_id" + summary: Create consent banner + description: | + Create a consent banner. It inherits the design of the account's most + recently updated banner, the same way the dashboard's new-banner flow + does, and is always created disabled — enable it once its content and + targeting are set. A name is generated if you don't supply one. + + Translations are not accepted here; add them by updating the banner + afterwards, which is where the account's language entitlement is + enforced. + operationId: createConsentBanner + tags: + - Consent Banners + requestBody: + description: Consent banner to create + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/ConsentBannerCreateRequest" + responses: + "201": + description: Created consent banner + content: + application/json: + schema: + $ref: "#/components/schemas/ConsentBannerResponse" + "400": + $ref: "#/components/responses/AmbiguousAccount" + "401": + $ref: "#/components/responses/AccountTokenRequired" + "403": + $ref: "#/components/responses/AccountAdminRequired" + "422": + $ref: "#/components/responses/ValidationError" + "429": + $ref: "#/components/responses/RateLimited" + + /consent-banners/{id}: + get: + summary: Get consent banner + description: Get consent banner by ID + operationId: getConsentBanner + tags: + - Consent Banners + parameters: + - $ref: "#/components/parameters/account_id" + - $ref: "#/components/parameters/id" + responses: + "200": + description: Consent banner + content: + application/json: + schema: + $ref: "#/components/schemas/ConsentBannerResponse" + "401": + $ref: "#/components/responses/AccountTokenRequired" + "404": + $ref: "#/components/responses/NotFound" + "429": + $ref: "#/components/responses/RateLimited" + + put: + summary: Update consent banner + description: | + Update a consent banner's content, appearance or targeting. Set + `enabled` to `true` to make it live. + operationId: updateConsentBanner + tags: + - Consent Banners + parameters: + - $ref: "#/components/parameters/account_id" + - $ref: "#/components/parameters/id" + requestBody: + description: Consent banner to update + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/ConsentBannerUpdateRequest" + responses: + "200": + description: Updated consent banner + content: + application/json: + schema: + $ref: "#/components/schemas/ConsentBannerResponse" + "400": + $ref: "#/components/responses/AmbiguousAccount" + "401": + $ref: "#/components/responses/AccountTokenRequired" + "403": + $ref: "#/components/responses/AccountAdminRequired" + "404": + $ref: "#/components/responses/NotFound" + "422": + $ref: "#/components/responses/ValidationError" + "429": + $ref: "#/components/responses/RateLimited" + + delete: + summary: Delete consent banner + description: Delete a consent banner. It is soft-deleted and stops appearing in listings. + operationId: deleteConsentBanner + tags: + - Consent Banners + parameters: + - $ref: "#/components/parameters/account_id" + - $ref: "#/components/parameters/id" + responses: + "200": + description: Consent banner deleted + content: + application/json: + schema: + $ref: "#/components/schemas/DeletedMessage" + example: + message: "Consent banner was deleted successfully" + "400": + $ref: "#/components/responses/AmbiguousAccount" + "401": + $ref: "#/components/responses/AccountTokenRequired" + "403": + $ref: "#/components/responses/AccountAdminRequired" + "404": + $ref: "#/components/responses/NotFound" + "429": + $ref: "#/components/responses/RateLimited" + + /consent-banners/{id}/duplicate: + post: + summary: Duplicate consent banner + description: | + Copy a consent banner, including its design and targeting. The copy is + named after the original and is always created disabled. + operationId: duplicateConsentBanner + tags: + - Consent Banners + parameters: + - $ref: "#/components/parameters/account_id" + - $ref: "#/components/parameters/id" + responses: + "201": + description: The duplicated consent banner + content: + application/json: + schema: + $ref: "#/components/schemas/ConsentBannerResponse" + "400": + $ref: "#/components/responses/AmbiguousAccount" + "401": + $ref: "#/components/responses/AccountTokenRequired" + "403": + $ref: "#/components/responses/AccountAdminRequired" + "404": + $ref: "#/components/responses/NotFound" + "422": + $ref: "#/components/responses/ValidationError" + "429": + $ref: "#/components/responses/RateLimited" + + # integration endpoints - index, GET & PUT + /integrations: + get: + summary: List integrations + description: | + List every integration available to the account, active and inactive. + Integrations the account has never opened are created inactive on first + listing, so the collection is always the full set you can enable. + + Only integrations the account's platform can run are included — a mobile + app account does not see the website-only ones. + operationId: listIntegrations + tags: + - Integrations + parameters: + - $ref: "#/components/parameters/account_id" + - $ref: "#/components/parameters/per_page" + - $ref: "#/components/parameters/page" + responses: + "200": + description: List of integrations + content: + application/json: + schema: + type: object + properties: + pagination: + $ref: "#/components/schemas/BasePagination" + data: + type: array + items: + $ref: "#/components/schemas/Integration" + "400": + $ref: "#/components/responses/BadRequest" + "401": + $ref: "#/components/responses/AccountTokenRequired" + "429": + $ref: "#/components/responses/RateLimited" + + /integrations/{id}: + get: + summary: Get integration + description: Get integration by ID + operationId: getIntegration + tags: + - Integrations + parameters: + - $ref: "#/components/parameters/account_id" + - $ref: "#/components/parameters/id" + responses: + "200": + description: Integration + content: + application/json: + schema: + $ref: "#/components/schemas/IntegrationResponse" + "401": + $ref: "#/components/responses/AccountTokenRequired" + "404": + $ref: "#/components/responses/NotFound" + "429": + $ref: "#/components/responses/RateLimited" + + put: + summary: Update integration + description: | + Enable, disable or configure an integration. Every integration accepts + `status`; the rest of the accepted keys depend on which integration it + is, and anything else is ignored. + + - `SiteScanIntegration` also accepts `scan_frequency` (`weekly` or + `monthly`; weekly is not available on free accounts) and + `auto_add_vendors`. + - `GoogleConsentModeIntegration` also accepts `url_passthrough` and + `ads_data_redaction`. + + Enabling an integration may add its associated vendors to the account. + + Scheduled scanning is a paid feature: on a free plan, setting + `SiteScanIntegration` to `active` or turning on `auto_add_vendors` is + refused with a `422`. Turning it off always works, so an account that + downgrades can still disable it. + operationId: updateIntegration + tags: + - Integrations + parameters: + - $ref: "#/components/parameters/account_id" + - $ref: "#/components/parameters/id" + requestBody: + description: Integration settings to update + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/IntegrationWriteRequest" + responses: + "200": + description: Updated integration + content: + application/json: + schema: + $ref: "#/components/schemas/IntegrationResponse" + "400": + $ref: "#/components/responses/AmbiguousAccount" + "401": + $ref: "#/components/responses/AccountTokenRequired" + "403": + $ref: "#/components/responses/AccountAdminRequired" + "404": + $ref: "#/components/responses/NotFound" + "422": + $ref: "#/components/responses/ValidationError" + "429": + $ref: "#/components/responses/RateLimited" + + # scan endpoints - index, GET & POST + /scans: + get: + summary: List scans + description: | + List the account's website scans, newest first. Requires an + account-scoped API token. + operationId: listScans + tags: + - Scans + parameters: + - $ref: "#/components/parameters/account_id" + - $ref: "#/components/parameters/per_page" + - $ref: "#/components/parameters/page" + responses: + "200": + description: List of scans + content: + application/json: + schema: + type: object + properties: + pagination: + $ref: "#/components/schemas/BasePagination" + data: + type: array + items: + $ref: "#/components/schemas/Scan" + "400": + $ref: "#/components/responses/BadRequest" + "401": + $ref: "#/components/responses/AccountTokenRequired" + "429": + $ref: "#/components/responses/RateLimited" + + post: + parameters: + - $ref: "#/components/parameters/account_id" + summary: Start a scan + description: | + Queue a scan of the account's website. Only one scan can run at a time + per account, and accounts have a daily scan limit — both come back as a + `422`, as does a mobile app account, which has no website to crawl. + + Scanning is asynchronous: the scan is created `queued`, and the report + appears on the scan once its status reaches `completed`. + operationId: createScan + tags: + - Scans + requestBody: + description: Scan to queue + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/ScanWriteRequest" + responses: + "201": + description: Queued scan + content: + application/json: + schema: + $ref: "#/components/schemas/ScanResponse" + "400": + $ref: "#/components/responses/AmbiguousAccount" + "401": + $ref: "#/components/responses/AccountTokenRequired" + "403": + $ref: "#/components/responses/AccountAdminRequired" + "422": + $ref: "#/components/responses/ValidationError" + "429": + $ref: "#/components/responses/RateLimited" + + /scans/{id}: + get: + summary: Get scan + description: | + Get a scan by ID. `report` is only present once the scan has completed; + poll `status` until then. + operationId: getScan + tags: + - Scans + parameters: + - $ref: "#/components/parameters/account_id" + - $ref: "#/components/parameters/id" + responses: + "200": + description: Scan + content: + application/json: + schema: + $ref: "#/components/schemas/ScanResponse" + "401": + $ref: "#/components/responses/AccountTokenRequired" + "404": + $ref: "#/components/responses/NotFound" + "429": + $ref: "#/components/responses/RateLimited" + + # compliance report + /compliance: + get: + parameters: + - $ref: "#/components/parameters/account_id" + summary: Get the regional compliance report + description: | + Evaluate the account's banners against every regulation CookieChimp + tracks, and report what is outstanding — the same evaluation the + dashboard's compliance screen renders. + + Regulations the account has chosen to ignore are reported separately in + `ignored_jurisdictions` rather than dropped, and are excluded from the + top-level `compliant` verdict. + + Requires a plan that includes the compliance report. + operationId: getComplianceReport + tags: + - Compliance + responses: + "200": + description: The compliance report + content: + application/json: + schema: + $ref: "#/components/schemas/ComplianceReportResponse" + "401": + $ref: "#/components/responses/AccountTokenRequired" + "403": + description: The account's plan does not include the compliance report + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + example: + errors: + - code: 403 + message: "The compliance report is not available on this plan" + "429": + $ref: "#/components/responses/RateLimited" + + # banner templates + /banner-templates: + get: + parameters: + - $ref: "#/components/parameters/account_id" + summary: List banner templates + description: | + The jurisdiction templates a banner can be created from, grouped by + region. Use a template's `id` as the `jurisdiction_id` for + `POST /consent-banners/from-template`. + operationId: listBannerTemplates + tags: + - Consent Banners + responses: + "200": + description: Templates grouped by region + content: + application/json: + schema: + $ref: "#/components/schemas/BannerTemplatesResponse" + "401": + $ref: "#/components/responses/AccountTokenRequired" + "429": + $ref: "#/components/responses/RateLimited" + + /consent-banners/from-template: + post: + parameters: + - $ref: "#/components/parameters/account_id" + summary: Create consent banner from a template + description: | + Create a banner preconfigured for a jurisdiction — the consent mode, + targeting and copy that regulation requires. As with every banner this + API creates, it starts disabled. + + `jurisdiction_id` comes from `GET /banner-templates`. + operationId: createConsentBannerFromTemplate + tags: + - Consent Banners + requestBody: + description: The jurisdiction to build the banner for + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/BannerFromTemplateRequest" + responses: + "201": + description: The created consent banner + content: + application/json: + schema: + $ref: "#/components/schemas/ConsentBannerResponse" + "400": + $ref: "#/components/responses/AmbiguousAccount" + "401": + $ref: "#/components/responses/AccountTokenRequired" + "403": + $ref: "#/components/responses/AccountAdminRequired" + "404": + description: No template exists for that `jurisdiction_id` + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "422": + $ref: "#/components/responses/ValidationError" + "429": + $ref: "#/components/responses/RateLimited" + +components: + securitySchemes: + authorization: + type: http + scheme: bearer + description: API token obtained from the login endpoint or the dashboard + + parameters: + account_id: + name: account_id + in: query + description: | + Which account to act on, as the account's API id. Only meaningful for a + user API key, which may reach several accounts — an account key already + identifies exactly one and ignores this. + + Accepted by the endpoints that declare it; others act on the account the + key resolves to on its own. + + Optional for reads, where it narrows the request to that account. + Required for writes that declare it when the key can reach more than one + account: without it the request would act on an account it picked rather + than one you named, so it is refused with a `400`. + required: false + schema: + type: string + id: + name: id + in: path + description: ID of the resource + required: true + schema: + type: string + page: + name: page + in: query + description: | + Page number. Must be a positive integer; anything else returns a `400`. + Requesting a page past the end returns the last page. + required: false + schema: + type: integer + minimum: 1 + default: 1 + per_page: + name: per_page + in: query + description: | + Number of records to return per page. The effective range is 1–50; any + value outside it is accepted but falls back to the default of 20. + required: false + schema: + type: integer + default: 20 + onboarding_status: + name: onboarding_status + in: query + description: | + The status of the account, where: + - `account_created`: The account has been created. + - `vendors_setup`: Categories, services or TCF vendors have been set up. + - `code_installed`: The script has been installed on the user's website. + - `inactive`: Less than 10 visitors to the user's website in the last 30 days. + - `live`: The account is live and operational. + + Accounts whose status could not be determined are excluded from a + filtered listing, since their status is unknown rather than any + particular value. + required: false + schema: + type: string + enum: [account_created, vendors_setup, code_installed, inactive, live] + visitor_stats: + name: visitor_stats + in: query + description: | + Set to `false` to skip resolving analytics and return `visitor_stats` as + `null`. Useful when you only need the account list, as it avoids the + analytics lookup entirely. + required: false + schema: + type: boolean + default: true + + responses: + BadRequest: + description: Invalid pagination parameter + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + example: + errors: + - code: 400 + message: "Invalid page parameter" + + Unauthorized: + description: Missing, invalid or expired API token + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + example: + errors: + - code: 401 + message: "Invalid API Key" + + AccountTokenRequired: + description: | + Missing or invalid API token, or a token that does not resolve to an + account. Account-scoped endpoints accept an account token, or a user + token whose user belongs to an account; partner tokens are rejected. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + example: + errors: + - code: 401 + message: "API key must belong to an account" + + PartnerTokenRequired: + description: Missing or invalid API token, or a token that does not belong to a partner + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + example: + errors: + - code: 401 + message: "API key must belong to a partner" + + AmbiguousAccount: + description: | + A user API key that can reach more than one account attempted a write + without saying which account it meant. Pass `account_id` (the account's + API id) as a query parameter. Account keys are never ambiguous. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + example: + errors: + - code: 400 + message: "Specify account_id when the API key can access more than one account" + + AccountAdminRequired: + description: | + The token resolved to a user who is not an admin of this account. + Configuration changes are admin-only, mirroring the dashboard — a group + admin qualifies even without a direct membership, since a group role + supersedes one. Account tokens carry the account's own authority and + never see this; reads are unaffected. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + example: + errors: + - code: 403 + message: "User must be an admin of this account" + + NotFound: + description: The record does not exist, or does not belong to the caller + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + example: + errors: + - code: 404 + message: "Record not found" + + RateLimited: + description: | + Too many requests. The general limit is 300 requests per 5 minutes per + API token; the auth endpoints are throttled more tightly per IP. + headers: + Retry-After: + description: Seconds until the current rate-limit window resets + schema: + type: integer + content: + application/json: schema: $ref: "#/components/schemas/Error" example: @@ -1113,1050 +1905,2053 @@ components: - code: 429 message: "Too many requests. Please retry later." - ValidationError: - description: The record could not be saved - content: - application/json: - schema: - $ref: "#/components/schemas/ValidationError" + ValidationError: + description: The record could not be saved + content: + application/json: + schema: + $ref: "#/components/schemas/ValidationError" + + ModelValidationError: + description: The record could not be saved + content: + application/json: + schema: + $ref: "#/components/schemas/ModelValidationError" + + schemas: + Error: + type: object + description: | + The standard error envelope, used for authentication, authorization, + pagination and not-found errors. + properties: + errors: + type: array + items: + type: object + properties: + code: + type: integer + message: + type: string + example: + errors: + - code: 401 + message: "Invalid API Key" + + ValidationError: + type: object + description: | + Returned when a create or update fails validation. Each entry carries a + single human-readable `error` string. + properties: + errors: + type: array + items: + type: object + properties: + error: + type: string + example: + errors: + - error: "Name can't be blank" + + ModelValidationError: + type: object + description: | + Returned by group create/update and account-user update, which render + the model's errors directly: an object keyed by attribute name. Other + validation failures — including removing an account owner — use + `ValidationError`. + additionalProperties: + type: array + items: + type: string + example: + name: ["can't be blank"] + external_id: ["has already been taken"] + + MessageError: + type: object + description: Returned by the auth and password endpoints, which use a single `error` string. + properties: + error: + type: string + example: + error: "Invalid Email or password." + + DeletedMessage: + type: object + properties: + message: + type: string + example: + message: "Record was deleted successfully" + + BasePagination: + type: object + properties: + prev_url: + type: string + description: Path to get the previous page + next_url: + type: string + description: Path to get the next page + count: + type: integer + description: Total number of records across all pages + page: + type: integer + description: Current page + prev: + type: [integer, "null"] + description: Previous page number, or null on the first page + next: + type: [integer, "null"] + description: Next page number, or null on the last page + + AccountPagination: + allOf: + - $ref: "#/components/schemas/BasePagination" + example: + prev_url: "/api/v1/accounts?page=" + next_url: "/api/v1/accounts?page=2" + count: 68 + page: 1 + prev: null + next: 2 + + AccountInvitationPagination: + allOf: + - $ref: "#/components/schemas/BasePagination" + example: + prev_url: "/api/v1/account-invitations?page=" + next_url: "/api/v1/account-invitations?page=2" + count: 68 + page: 1 + prev: null + next: 2 + + AccountUserPagination: + allOf: + - $ref: "#/components/schemas/BasePagination" + example: + prev_url: "/api/v1/account-users?page=" + next_url: "/api/v1/account-users?page=2" + count: 68 + page: 1 + prev: null + next: 2 + + AIActionPagination: + allOf: + - $ref: "#/components/schemas/BasePagination" + example: + prev_url: "/api/v1/ai-actions?page=" + next_url: "/api/v1/ai-actions?page=2" + count: 68 + page: 1 + prev: null + next: 2 + + CategoryPagination: + allOf: + - $ref: "#/components/schemas/BasePagination" + example: + prev_url: "/api/v1/categories?page=" + next_url: "/api/v1/categories?page=2" + count: 68 + page: 1 + prev: null + next: 2 + + ConsentPagination: + allOf: + - $ref: "#/components/schemas/BasePagination" + example: + prev_url: "/api/v1/consents?page=" + next_url: "/api/v1/consents?page=2" + count: 68 + page: 1 + prev: null + next: 2 + + GroupPagination: + allOf: + - $ref: "#/components/schemas/BasePagination" + example: + prev_url: "/api/v1/groups?page=" + next_url: "/api/v1/groups?page=2" + count: 68 + page: 1 + prev: null + next: 2 + + StorageItemPagination: + allOf: + - $ref: "#/components/schemas/BasePagination" + example: + prev_url: "/api/v1/storage-items?page=" + next_url: "/api/v1/storage-items?page=2" + count: 68 + page: 1 + prev: null + next: 2 + + LoginRequest: + type: object + properties: + email: + type: string + format: email + description: The user's email address + password: + type: string + format: password + description: The user's password + required: + - email + - password + example: + email: "user@example.com" + password: "correct-horse-battery-staple" + + LoginResponse: + type: object + properties: + token: + type: string + description: API token to send as a bearer token on subsequent requests + example: + token: "0mHFuvY2mCVfMBXbtNCPXA" + + PasswordUpdateRequest: + type: object + properties: + user: + type: object + properties: + current_password: + type: string + format: password + password: + type: string + format: password + password_confirmation: + type: string + format: password + required: + - current_password + - password + - password_confirmation + required: + - user + example: + user: + current_password: "correct-horse-battery-staple" + password: "a-longer-new-password" + password_confirmation: "a-longer-new-password" - ModelValidationError: - description: The record could not be saved - content: - application/json: - schema: - $ref: "#/components/schemas/ModelValidationError" + CategoryWrite: + type: object + properties: + name: + type: string + description: Name of the category that is displayed to users + slug: + type: string + description: | + URL-friendly identifier for the category, used by the consent banner + and the JavaScript SDK. Some slugs are reserved by CookieChimp. + description: + type: string + description: Description of the category that is displayed to users + required: + type: boolean + description: Whether the category is required for the website to function + required: + - name - schemas: - Error: + CategoryWriteRequest: type: object - description: | - The standard error envelope, used for authentication, authorization, - pagination and not-found errors. properties: - errors: - type: array - items: - type: object - properties: - code: - type: integer - message: - type: string + category: + $ref: "#/components/schemas/CategoryWrite" + required: + - category example: - errors: - - code: 401 - message: "Invalid API Key" + category: + name: "Analytics" + slug: "analytics" + description: "Cookies that help us understand how the site is used" + required: false - ValidationError: + Category: type: object - description: | - Returned when a create or update fails validation. Each entry carries a - single human-readable `error` string. properties: - errors: - type: array - items: - type: object - properties: - error: - type: string + id: + type: string + readOnly: true + description: Unique identifier for the category + name: + type: string + description: Name of the category that is displayed to users + description: + type: string + description: Description of the category that is displayed to users + slug: + type: string + description: URL-friendly identifier for the category + required: + type: boolean + description: Whether the category is required for the website to function + default_language: + type: string + description: Language code the name and description are written in + created_at: + type: string + format: date-time + readOnly: true + updated_at: + type: string + format: date-time + readOnly: true example: - errors: - - error: "Name can't be blank" + id: "essential" + name: "Essential" + description: "Cookies essential for website functionality" + slug: "essential" + required: true + default_language: "en" + created_at: "2023-01-01T00:00:00Z" + updated_at: "2023-01-02T00:00:00Z" - ModelValidationError: + CategoryResponse: type: object - description: | - Returned by group create/update and account-user update, which render - the model's errors directly: an object keyed by attribute name. Other - validation failures — including removing an account owner — use - `ValidationError`. - additionalProperties: - type: array - items: + properties: + data: + $ref: "#/components/schemas/Category" + + StorageItemWrite: + type: object + properties: + name: + type: string + description: Name of the storage item + storage_type: + type: string + enum: [cookie, local_storage, session_storage] + description: The kind of browser storage this item uses + description: + type: string + description: Description of the storage item that is displayed to users + company_name: + type: string + description: Name of the company that owns the storage item + domain: + type: string + description: Domain of the storage item + duration: + type: string + description: How long the storage item persists + partitioned: + type: boolean + description: Whether the storage item is partitioned (CHIPS) + path: + type: string + description: Path of the storage item + samesite: + type: string + enum: [lax, strict] + description: SameSite attribute of the storage item + category_id: + type: string + description: ID of the category to file this item under + site_service_id: + type: string + description: | + ID of the service this item belongs to. When set, the item inherits + the service's category. + required: + - name + + StorageItemWriteRequest: + type: object + properties: + storage_item: + $ref: "#/components/schemas/StorageItemWrite" + required: + - storage_item + example: + storage_item: + name: "_ga" + storage_type: "cookie" + description: "Used to distinguish users." + company_name: "Google" + domain: ".example.com" + duration: "2 years" + partitioned: false + path: "/" + samesite: "lax" + category_id: "cat1" + + StorageItem: + type: object + properties: + id: + type: string + readOnly: true + description: Unique identifier for the storage item + name: + type: string + storage_type: + type: string + enum: [cookie, local_storage, session_storage] + category: + type: object + description: | + The category this item is filed under. Falls back to the category of + the item's service when the item has no category of its own. + properties: + id: + type: [string, "null"] + name: + type: [string, "null"] + service: + type: object + description: The service this item belongs to, if any + properties: + id: + type: [string, "null"] + name: + type: [string, "null"] + description: + type: string + company_name: + type: string + duration: + type: string + domain: + type: string + partitioned: + type: boolean + path: type: string - example: - name: ["can't be blank"] - external_id: ["has already been taken"] - - MessageError: - type: object - description: Returned by the auth and password endpoints, which use a single `error` string. - properties: - error: + samesite: + type: [string, "null"] + enum: [lax, strict, null] + wildcard_id: + type: [string, "null"] + readOnly: true + description: ID of the wildcard item this one was matched against, if any + default_language: + type: string + description: Language code the name and description are written in + last_seen_at: + type: [string, "null"] + format: date-time + readOnly: true + description: When this item was last observed on the website + created_by: + type: string + enum: [user, scan] + readOnly: true + description: Whether the item was added by a user or found by a scan + created_at: + type: string + format: date-time + readOnly: true + updated_at: type: string + format: date-time + readOnly: true example: - error: "Invalid Email or password." + id: "sto123" + name: "_ga" + storage_type: "cookie" + category: + id: "cat1" + name: "Analytics" + service: + id: "svc1" + name: "Google Analytics" + description: "Used to distinguish users." + company_name: "Google" + duration: "2 years" + domain: ".example.com" + partitioned: false + path: "/" + samesite: "lax" + wildcard_id: null + default_language: "en" + last_seen_at: "2023-03-23T12:34:56Z" + created_by: "scan" + created_at: "2023-03-01T09:00:00Z" + updated_at: "2023-03-23T12:34:56Z" - DeletedMessage: + StorageItemResponse: type: object properties: - message: - type: string - example: - message: "Record was deleted successfully" + data: + $ref: "#/components/schemas/StorageItem" - BasePagination: + GroupCreate: type: object properties: - prev_url: + name: type: string - description: Path to get the previous page - next_url: + description: Name of the group + external_id: type: string - description: Path to get the next page - count: + description: External ID set by the partner + max_pageviews_per_month: type: integer - description: Total number of records across all pages - page: + description: Maximum number of pageviews per month + max_unique_visitors_per_month: type: integer - description: Current page - prev: - type: [integer, "null"] - description: Previous page number, or null on the first page - next: - type: [integer, "null"] - description: Next page number, or null on the last page - - AccountPagination: - allOf: - - $ref: "#/components/schemas/BasePagination" - example: - prev_url: "/api/v1/accounts?page=" - next_url: "/api/v1/accounts?page=2" - count: 68 - page: 1 - prev: null - next: 2 - - AccountInvitationPagination: - allOf: - - $ref: "#/components/schemas/BasePagination" - example: - prev_url: "/api/v1/account-invitations?page=" - next_url: "/api/v1/account-invitations?page=2" - count: 68 - page: 1 - prev: null - next: 2 - - AccountUserPagination: - allOf: - - $ref: "#/components/schemas/BasePagination" - example: - prev_url: "/api/v1/account-users?page=" - next_url: "/api/v1/account-users?page=2" - count: 68 - page: 1 - prev: null - next: 2 - - AIActionPagination: - allOf: - - $ref: "#/components/schemas/BasePagination" - example: - prev_url: "/api/v1/ai-actions?page=" - next_url: "/api/v1/ai-actions?page=2" - count: 68 - page: 1 - prev: null - next: 2 - - CategoryPagination: - allOf: - - $ref: "#/components/schemas/BasePagination" - example: - prev_url: "/api/v1/categories?page=" - next_url: "/api/v1/categories?page=2" - count: 68 - page: 1 - prev: null - next: 2 - - ConsentPagination: - allOf: - - $ref: "#/components/schemas/BasePagination" - example: - prev_url: "/api/v1/consents?page=" - next_url: "/api/v1/consents?page=2" - count: 68 - page: 1 - prev: null - next: 2 - - GroupPagination: - allOf: - - $ref: "#/components/schemas/BasePagination" - example: - prev_url: "/api/v1/groups?page=" - next_url: "/api/v1/groups?page=2" - count: 68 - page: 1 - prev: null - next: 2 - - StorageItemPagination: - allOf: - - $ref: "#/components/schemas/BasePagination" - example: - prev_url: "/api/v1/storage-items?page=" - next_url: "/api/v1/storage-items?page=2" - count: 68 - page: 1 - prev: null - next: 2 - - LoginRequest: - type: object - properties: - email: - type: string - format: email - description: The user's email address - password: - type: string - format: password - description: The user's password + description: Maximum number of unique visitors per month required: - - email - - password - example: - email: "user@example.com" - password: "correct-horse-battery-staple" - - LoginResponse: - type: object - properties: - token: - type: string - description: API token to send as a bearer token on subsequent requests - example: - token: "0mHFuvY2mCVfMBXbtNCPXA" + - name + - external_id - PasswordUpdateRequest: + GroupCreateRequest: type: object properties: - user: - type: object - properties: - current_password: - type: string - format: password - password: - type: string - format: password - password_confirmation: - type: string - format: password - required: - - current_password - - password - - password_confirmation + group: + $ref: "#/components/schemas/GroupCreate" required: - - user + - group example: - user: - current_password: "correct-horse-battery-staple" - password: "a-longer-new-password" - password_confirmation: "a-longer-new-password" + group: + name: "Example Ltd" + external_id: "ABC_123" + max_pageviews_per_month: 500000 + max_unique_visitors_per_month: 100000 - CategoryWrite: + GroupUpdate: type: object properties: name: type: string - description: Name of the category that is displayed to users - slug: - type: string - description: | - URL-friendly identifier for the category, used by the consent banner - and the JavaScript SDK. Some slugs are reserved by CookieChimp. - description: + description: Name of the group + external_id: type: string - description: Description of the category that is displayed to users - required: - type: boolean - description: Whether the category is required for the website to function + description: External ID set by the partner required: - name + - external_id - CategoryWriteRequest: + GroupUpdateRequest: type: object properties: - category: - $ref: "#/components/schemas/CategoryWrite" + group: + $ref: "#/components/schemas/GroupUpdate" required: - - category + - group example: - category: - name: "Analytics" - slug: "analytics" - description: "Cookies that help us understand how the site is used" - required: false + group: + name: "Example Ltd" + external_id: "ABC_123" - Category: + Group: type: object properties: id: type: string readOnly: true - description: Unique identifier for the category + description: Unique identifier for the group + partner: + type: string + readOnly: true + description: Name of the partner the group belongs to name: type: string - description: Name of the category that is displayed to users - description: + description: Name of the group + external_id: type: string - description: Description of the category that is displayed to users - slug: + description: External ID set by the partner + max_pageviews_per_month: + type: [integer, "null"] + description: Maximum number of pageviews per month + max_unique_visitors_per_month: + type: [integer, "null"] + description: Maximum number of unique visitors per month + example: + id: "abc123" + partner: "partner_one" + name: "Example Ltd" + external_id: "ABC_123" + max_pageviews_per_month: 500000 + max_unique_visitors_per_month: 100000 + + Consent: + type: object + description: | + A consent record, written when a visitor interacts with the consent + banner. + properties: + id: type: string - description: URL-friendly identifier for the category - required: - type: boolean - description: Whether the category is required for the website to function - default_language: + description: Unique identifier for the consent + consent_banner_id: + type: [string, "null"] + description: ID of the consent banner the visitor saw + uuid: type: string - description: Language code the name and description are written in - created_at: + description: Anonymous visitor identifier generated by the widget + url: type: string - format: date-time - readOnly: true - updated_at: + description: URL the consent was given on + ip_address: + type: [string, "null"] + description: Partially masked IP address of the visitor + ip_country: + type: [string, "null"] + description: Two-letter country code resolved from the visitor's IP + ip_region: + type: [string, "null"] + description: Region resolved from the visitor's IP + visitor_user_id: + type: [string, "null"] + description: ID of the user set by your website + accept_type: + type: [string, "null"] + description: | + How the visitor responded. Typically `all`, `custom` or `necessary`. + accepted_categories: + type: [array, "null"] + items: + type: string + description: Slugs of the categories the visitor accepted + rejected_categories: + type: [array, "null"] + items: + type: string + description: Slugs of the categories the visitor rejected + accepted_services: + type: [object, "null"] + additionalProperties: + type: array + items: + type: string + description: Accepted services, keyed by category slug + rejected_services: + type: [object, "null"] + additionalProperties: + type: array + items: + type: string + description: Rejected services, keyed by category slug + accepted_cookies: + type: [array, "null"] + items: + type: object + description: Storage items the visitor accepted + rejected_cookies: + type: [array, "null"] + items: + type: object + description: Storage items the visitor rejected + device: + type: string + description: Device type derived from the user agent + user_agent: + type: string + description: User agent of the visitor's browser + gpc_preference: + type: [boolean, "null"] + description: Whether the visitor sent a Global Privacy Control signal + do_not_sell: + type: boolean + description: Whether the visitor opted out of the sale of their data + created_at: type: string format: date-time - readOnly: true + description: Date and time when the consent was recorded example: - id: "essential" - name: "Essential" - description: "Cookies essential for website functionality" - slug: "essential" - required: true - default_language: "en" - created_at: "2023-01-01T00:00:00Z" - updated_at: "2023-01-02T00:00:00Z" + id: "consent321" + consent_banner_id: "banner123" + uuid: "8f14e45f-ea8f-4f2e-b1b0-0f1d2c3b4a59" + url: "https://example.com/pricing" + ip_address: "203.0.113.XXX" + ip_country: "GB" + ip_region: "England" + visitor_user_id: "user456" + accept_type: "custom" + accepted_categories: ["essential", "analytics"] + rejected_categories: ["marketing"] + accepted_services: { "analytics": ["Google Analytics"] } + rejected_services: { "marketing": ["Meta Pixel"] } + accepted_cookies: [] + rejected_cookies: [] + device: "desktop" + user_agent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)" + gpc_preference: false + do_not_sell: false + created_at: "2023-01-20T12:46:30Z" - CategoryResponse: + ConsentResponse: type: object properties: data: - $ref: "#/components/schemas/Category" + $ref: "#/components/schemas/Consent" - StorageItemWrite: + AIAction: type: object + description: An action CookieChimp's AI took on a category or storage item. properties: - name: - type: string - description: Name of the storage item - storage_type: - type: string - enum: [cookie, local_storage, session_storage] - description: The kind of browser storage this item uses - description: - type: string - description: Description of the storage item that is displayed to users - company_name: - type: string - description: Name of the company that owns the storage item - domain: - type: string - description: Domain of the storage item - duration: + id: type: string - description: How long the storage item persists - partitioned: - type: boolean - description: Whether the storage item is partitioned (CHIPS) - path: + description: Unique identifier for the AI action + action_type: type: string - description: Path of the storage item - samesite: + enum: [autofill, auto_categorise, translate] + description: What the AI did + actionable_type: type: string - enum: [lax, strict] - description: SameSite attribute of the storage item - category_id: + enum: [Category, ClientStorageItem] + description: The type of record the action was taken on + actionable_id: type: string - description: ID of the category to file this item under - site_service_id: + description: ID of the record the action was taken on + suggested_params: + type: [object, "null"] + additionalProperties: true + description: The values the AI suggested + created_at: type: string + format: date-time + actionable: description: | - ID of the service this item belongs to. When set, the item inherits - the service's category. - required: - - name + The record the action was taken on — a category or a storage item, + in the same shape those endpoints return. Use `actionable_type` to + tell which one it is. + anyOf: + - $ref: "#/components/schemas/Category" + - $ref: "#/components/schemas/StorageItem" + example: + id: "act123" + action_type: "autofill" + actionable_type: "ClientStorageItem" + actionable_id: "sto123" + suggested_params: + description: "Used to distinguish users." + company_name: "Google" + duration: "2 years" + created_at: "2023-02-01T10:00:00Z" + actionable: + id: "sto123" + name: "_ga" + storage_type: "cookie" + + AIActionResponse: + type: object + properties: + data: + $ref: "#/components/schemas/AIAction" + + Roles: + type: object + description: | + Role flags for a membership. Currently `admin` is the only role, and it + defaults to `true`. + additionalProperties: + type: boolean + example: + admin: true + + AccountUserWrite: + type: object + properties: + roles: + $ref: "#/components/schemas/Roles" - StorageItemWriteRequest: + AccountUserWriteRequest: type: object properties: - storage_item: - $ref: "#/components/schemas/StorageItemWrite" + account_user: + $ref: "#/components/schemas/AccountUserWrite" required: - - storage_item + - account_user example: - storage_item: - name: "_ga" - storage_type: "cookie" - description: "Used to distinguish users." - company_name: "Google" - domain: ".example.com" - duration: "2 years" - partitioned: false - path: "/" - samesite: "lax" - category_id: "cat1" + account_user: + roles: + admin: false - StorageItem: + AccountUser: type: object properties: id: type: string - readOnly: true - description: Unique identifier for the storage item - name: + description: Unique identifier for the account user + roles: + $ref: "#/components/schemas/Roles" + created_at: type: string - storage_type: + format: date-time + updated_at: type: string - enum: [cookie, local_storage, session_storage] - category: + format: date-time + user: type: object - description: | - The category this item is filed under. Falls back to the category of - the item's service when the item has no category of its own. properties: id: - type: [string, "null"] + type: string name: - type: [string, "null"] - service: + type: string + email: + type: string + format: email + account: type: object - description: The service this item belongs to, if any properties: id: - type: [string, "null"] + type: string name: - type: [string, "null"] - description: - type: string - company_name: - type: string - duration: - type: string - domain: - type: string - partitioned: - type: boolean - path: - type: string - samesite: - type: [string, "null"] - enum: [lax, strict, null] - wildcard_id: - type: [string, "null"] - readOnly: true - description: ID of the wildcard item this one was matched against, if any - default_language: - type: string - description: Language code the name and description are written in - last_seen_at: - type: [string, "null"] - format: date-time - readOnly: true - description: When this item was last observed on the website - created_by: - type: string - enum: [user, scan] - readOnly: true - description: Whether the item was added by a user or found by a scan - created_at: - type: string - format: date-time - readOnly: true - updated_at: - type: string - format: date-time - readOnly: true + type: string example: - id: "sto123" - name: "_ga" - storage_type: "cookie" - category: - id: "cat1" - name: "Analytics" - service: - id: "svc1" - name: "Google Analytics" - description: "Used to distinguish users." - company_name: "Google" - duration: "2 years" - domain: ".example.com" - partitioned: false - path: "/" - samesite: "lax" - wildcard_id: null - default_language: "en" - last_seen_at: "2023-03-23T12:34:56Z" - created_by: "scan" - created_at: "2023-03-01T09:00:00Z" - updated_at: "2023-03-23T12:34:56Z" + id: "accuser123" + roles: + admin: true + created_at: "2023-01-05T10:15:30Z" + updated_at: "2023-01-15T11:20:45Z" + user: + id: "usr123" + name: "Ada Lovelace" + email: "admin@example.com" + account: + id: "acc123" + name: "example.com" - StorageItemResponse: + AccountUserResponse: type: object properties: data: - $ref: "#/components/schemas/StorageItem" + $ref: "#/components/schemas/AccountUser" - GroupCreate: + AccountInvitationCreate: type: object properties: name: type: string - description: Name of the group - external_id: + description: Name of the user the invitation is for + email: type: string - description: External ID set by the partner - max_pageviews_per_month: - type: integer - description: Maximum number of pageviews per month - max_unique_visitors_per_month: - type: integer - description: Maximum number of unique visitors per month + format: email + description: Email address of the user required: - name - - external_id + - email - GroupCreateRequest: + AccountInvitationCreateRequest: type: object properties: - group: - $ref: "#/components/schemas/GroupCreate" + account_invitation: + $ref: "#/components/schemas/AccountInvitationCreate" required: - - group + - account_invitation example: - group: - name: "Example Ltd" - external_id: "ABC_123" - max_pageviews_per_month: 500000 - max_unique_visitors_per_month: 100000 + account_invitation: + name: "Grace Hopper" + email: "newmember@cookiechimp.com" - GroupUpdate: + AccountInvitationUpdate: type: object properties: name: type: string - description: Name of the group - external_id: - type: string - description: External ID set by the partner + description: Name of the user the invitation is for required: - name - - external_id - GroupUpdateRequest: + AccountInvitationUpdateRequest: + type: object + properties: + account_invitation: + $ref: "#/components/schemas/AccountInvitationUpdate" + required: + - account_invitation + example: + account_invitation: + name: "Grace Hopper" + + AccountInvitation: + type: object + properties: + id: + type: string + name: + type: string + description: Name of the user the invitation is for + email: + type: string + format: email + description: Email address of the user + invite_url: + type: string + description: URL the invited user follows to accept the invitation + roles: + $ref: "#/components/schemas/Roles" + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + invited_by: + type: [object, "null"] + description: The user who sent the invitation. Empty when it was created via the API. + properties: + id: + type: string + name: + type: string + email: + type: string + format: email + example: + id: "inv123" + name: "Grace Hopper" + email: "newmember@cookiechimp.com" + invite_url: "https://cookiechimp.com/account_invitations/inv123" + roles: + admin: true + created_at: "2023-01-25T09:30:20Z" + updated_at: "2023-01-25T09:30:20Z" + invited_by: + id: "usr123" + name: "Ada Lovelace" + email: "admin@example.com" + + AccountInvitationResponse: + type: object + properties: + data: + $ref: "#/components/schemas/AccountInvitation" + + VisitorStatsMetric: + type: object + properties: + count: + type: number + description: Value for the current period + rate_of_change: + type: number + description: Percentage change against the comparison period + + VisitorStatsRawMetric: type: object properties: - group: - $ref: "#/components/schemas/GroupUpdate" - required: - - group - example: - group: - name: "Example Ltd" - external_id: "ABC_123" + current_period: + type: number + previous_period: + type: number - Group: + VisitorStats: + type: [object, "null"] + description: | + Visitor analytics for the last 30 days, compared against the 30 days + before that. `null` when the client passed `?visitor_stats=false`. + properties: + comparison: + type: string + description: Label for the comparison period + statistics: + type: object + properties: + consents: + $ref: "#/components/schemas/VisitorStatsMetric" + unique_visitors: + $ref: "#/components/schemas/VisitorStatsMetric" + consent_rate: + $ref: "#/components/schemas/VisitorStatsMetric" + raw_data: + type: object + properties: + consents: + $ref: "#/components/schemas/VisitorStatsRawMetric" + unique_visitors: + $ref: "#/components/schemas/VisitorStatsRawMetric" + consent_rate: + $ref: "#/components/schemas/VisitorStatsRawMetric" + + Account: type: object properties: id: type: string readOnly: true - description: Unique identifier for the group - partner: + description: Unique identifier for the account + name: type: string readOnly: true - description: Name of the partner the group belongs to - name: + description: Domain of the account + account_type: type: string - description: Name of the group - external_id: + enum: [website, mobile_app] + readOnly: true + domain: + type: [string, "null"] + subdomain: + type: [string, "null"] + default_language: type: string - description: External ID set by the partner - max_pageviews_per_month: - type: [integer, "null"] - description: Maximum number of pageviews per month - max_unique_visitors_per_month: - type: [integer, "null"] - description: Maximum number of unique visitors per month + description: ISO 639-1 code the banner falls back to + auto_translate: + type: boolean + translations: + type: array + items: + type: string + description: ISO 639-1 codes the banner is translated into + industry: + type: [string, "null"] + description: + type: [string, "null"] + support_email: + type: [string, "null"] + banner_debug_mode: + type: boolean + blocking_mode: + type: [string, "null"] + retain_consent_duration: + type: integer + description: Years consent records are retained + allowed_domains: + type: array + items: + type: string + readOnly: true + description: List of allowed domains the script will run on + owner_id: + type: string + readOnly: true + description: ID of the account user that owns the account + onboarding_status: + type: [string, "null"] + readOnly: true + description: | + The status of the account, where: + - `account_created`: The account has been created. + - `vendors_setup`: Categories, services or TCF vendors have been set up. + - `code_installed`: The script has been installed on the user's website. + - `inactive`: Less than 10 visitors to the user's website in the last 30 days. + - `live`: The account is live and operational. + + `null` means the status could not be determined — the analytics store + behind it was unreachable when the response was built. It is not a + statement about the account, and the request itself still succeeded. + Treat it as "unknown" and retry rather than as a downgrade in status. + visitor_stats: + $ref: "#/components/schemas/VisitorStats" + created_at: + type: string + format: date-time + readOnly: true + description: Date and time when the account was created + updated_at: + type: string + format: date-time + readOnly: true + description: Date and time when the account was last updated example: id: "abc123" - partner: "partner_one" - name: "Example Ltd" - external_id: "ABC_123" - max_pageviews_per_month: 500000 - max_unique_visitors_per_month: 100000 + name: "example.com" + allowed_domains: ["example.net", "example.org"] + owner_id: "xyz123" + onboarding_status: "live" + visitor_stats: + comparison: "vs. Last 30 days" + statistics: + consents: + count: 213 + rate_of_change: 25.29 + unique_visitors: + count: 895 + rate_of_change: 42.06 + consent_rate: + count: 23.8 + rate_of_change: -11.85 + raw_data: + consents: + current_period: 213 + previous_period: 170 + unique_visitors: + current_period: 895 + previous_period: 630 + consent_rate: + current_period: 23.8 + previous_period: 27.0 + created_at: "2022-12-20T18:45:00Z" + updated_at: "2023-01-10T19:00:00Z" - Consent: + AccountWrite: type: object - description: | - A consent record, written when a visitor interacts with the consent - banner. properties: - id: + name: type: string - description: Unique identifier for the consent - consent_banner_id: - type: [string, "null"] - description: ID of the consent banner the visitor saw - uuid: + description: | + The account's domain. Must be a valid domain for website accounts. + domain: type: string - description: Anonymous visitor identifier generated by the widget - url: + subdomain: type: string - description: URL the consent was given on - ip_address: - type: [string, "null"] - description: Partially masked IP address of the visitor - ip_country: - type: [string, "null"] - description: Two-letter country code resolved from the visitor's IP - ip_region: - type: [string, "null"] - description: Region resolved from the visitor's IP - visitor_user_id: - type: [string, "null"] - description: ID of the user set by your website - accept_type: - type: [string, "null"] - description: | - How the visitor responded. Typically `all`, `custom` or `necessary`. - accepted_categories: - type: [array, "null"] - items: - type: string - description: Slugs of the categories the visitor accepted - rejected_categories: - type: [array, "null"] + default_language: + type: string + description: ISO 639-1 code the banner falls back to + auto_translate: + type: boolean + description: Whether new content is machine-translated into the account's languages + translations: + type: array items: type: string - description: Slugs of the categories the visitor rejected - accepted_services: - type: [object, "null"] - additionalProperties: - type: array - items: - type: string - description: Accepted services, keyed by category slug - rejected_services: - type: [object, "null"] - additionalProperties: - type: array - items: - type: string - description: Rejected services, keyed by category slug - accepted_cookies: - type: [array, "null"] - items: - type: object - description: Storage items the visitor accepted - rejected_cookies: - type: [array, "null"] - items: - type: object - description: Storage items the visitor rejected - device: + description: ISO 639-1 codes the banner is translated into + allowed_domains_list: type: string - description: Device type derived from the user agent - user_agent: + description: | + Comma-separated list of domains the script may run on. Write-only — + responses return the parsed `allowed_domains` array instead. + industry: type: string - description: User agent of the visitor's browser - gpc_preference: - type: [boolean, "null"] - description: Whether the visitor sent a Global Privacy Control signal - do_not_sell: + description: + type: string + support_email: + type: string + format: email + banner_debug_mode: type: boolean - description: Whether the visitor opted out of the sale of their data - created_at: + blocking_mode: type: string - format: date-time - description: Date and time when the consent was recorded - example: - id: "consent321" - consent_banner_id: "banner123" - uuid: "8f14e45f-ea8f-4f2e-b1b0-0f1d2c3b4a59" - url: "https://example.com/pricing" - ip_address: "203.0.113.XXX" - ip_country: "GB" - ip_region: "England" - visitor_user_id: "user456" - accept_type: "custom" - accepted_categories: ["essential", "analytics"] - rejected_categories: ["marketing"] - accepted_services: { "analytics": ["Google Analytics"] } - rejected_services: { "marketing": ["Meta Pixel"] } - accepted_cookies: [] - rejected_cookies: [] - device: "desktop" - user_agent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)" - gpc_preference: false - do_not_sell: false - created_at: "2023-01-20T12:46:30Z" + description: How aggressively scripts are blocked before consent + retain_consent_duration: + type: integer + description: | + Years consent records are retained. Free accounts are fixed at 3. - ConsentResponse: + AccountWriteRequest: type: object properties: - data: - $ref: "#/components/schemas/Consent" + account: + $ref: "#/components/schemas/AccountWrite" + required: + - account + example: + account: + name: "example.com" + support_email: "privacy@example.com" + banner_debug_mode: false - AIAction: + SiteServiceSettings: type: object - description: An action CookieChimp's AI took on a category or storage item. properties: - id: + name: type: string - description: Unique identifier for the AI action - action_type: + description: Name of the service, shown to visitors + category_id: type: string - enum: [autofill, auto_categorise, translate] - description: What the AI did - actionable_type: + description: | + ID of the category the service belongs to. Required on create; + on update, supplying it moves the service to that category. + description: type: string - enum: [Category, ClientStorageItem] - description: The type of record the action was taken on - actionable_id: + description: Description shown to visitors + processing_company: type: string - description: ID of the record the action was taken on - suggested_params: - type: [object, "null"] - additionalProperties: true - description: The values the AI suggested - created_at: + description: Company that processes data through this service + privacy_policy_url: + type: string + additional_info: + type: string + accepted_by_default: + type: boolean + description: Whether the service is pre-accepted in the banner + enabled: + type: boolean + description: Whether the service is active on the account + auto_block: + type: boolean + description: Whether the widget blocks this service's scripts before consent + auto_block_domains_input: type: string - format: date-time - actionable: description: | - The record the action was taken on — a category or a storage item, - in the same shape those endpoints return. Use `actionable_type` to - tell which one it is. - anyOf: - - $ref: "#/components/schemas/Category" - - $ref: "#/components/schemas/StorageItem" - example: - id: "act123" - action_type: "autofill" - actionable_type: "ClientStorageItem" - actionable_id: "sto123" - suggested_params: - description: "Used to distinguish users." - company_name: "Google" - duration: "2 years" - created_at: "2023-02-01T10:00:00Z" - actionable: - id: "sto123" - name: "_ga" - storage_type: "cookie" + Domains to auto-block, separated by commas or newlines. Parsed into + the `auto_block_domains` list on save. - AIActionResponse: - type: object - properties: - data: - $ref: "#/components/schemas/AIAction" - - Roles: - type: object + SiteServiceWrite: description: | - Role flags for a membership. Currently `admin` is the only role, and it - defaults to `true`. - additionalProperties: - type: boolean - example: - admin: true + Everything a service accepts on update, including its translations. + allOf: + - $ref: "#/components/schemas/SiteServiceSettings" + - type: object + properties: + site_service_translations_attributes: + type: array + description: | + Create, update or discard the localized copy a visitor reads when the + banner runs in another language. Send an entry without an `id` to add + a translation, with the `id` the response gave you to change one, and + with `discarded_at` set to remove one. + + Not accepted when creating a service — add them by updating it, which + is where the account's language entitlement is enforced. + items: + type: object + description: | + An entry without an `id` creates a translation and must name its + `language_code`; an entry with one may send only the fields it + changes. + anyOf: + - required: [id] + - required: [language_code] + properties: + id: + type: string + description: | + The translation's ID, as returned in `translations`. Omit it to + add a new translation. + language_code: + type: string + description: | + The language this copy is for. Cannot be the service's own + `default_language`, and each language may appear once. + description: + type: [string, "null"] + additional_info: + type: [string, "null"] + discarded_at: + type: [string, "null"] + format: date-time + description: Set to remove this translation + + SiteServiceCreate: + description: | + A service is always filed under a category, so `category_id` is + required here even though it is optional when updating. Translations are + absent for the same reason they are on banners — add them by updating + the service, which is where the account's language entitlement is + enforced. Sending them anyway is not an error, they are simply ignored. + allOf: + - $ref: "#/components/schemas/SiteServiceSettings" + required: + - name + - category_id - AccountUserWrite: + SiteServiceCreateRequest: type: object properties: - roles: - $ref: "#/components/schemas/Roles" + site_service: + $ref: "#/components/schemas/SiteServiceCreate" + required: + - site_service + example: + site_service: + name: "Google Analytics" + category_id: "cat1" + processing_company: "Google LLC" + privacy_policy_url: "https://policies.google.com/privacy" + auto_block: true - AccountUserWriteRequest: + SiteServiceUpdateRequest: type: object + description: | + Every field is optional; anything omitted keeps its current value. + Supplying `category_id` moves the service to that category. properties: - account_user: - $ref: "#/components/schemas/AccountUserWrite" + site_service: + $ref: "#/components/schemas/SiteServiceWrite" required: - - account_user + - site_service example: - account_user: - roles: - admin: false + site_service: + name: "Google Analytics 4" + auto_block: false - AccountUser: + SiteService: type: object properties: id: type: string - description: Unique identifier for the account user - roles: - $ref: "#/components/schemas/Roles" + readOnly: true + name: + type: string + description: + type: [string, "null"] + processing_company: + type: [string, "null"] + privacy_policy_url: + type: [string, "null"] + additional_info: + type: [string, "null"] + accepted_by_default: + type: boolean + enabled: + type: boolean + auto_block: + type: boolean + auto_block_domains: + type: array + items: + type: string + description: Domains the widget blocks for this service before consent + default_language: + type: [string, "null"] + category: + type: object + description: The category this service is filed under + properties: + id: + type: [string, "null"] + name: + type: [string, "null"] + translations: + type: array + description: | + The localized copy for this service, one entry per language, with the + IDs `site_service_translations_attributes` expects back. Discarded + translations are not returned. + items: + type: object + properties: + id: + type: string + language_code: + type: string + description: + type: [string, "null"] + additional_info: + type: [string, "null"] created_at: type: string format: date-time + readOnly: true updated_at: type: string format: date-time - user: - type: object - properties: - id: - type: string - name: - type: string - email: - type: string - format: email - account: - type: object - properties: - id: - type: string - name: - type: string + readOnly: true example: - id: "accuser123" - roles: - admin: true - created_at: "2023-01-05T10:15:30Z" - updated_at: "2023-01-15T11:20:45Z" - user: - id: "usr123" - name: "Ada Lovelace" - email: "admin@example.com" - account: - id: "acc123" - name: "example.com" + id: "svc1" + name: "Google Analytics" + description: "Measures how visitors use the site." + processing_company: "Google LLC" + privacy_policy_url: "https://policies.google.com/privacy" + additional_info: null + accepted_by_default: false + enabled: true + auto_block: true + auto_block_domains: ["google-analytics.com"] + default_language: "en" + category: + id: "cat1" + name: "Analytics" + translations: + - id: "svct1" + language_code: "de" + description: "Misst, wie Besucher die Website nutzen." + additional_info: null + created_at: "2023-03-01T09:00:00Z" + updated_at: "2023-03-23T12:34:56Z" - AccountUserResponse: + SiteServiceResponse: type: object properties: data: - $ref: "#/components/schemas/AccountUser" + $ref: "#/components/schemas/SiteService" - AccountInvitationCreate: + ConsentBannerSettings: type: object + description: | + Every setting the banner designer exposes. The response returns the same + set, so a banner can be read, edited and sent back unchanged. Fields the + database leaves nullable are declared nullable here too — the response + returns null for unset copy, and the same shape has to be sendable back. properties: + banner_accept_btn_text: + type: [string, "null"] + banner_accept_necessary_btn_text: + type: [string, "null"] + banner_footer_content: + type: [string, "null"] + banner_preferences_btn_text: + type: [string, "null"] + btn_border_radius: + type: string + consent_additional_description: + type: [string, "null"] + consent_button_placement: + type: string + consent_description: + type: [string, "null"] + consent_duration_days: + type: integer + description: How long a visitor's choice is remembered + consent_layout: + type: string + description: e.g. `box`, `bar` + consent_mode: + type: string + description: e.g. `opt_in`, `opt_out` + consent_position: + type: string + description: e.g. `bottom_center` + consent_title: + type: [string, "null"] + dark_mode: + type: string + description: e.g. `light`, `dark`, `auto` + do_not_sell_description: + type: [string, "null"] + do_not_sell_title: + type: [string, "null"] + enable_banner_footer: + type: boolean + enable_tcf: + type: boolean + description: Whether the banner runs in IAB TCF mode + equal_weight_buttons: + type: boolean + floating_preferences_icon_position: + type: string + force_consent: + type: boolean + description: Whether the page is blocked until the visitor chooses + language_autodetect: + type: string + logo_url_dark: + type: [string, "null"] + logo_url_light: + type: [string, "null"] + modal_border_radius: + type: string name: type: string - description: Name of the user the invitation is for - email: + preferences_accept_btn_text: + type: [string, "null"] + preferences_accept_necessary_btn_text: + type: [string, "null"] + preferences_control_level: type: string - format: email - description: Email address of the user + description: How granular the preferences modal is + privacy_policy_url: + type: [string, "null"] + save_preferences_btn_text: + type: [string, "null"] + settings_description: + type: [string, "null"] + settings_layout: + type: string + settings_more_info: + type: [string, "null"] + settings_more_info_title: + type: [string, "null"] + settings_position_x: + type: string + settings_title: + type: [string, "null"] + show_banner_accept_btn: + type: boolean + show_banner_accept_necessary_btn: + type: boolean + show_banner_close_btn: + type: boolean + show_banner_preferences_btn: + type: boolean + show_floating_preferences_icon: + type: boolean + show_preferences_accept_btn: + type: boolean + show_preferences_accept_necessary_btn: + type: boolean + show_save_preferences_btn: + type: boolean + show_services: + type: boolean + swap_buttons: + type: boolean + target_canadian_regions: + type: array + items: + type: string + description: Province and territory codes, used when `target_type` is `selected_canadian_provinces`. Shares a column with `target_us_states`. + target_countries: + type: array + items: + type: string + description: ISO 3166-1 alpha-2 codes + target_type: + type: string + description: Who sees the banner, e.g. `worldwide`, `selected_countries` + target_us_states: + type: array + items: + type: string + button_colors: + type: object + additionalProperties: + type: string + description: Colour overrides, keyed by button element + + ConsentBannerWrite: + description: | + Every setting the banner designer exposes, plus whether the banner is + live. Creating a banner uses ConsentBannerSettings instead, which is this + same set without `enabled`. + allOf: + - $ref: "#/components/schemas/ConsentBannerSettings" + - type: object + properties: + enabled: + type: boolean + description: Whether the banner is live. + + ConsentBannerUpdate: + description: | + Everything a banner accepts on update, including its translations. + allOf: + - $ref: "#/components/schemas/ConsentBannerWrite" + - type: object + properties: + banner_languages_attributes: + type: object + description: | + Create, update or discard translations. Not accepted when creating a + banner — add them by updating it, which is where the account's + language entitlement is enforced. + + Keys are arbitrary indices. Omit `id` to add a translation; pass the + `id` from the banner's `translations` array to change an existing + one, and set `discarded_at` to remove it. + additionalProperties: + type: object + description: | + An entry without an `id` creates a translation and must name its + `language_code`; an entry with one may send only the fields it + changes. + anyOf: + - required: [id] + - required: [language_code] + properties: + id: + type: string + description: From the banner's `translations` array + language_code: + type: string + description: ISO 639-1 code + consent_title: + type: [string, "null"] + consent_description: + type: [string, "null"] + banner_accept_btn_text: + type: [string, "null"] + banner_accept_necessary_btn_text: + type: [string, "null"] + banner_preferences_btn_text: + type: [string, "null"] + settings_title: + type: [string, "null"] + settings_description: + type: [string, "null"] + privacy_policy_url: + type: [string, "null"] + save_preferences_btn_text: + type: [string, "null"] + consent_additional_description: + type: [string, "null"] + banner_footer_content: + type: [string, "null"] + settings_more_info_title: + type: [string, "null"] + settings_more_info: + type: [string, "null"] + do_not_sell_title: + type: [string, "null"] + do_not_sell_description: + type: [string, "null"] + preferences_accept_btn_text: + type: [string, "null"] + preferences_accept_necessary_btn_text: + type: [string, "null"] + discarded_at: + type: [string, "null"] + format: date-time + description: Set to remove this translation + + ConsentBannerCreate: + description: | + The create payload: every banner setting except `enabled`, which is not + part of it because a new banner is always created disabled — enable it + with an update once its content and targeting are set. Sending `enabled` + anyway is not an error, the value is simply ignored. + allOf: + - $ref: "#/components/schemas/ConsentBannerSettings" + + ConsentBannerCreateRequest: + type: object + description: | + Translations are not accepted here — add them by updating the banner, + which is where the account's language entitlement is enforced. + properties: + consent_banner: + $ref: "#/components/schemas/ConsentBannerCreate" required: - - name - - email + - consent_banner + + ConsentBannerUpdateRequest: + type: object + properties: + consent_banner: + $ref: "#/components/schemas/ConsentBannerUpdate" + required: + - consent_banner + example: + consent_banner: + name: "EU banner" + consent_title: "We use cookies" + consent_mode: "opt_in" + target_type: "selected_countries" + target_countries: ["DE", "FR"] + + ConsentBanner: + description: | + Every setting the update endpoint accepts, plus the read-only fields + below — the response is derived from the same list the API permits, so a + banner can be read, edited and sent back unchanged. + allOf: + - $ref: "#/components/schemas/ConsentBannerWrite" + - type: object + properties: + id: + type: string + readOnly: true + name: + type: string + enabled: + type: boolean + enable_tcf: + type: boolean + force_consent: + type: boolean + consent_mode: + type: string + consent_duration_days: + type: integer + consent_layout: + type: string + consent_position: + type: string + preferences_control_level: + type: string + dark_mode: + type: string + language_autodetect: + type: string + revision: + type: integer + readOnly: true + description: Bumped when a change requires visitors to consent again + translations: + type: array + readOnly: true + description: | + The banner's translations, each with its localized copy alongside + its `id` — so a client can read what it is allowed to write rather + than overwriting fields it cannot see. The `id` is what you send + back in `banner_languages_attributes` to update or discard one. + items: + type: object + properties: + id: + type: string + language_code: + type: string + description: ISO 639-1 code + consent_title: + type: [string, "null"] + consent_description: + type: [string, "null"] + consent_additional_description: + type: [string, "null"] + banner_accept_btn_text: + type: [string, "null"] + banner_accept_necessary_btn_text: + type: [string, "null"] + banner_preferences_btn_text: + type: [string, "null"] + banner_footer_content: + type: [string, "null"] + settings_title: + type: [string, "null"] + settings_description: + type: [string, "null"] + settings_more_info_title: + type: [string, "null"] + settings_more_info: + type: [string, "null"] + privacy_policy_url: + type: [string, "null"] + do_not_sell_title: + type: [string, "null"] + do_not_sell_description: + type: [string, "null"] + preferences_accept_btn_text: + type: [string, "null"] + preferences_accept_necessary_btn_text: + type: [string, "null"] + save_preferences_btn_text: + type: [string, "null"] + created_at: + type: string + format: date-time + readOnly: true + updated_at: + type: string + format: date-time + readOnly: true + example: + id: "ban1" + name: "EU banner" + enabled: true + enable_tcf: false + force_consent: false + consent_mode: "opt_in" + consent_duration_days: 180 + consent_layout: "box" + consent_position: "bottom_center" + consent_title: "We use cookies" + consent_description: "We use cookies to improve your experience." + privacy_policy_url: "https://example.com/privacy" + preferences_control_level: "category_and_services" + dark_mode: "light" + language_autodetect: "document" + revision: 2 + target_type: "selected_countries" + target_countries: ["DE", "FR"] + target_us_states: [] + target_canadian_regions: [] + translations: + - id: "trn1" + language_code: "de" + - id: "trn2" + language_code: "fr" + created_at: "2023-01-05T10:15:30Z" + updated_at: "2023-02-01T08:00:00Z" - AccountInvitationCreateRequest: + ConsentBannerResponse: type: object properties: - account_invitation: - $ref: "#/components/schemas/AccountInvitationCreate" - required: - - account_invitation - example: - account_invitation: - name: "Grace Hopper" - email: "newmember@cookiechimp.com" + data: + $ref: "#/components/schemas/ConsentBanner" - AccountInvitationUpdate: + IntegrationWrite: type: object + description: | + `status` applies to every integration. The remaining keys are only + accepted by the integration that declares them; anything else is ignored. properties: - name: + status: type: string - description: Name of the user the invitation is for - required: - - name + enum: [active, inactive] + scan_frequency: + type: string + enum: [weekly, monthly] + description: | + `SiteScanIntegration` only. Weekly is not available on free accounts. + auto_add_vendors: + type: boolean + description: "`SiteScanIntegration` only: add vendors a scan discovers." + url_passthrough: + type: boolean + description: "`GoogleConsentModeIntegration` only." + ads_data_redaction: + type: boolean + description: "`GoogleConsentModeIntegration` only." - AccountInvitationUpdateRequest: + IntegrationWriteRequest: type: object properties: - account_invitation: - $ref: "#/components/schemas/AccountInvitationUpdate" + integration: + $ref: "#/components/schemas/IntegrationWrite" required: - - account_invitation + - integration example: - account_invitation: - name: "Grace Hopper" + integration: + status: "active" + scan_frequency: "monthly" + auto_add_vendors: true - AccountInvitation: + Integration: type: object properties: id: type: string - name: + readOnly: true + type: type: string - description: Name of the user the invitation is for - email: + readOnly: true + description: The integration class, e.g. `SiteScanIntegration` + name: type: string - format: email - description: Email address of the user - invite_url: + readOnly: true + description: Human-readable name + status: type: string - description: URL the invited user follows to accept the invitation - roles: - $ref: "#/components/schemas/Roles" + enum: [active, inactive] + short_description: + type: [string, "null"] + readOnly: true + settings: + type: [object, "null"] + additionalProperties: true + description: Integration-specific settings created_at: type: string format: date-time + readOnly: true updated_at: type: string format: date-time - invited_by: - type: [object, "null"] - description: The user who sent the invitation. Empty when it was created via the API. - properties: - id: - type: string - name: - type: string - email: - type: string - format: email + readOnly: true example: - id: "inv123" - name: "Grace Hopper" - email: "newmember@cookiechimp.com" - invite_url: "https://cookiechimp.com/account_invitations/inv123" - roles: - admin: true - created_at: "2023-01-25T09:30:20Z" - updated_at: "2023-01-25T09:30:20Z" - invited_by: - id: "usr123" - name: "Ada Lovelace" - email: "admin@example.com" + id: "int1" + type: "SiteScanIntegration" + name: "Site Scan" + status: "active" + short_description: "Scan your website for cookies on a schedule." + settings: + scan_frequency: "monthly" + auto_add_vendors: false + created_at: "2023-01-05T10:15:30Z" + updated_at: "2023-02-01T08:00:00Z" - AccountInvitationResponse: + IntegrationResponse: type: object properties: data: - $ref: "#/components/schemas/AccountInvitation" + $ref: "#/components/schemas/Integration" - VisitorStatsMetric: + ScanWrite: type: object properties: - count: - type: number - description: Value for the current period - rate_of_change: - type: number - description: Percentage change against the comparison period + starting_url: + type: string + description: | + URL the crawl starts from. A missing scheme is treated as `https://`. + Defaults to the account's own domain when omitted. + max_pages: + type: integer + minimum: 1 + maximum: 100 + description: Maximum pages to crawl + crawl_subdomains: + type: boolean + description: Whether the crawl follows links onto subdomains - VisitorStatsRawMetric: + ScanWriteRequest: type: object properties: - current_period: - type: number - previous_period: - type: number - - VisitorStats: - type: [object, "null"] - description: | - Visitor analytics for the last 30 days, compared against the 30 days - before that. `null` when the client passed `?visitor_stats=false`. - properties: - comparison: - type: string - description: Label for the comparison period - statistics: - type: object - properties: - consents: - $ref: "#/components/schemas/VisitorStatsMetric" - unique_visitors: - $ref: "#/components/schemas/VisitorStatsMetric" - consent_rate: - $ref: "#/components/schemas/VisitorStatsMetric" - raw_data: - type: object - properties: - consents: - $ref: "#/components/schemas/VisitorStatsRawMetric" - unique_visitors: - $ref: "#/components/schemas/VisitorStatsRawMetric" - consent_rate: - $ref: "#/components/schemas/VisitorStatsRawMetric" + scan: + $ref: "#/components/schemas/ScanWrite" + required: + - scan + example: + scan: + starting_url: "https://example.com" + max_pages: 25 + crawl_subdomains: false - Account: + Scan: type: object properties: id: type: string readOnly: true - description: Unique identifier for the account - name: + status: type: string + enum: [queued, in_progress, completed, failed] readOnly: true - description: Domain of the account - allowed_domains: - type: array - items: - type: string - readOnly: true - description: List of allowed domains the script will run on - owner_id: + starting_url: type: string - readOnly: true - description: ID of the account user that owns the account - onboarding_status: + domain: type: string readOnly: true + description: Host derived from `starting_url`, without a `www.` prefix + max_pages: + type: [integer, "null"] + crawl_subdomains: + type: boolean + response_code: + type: [string, "null"] + readOnly: true + report: + type: object + readOnly: true description: | - The status of the account, where: - - `account_created`: The account has been created. - - `vendors_setup`: Categories, services or TCF vendors have been set up. - - `code_installed`: The script has been installed on the user's website. - - `inactive`: Less than 10 visitors to the user's website in the last 30 days. - - `live`: The account is live and operational. - visitor_stats: - $ref: "#/components/schemas/VisitorStats" + Only present once `status` is `completed`. Storage items found + before and after consent, grouped by vendor and storage type. + properties: + before_consent: + type: object + additionalProperties: true + after_consent: + type: object + additionalProperties: true created_at: type: string format: date-time readOnly: true - description: Date and time when the account was created updated_at: type: string format: date-time readOnly: true - description: Date and time when the account was last updated example: - id: "abc123" - name: "example.com" - allowed_domains: ["example.net", "example.org"] - owner_id: "xyz123" - onboarding_status: "live" - visitor_stats: - comparison: "vs. Last 30 days" - statistics: - consents: - count: 213 - rate_of_change: 25.29 - unique_visitors: - count: 895 - rate_of_change: 42.06 - consent_rate: - count: 23.8 - rate_of_change: -11.85 - raw_data: - consents: - current_period: 213 - previous_period: 170 - unique_visitors: - current_period: 895 - previous_period: 630 - consent_rate: - current_period: 23.8 - previous_period: 27.0 - created_at: "2022-12-20T18:45:00Z" - updated_at: "2023-01-10T19:00:00Z" + id: "scn1" + status: "queued" + starting_url: "https://example.com" + domain: "example.com" + max_pages: 25 + crawl_subdomains: false + response_code: null + created_at: "2023-03-01T09:00:00Z" + updated_at: "2023-03-01T09:00:00Z" + + ScanResponse: + type: object + properties: + data: + $ref: "#/components/schemas/Scan" + + ComplianceJurisdiction: + type: object + properties: + id: + type: string + description: Identifier for the regulation, e.g. `gdpr` + name: + type: string + region: + type: string + flag: + type: [string, "null"] + applicability: + type: [string, "null"] + description: Who the regulation applies to + legal_refs: + type: [array, "null"] + items: + type: object + additionalProperties: true + compliant: + type: boolean + description: Whether the banner covering this regulation satisfies it + issues: + type: array + description: Requirements not currently met + items: + type: object + additionalProperties: true + warnings: + type: array + description: Things worth attention that do not by themselves fail the check + items: + type: object + additionalProperties: true + compliant_items: + type: array + description: Requirements already satisfied + items: + type: object + additionalProperties: true + matched_region: + type: [string, "null"] + description: The targeted region that caused this banner to be selected + banner: + type: object + description: | + The banner evaluated for this regulation. Both fields are null when + no banner targets it, which is itself a compliance issue. + properties: + id: + type: [string, "null"] + name: + type: [string, "null"] + + ComplianceReportResponse: + type: object + properties: + data: + type: object + properties: + compliant: + type: boolean + description: | + True when every regulation not ignored is satisfied. + jurisdictions: + type: array + items: + $ref: "#/components/schemas/ComplianceJurisdiction" + ignored_jurisdictions: + type: array + description: | + Regulations the account has chosen to ignore. Reported so a + client can see what was excluded from `compliant`. + items: + $ref: "#/components/schemas/ComplianceJurisdiction" + + BannerTemplate: + type: object + properties: + id: + type: string + description: Pass as `jurisdiction_id` when creating a banner + name: + type: string + flag: + type: [string, "null"] + consent_type: + type: string + description: e.g. `opt_in`, `opt_out` + consent_type_label: + type: string + description: e.g. `Opt-in` + + BannerTemplatesResponse: + type: object + properties: + data: + type: array + items: + type: object + properties: + region: + type: string + region_label: + type: string + templates: + type: array + items: + $ref: "#/components/schemas/BannerTemplate" + example: + data: + - region: "europe" + region_label: "Europe" + templates: + - id: "gdpr" + name: "GDPR" + flag: "🇪🇺" + consent_type: "opt_in" + consent_type_label: "Opt-in" + + BannerFromTemplateRequest: + type: object + properties: + jurisdiction_id: + type: string + description: A template `id` from `GET /banner-templates` + required: + - jurisdiction_id + example: + jurisdiction_id: "gdpr" security: - authorization: []