Management API
Everything the dashboard does, for your own automation, partner platforms and AI assistants: accounts and sub-accounts, sites and all their settings, cookie lists, scans, consent proof, statistics and webhooks.
Basics
- Base address:
https://cmp-app.trackable.digital/api/v1 - The full description, for tools and code generators: openapi.json (OpenAPI 3.1, no key needed).
- Keys are made in the dashboard (API and AI assistants). The secret is shown once; we keep only a hash of it.
curl https://cmp-app.trackable.digital/api/v1/sites -H "Authorization: Bearer tcmp_…"Scopes
| Scope | Allows |
|---|---|
read | Everything readable: sites, settings, cookie lists, scans, consent proof, statistics, compliance, experiments |
write | Plus creating, changing and deleting sites, replacing cookie lists, running scans, fixes, imports and experiments |
admin | Plus organisations, members, API keys, webhooks and what the consent log keeps |
A key acts on its own account and every sub-account below it, so an agency's key reaches all its clients. Limit: 120 requests a minute per key.
Errors
Errors look like { "error": { "code": "…", "message": "…", "details": … } }.
| Status | When |
|---|---|
| 400 | Invalid JSON, an unknown field or a wrong type (details lists each problem) |
| 401 | Missing, unknown or revoked key |
| 403 | The key lacks the scope |
| 404 | Not found, or outside the key's accounts (the same answer on purpose) |
| 409 | A domain another site has, or deleting an account that still has sites |
| 422 | Valid JSON that is not allowed, such as an unreadable colour |
| 429 | Over the rate limit |
Endpoints
Paths are relative to the base address. Scope is the least a key needs.
Account
GET
/mereadThe calling key, its scopes and organisation
Organisations
Accounts and sub-accounts (agency → client).
GET
/organisationsreadList the key's organisation and its sub-accounts
POST
/organisationsadminCreate a sub-account (parentId defaults to the key's organisation)
GET
/organisations/{id}readGet an organisation
PATCH
/organisations/{id}adminRename or change kind
DELETE
/organisations/{id}adminDelete an empty organisation (no sites, sub-accounts or kept consent records)
Members
GET
/organisations/{id}/membersreadPeople with dashboard access, and pending invitations
POST
/organisations/{id}/membersadminGive a person access; without an account they are invited: the answer has signUpUrl, their invitation link (signing up needs it)
DELETE
/organisations/{id}/members/{email}adminRemove a member or invitation
Sites
Domain groups: one banner configuration for one or more domains.
GET
/sitesreadList domain groups
Query:
organisationId(Only this organisation and its sub-accounts),domain(Only the site with this hostname),verified(true: only where that hostname is verified),limit(1–200, default 50),offset(Default 0)POST
/siteswriteCreate a domain group; any settings not given use the defaults
GET
/sites/{key}readGet a domain group with settings and install code
PATCH
/sites/{key}writeChange any settings (partial; nested objects merge; null clears gtm, cookieDomain, loaderUrl). Publishes a new config version when the banner config changes
DELETE
/sites/{key}writeDelete: the banner stops and the site is hidden; consent records are kept for their retention period
GET
/sites/{key}/installreadInstall code: recommended (hybrid), same-origin, quick, the cookie declaration embed, and the step-by-step guide for the site's platform
Query:
platform(Show the guide for another platform: wordpress, shopify, webflow, wix, squarespace, framer, hubspot, ghost, bigcommerce, godaddy, hostinger-builder, duda, nextjs, nuxt, astro, gatsby, drupal, joomla, custom)GET
/sites/{key}/domainsreadEach domain: verified or not (verifiedBy: install, dns or before-verification), the last check's reason, and for unverified ones the DNS TXT record that proves them. The banner works on every domain; verified ones belong to this account only, and the Shopify app and server-side tracking connect only to verified domains. Checked again automatically
POST
/sites/{key}/domains/verifywriteCheck the unverified domains now: this site's banner on the homepage (directly or through a GTM container), or the DNS TXT record. Returns every domain's state
GET
/sites/{key}/platformreadThe site's platform, the last detection with its evidence, and every platform there is a guide for
POST
/sites/{key}/platform/detectwriteRead the homepage: platform with evidence, CDN, other cookie banners, GTM containers, whether this banner is installed (on the page or through a GTM container it loads); a confident answer becomes the site's platform unless apply is false. Returns the install guide
GET
/sites/{key}/versionsreadPublished versions, newest first (kept as evidence)
Query:
body(1 to include each version's settings)
Scans
GET
/sites/{key}/scansreadRecent scans
POST
/sites/{key}/scanswriteScan now (about a minute) and update the cookie list; with region, check the banner as a visitor from there instead (evidence only)
GET
/sites/{key}/scans/{id}readA scan's full report, including Google Consent Mode as Google received it per stage (decoded gcd); format=markdown for the report as Markdown
Query:
format(markdown)
Consent
GET
/sites/{key}/consent-logreadWhat the consent log keeps: retentionDays (records older are deleted nightly), ipMode (truncated | none; full on older sites) and storeUserAgent
PUT
/sites/{key}/consent-logadminChange what the consent log keeps. Admin keys only
GET
/sites/{key}/consent-backupreadConsent log backup to your own S3-compatible storage: settings and state (never the secret)
PUT
/sites/{key}/consent-backupadminSet where nightly backups go (one CSV per day). A test file is written first; a refusal is a 422 with the reason
DELETE
/sites/{key}/consent-backupadminStop backups and forget the key (files already written stay)
GET
/sites/{key}/consentsreadProof of consent: every decision for one visitor (subjectId) or one receipt (consentId); without either, the consent log newest first, filtered
Query:
subjectId(sub_…),consentId(cns_…),from(YYYY-MM-DD (default 30 days ago)),to(YYYY-MM-DD),action(accept_all | reject_all | custom | withdraw),jurisdiction(Rules applied, e.g. europe_opt_in),country(ISO country code),limit(1–500, default 100),offset(Default 0),format(csv: every matching row as CSV),all(1: every record kept, not the last 30 days)POST
/sites/{key}/renew-consentsadminAsk every visitor again: publishes the live settings as a new version that voids every earlier choice (the banner shows again on their next visit). Ordinary changes keep choices. Needs an admin key
GET
/sites/{key}/statsreadConsent funnel, rates (answered, ignored, accepted, rejected, partly accepted, analytics and marketing allowed, tags loaded), region and device breakdowns, and the pages where visitors answer
Query:
days(1–395, default 30)
Compliance
The compliance score and its one-click fixes.
GET
/sites/{key}/compliancereadThe compliance score (0–100) and risk per region, every check with its fix, and what to fix first
Query:
region(uk | eu | us; all three when left out)POST
/sites/{key}/fixeswriteApply a one-click fix from the compliance score
Migration
Switching from Cookiebot.
POST
/sites/{key}/cookiebot/bannerwriteImport the site's Cookiebot banner (wording in every language it has, layout) and publish it
POST
/sites/{key}/cookiebot/cookieswriteAdd the cookies in the site's Cookiebot declaration (found by a scan) that the cookie list lacks
Server-side tracking
Optional connection to Trackable SST (server-side GTM). The banner works without it and SST works with any consent tool; connected, both sides can be checked together.
GET
/sites/{key}/sstreadThe connected Trackable SST site and its status: tagging server health, GTM container refreshed in the last 5 minutes, client hostnames. 404 when not connected
POST
/sites/{key}/sstwriteConnect an existing Trackable SST site (created through the SST API). The SST site must be for one of this site's domains, and that domain not another account's site (403 otherwise; Trackable can connect it). Optional: SST works with any consent tool, and this banner works without SST. With applyToBanner, the banner loads the SST site's GTM container through SST (a new version; visitors are not asked again)
DELETE
/sites/{key}/sstwriteDisconnect. The SST site keeps running and the banner keeps its settings
POST
/sites/{key}/sst/verifywriteCheck the banner and server-side tracking together: same site, the banner loads the GTM container SST expects (through SST), tracking works end to end, and every hit SST received carries the banner's consent signal. Each check is pass, warn or fail; ok means nothing failed. Sends one debug GA4 hit
Experiments
A/B tests of the banner, with sequential analysis per region.
GET
/sites/{key}/experimentsreadBanner experiments, running first; sticky = a returning visitor keeps their arm
POST
/sites/{key}/experimentswriteCreate a draft: the full experiment, or { template } (with optional name, hypothesis, regions, mainRegion, mdePoints, confidence)
GET
/sites/{key}/experiments/templatesreadReady-made first experiments from published evidence, each with the draft it creates for this banner (null: does not apply)
PUT
/sites/{key}/experiments/settingswriteExperiment settings for the site
GET
/sites/{key}/experiments/{id}readOne experiment with its results per region (live while running, frozen once ended); a draft gets its problems and a duration forecast
DELETE
/sites/{key}/experiments/{id}writeDelete a draft
POST
/sites/{key}/experiments/{id}/startwriteStart a draft (one running experiment per site)
POST
/sites/{key}/experiments/{id}/stopwriteStop a running experiment and freeze its result; apply publishes the winning arm as the banner
Insights
Anonymised industry benchmarks for sites that share their own results.
GET
/benchmarksreadIndustry benchmarks without a site (for businesses not on the CMP yet): the narrowest anonymised cells for a sector, subsector and business model in one jurisdiction; empty until enough sites share
Query:
sector(Sector id from the taxonomy),subsector(Subsector id (default other)),model(Business model, e.g. ecommerce),region(Jurisdiction, default trackable_uk_opt_in)GET
/sites/{key}/insightsreadOwn rates, and (profile confirmed, sharing on) industry benchmarks, best-banner traits, pooled learnings and the next experiment to run
Query:
region(Jurisdiction compared, e.g. trackable_uk_opt_in; default: the one with most visitors)GET
/sites/{key}/insights/profilereadThe business profile benchmarks are matched on
PUT
/sites/{key}/insights/profilewriteConfirm the business profile
PUT
/sites/{key}/insights/opt-inwriteShare anonymised results (and see the industry's); turning on needs agree: true
Tracking
GTM container audit and the sGTM template.
POST
/gtm-auditreadAudit a GTM container export (web or server) for consent problems and get a fixed copy to import. Nothing is stored. Up to 10 MB
GET
/sgtm-templatereadThe sGTM variable template that verifies the signed consent cookie, with the steps to use it
Audits
Prospect audits: scans of any address.
GET
/auditsreadProspect audits (scans of any address, not tied to a site) of your organisation
POST
/auditswriteAudit any public address now (about a minute)
GET
/audits/{id}readOne prospect audit's full report; format=markdown for Markdown
Query:
format(markdown)
Webhooks
GET
/webhooksadminList webhooks
POST
/webhooksadminAdd a webhook (public https only). The signing secret is returned once
DELETE
/webhooks/{id}adminDelete a webhook
POST
/webhooks/{id}/testadminSend a test delivery (event webhook.test)
API keys
GET
/api-keysadminList keys (never their secrets)
POST
/api-keysadminCreate a key for this organisation or a sub-account, with at most the caller's scopes. The secret is returned once
DELETE
/api-keys/{id}adminRevoke a key
Billing
GET
/sites/{key}/billingreadThe site's plan and product, who pays (self, parent, shopify), and any notice (choosePlan: the plan a Shopify store is asked to choose)
PUT
/sites/{key}/billingadminPlatform keys only: a Shopify store's plan from its Shopify subscription ({"plan": "starter", "payer": "shopify"}). The account is then billed by Shopify, never by our Stripe
Partners
GET
/partnersreadThe partner directory: Certified and Premier agencies that install Trackable CMP, Premier first, then the best fit for a platform and region
Query:
platform(Platform id, e.g. shopify),region(uk | eu | us | ca | au | other)GET
/partnerreadA partner's tier, active paid sites, what the next tier needs, benefits, referral code and link, credits owed and paid, referred accounts and deals
Query:
organisationId(The partner (default: the key's organisation))POST
/partner/dealswriteRegister a prospect's domain as a deal: for 90 days a direct sign-up with it is the partner's referral. Refused when the domain is already a customer or another partner registered it first
PUT
/organisations/{id}/agencyadminMove a client account under another agency. Platform keys move it at once; the client account's own key asks, and an admin of that agency accepts in the dashboard (Clients): the answer is then {id, requested: true, agencyId}. Sites and consent records stay with the client; the old agency loses access once it moves
Next: AI assistants