Consenti

Public API Routes

Base path: /consenti/api/v1 (default — set via basePath in createConsenti()). No authentication required.

💡Explore and test all routes interactively in the Swagger UI.
MethodPathDescription
GET/profiles/:tenantId/:complianceGroup/:localeHot-serve — serve locale JSON directly from disk (zero DB)
GET/profiles/:tenantId/:profileId/:version/:localeServe a specific version locale JSON (dashboard preview)
GET/resolve-profileGeo-resolve compliance group, return file path + locale
GET/profiles/:idGet profile by ID (DB-backed, default locale)
GET/profiles/:id/:localeGet profile by ID and locale (DB-backed)
GET/profiles/auto/:localeLegacy: geo-resolve and return matching active profile
POST/consentSubmit a consent record
GET/consent/:visitorIdGet current consent for a visitor
PUT/consent/:visitorIdUpdate consent for a visitor
DELETE/consent/:visitorIdGDPR erasure — delete all data for visitor
GET/consent/:visitorId/verifyVerify consent is still valid

Static profile file serving (hot path)

The primary way to serve profiles at scale. Locale JSON files are written to disk when a profile is activated in the dashboard. These routes serve them directly from the filesystem — no DB query on the hot path.

GET /profiles/:tenantId/:complianceGroup/:locale

Serves the active locale JSON for a compliance group from ${storage.path}/profiles/${tenantId}/${complianceGroup}/${locale}.json. Returns Cache-Control: public, max-age=3600, stale-while-revalidate=60 — safe for CDN caching.

If the requested locale file does not exist, responds with 303 See Other to .../default, which serves default.json (the defaultLocale content).

// GET /consenti/api/v1/profiles/default/opt-in/en HTTP/1.1

GET /profiles/:tenantId/:profileId/:version/:locale

Serves a specific immutable version of a profile locale JSON — used by the dashboard version history viewer. These files are written once and never modified.

// GET /consenti/api/v1/profiles/default/my-profile-uuid/2/fr-FR HTTP/1.1

Resolve profile

GET /resolve-profile

Used by widgets configured with compliance.type: 'auto'. Call this once on page load to discover which compliance group and locale file the visitor should receive. The widget then fetches that file directly — zero subsequent DB queries.

Geo resolution uses the geoDataProvider configured in createConsenti(). When S3 sync is enabled, the returned path is an S3 URL.

Query paramDescription
tzIANA timezone string (e.g. Europe/Paris).
langAccept-Language value (e.g. fr-FR,fr;q=0.9).
localePreferred BCP 47 locale (e.g. fr-FR).
// GET /consenti/api/v1/resolve-profile?tz=Europe/Paris&lang=fr-FR&locale=fr-FR HTTP/1.1
ℹ️The warning: 'locale_not_found' field is present only when the requested locale was unavailable and the path was redirected to default.json.

DB-backed profile routes

These routes query the database on each request. Use them for server-side rendering or when you need a fully resolved profile object including the translations map. For high-traffic public-facing widget use, prefer the static file serving routes above.

ℹ️There is no public list endpoint — clients must know the profile ID (copy it from the admin dashboard or pass it from your backend).

Both endpoints return the same response shape. The only difference is which locale is resolved: /profiles/:id uses the profile's defaultLocale, while /profiles/:id/:locale uses the requested locale (falling back to defaultLocale if no translation exists).

Response shape

Both endpoints return a fully-resolved profile — banner text, modal copy, and button labels are already merged for the active locale. There is no raw translations map.

FieldTypeDescription
idstringProfile UUID
versionnumberSchema version — bump triggers re-consent
defaultLocalestringBCP 47 locale used as the translation fallback
currentLocalestringLocale whose content is present in this response
localesstring[]All locale keys defined on the profile
cookiesCookie[]Cookies/purposes managed by this profile
mainBannerMainBannerBanner config resolved for currentLocale
preferenceModalPreferenceModalModal config resolved for currentLocale
gpcBanner?GpcBannerGPC-specific banner variant (omitted if not configured)
resolvedComplianceGroup?ComplianceGroupIdPresent only on /profiles/auto/:locale — the geo-resolved compliance group

GET /profiles/:id

Returns the profile resolved in its defaultLocale. currentLocale will equal defaultLocale.

// GET /consenti/api/v1/profiles/my-profile-id HTTP/1.1

GET /profiles/:id/:locale

Same response shape as /profiles/:id, but currentLocale is set to :locale and all banner/modal strings are resolved for that locale. If the requested locale has no translation, the response falls back to defaultLocale (and currentLocale reflects the requested locale regardless).

// GET /consenti/api/v1/profiles/my-profile-id/fr HTTP/1.1
💡Use locales in the response to build a locale switcher — it lists every locale key the profile has translations for, so you can offer only the languages that are actually configured.

GET /profiles/auto/:locale Legacy

Legacy geo-resolution route — returns a fully resolved profile object from the database. Prefer GET /resolve-profile (above) for new integrations: it returns a file path so the widget fetches the locale JSON directly from disk or CDN, removing all DB overhead from the hot path.

Geo-resolves the visitor's compliance group from geo signals, finds the active profile tagged to that group, and returns it resolved for :locale. The response includes resolvedComplianceGroup. Falls back to the default profile when no active profile exists for the resolved group.

Requires compliance.type: 'auto' in createConsenti().

Query paramDescription
tzIANA timezone string (e.g. Europe/Paris). Used when geoDataProvider: 'timezone'.
langAccept-Language header value (e.g. fr-FR,fr;q=0.9). Used when geoDataProvider: 'language'.
// GET /consenti/api/v1/profiles/auto/en?tz=Europe/Paris HTTP/1.1
ℹ️The 3-step jurisdiction lookup: (1) country + region override (e.g. California → CPRA), (2) country base group (e.g. US → opt-out), (3) country default (most strict) when region is unknown (US unknown state → opt-out-strict).

Consent

Consent records are keyed by visitorId. The visitor ID is persisted in a cookie (consenti_vid) set by the widget after the first submission.

POST /consent

Creates or updates a consent record. If visitorId is omitted, the server generates one and sets it as a cookie in the response.

// POST /consenti/api/v1/consent HTTP/1.1
// Content-Type: application/json

{
  "profileId": "my-profile-id",  // required
  "consentJson": {               // required
    "necessary": "granted",
    "analytics": "granted",
    "marketing": "denied"
  },
  "visitorId": "uuid-v4",        // optional — generated if omitted
  "profileVersion": 3,           // optional — defaults to 1
  "locale": "en",                // optional — defaults to "en"
  "gpcDetected": false,          // optional — defaults to false
  "source": "banner"             // optional — "banner" | "api" | "import"
}

GET /consent/:visitorId

// GET /consenti/api/v1/consent/visitor-uuid HTTP/1.1

PUT /consent/:visitorId

Updates an existing consent record. Only the supplied fields are applied.

// PUT /consenti/api/v1/consent/visitor-uuid HTTP/1.1
// Content-Type: application/json

{
  "consentJson": {          // required
    "necessary": "granted",
    "analytics": "denied",
    "marketing": "denied"
  },
  "locale": "fr",           // optional
  "gpcDetected": true       // optional
}

DELETE /consent/:visitorId

GDPR right-to-erasure. Deletes the consent record, consent history, and visitor record for the given visitor ID. Also expires the consent cookie in the response.

// DELETE /consenti/api/v1/consent/visitor-uuid HTTP/1.1

GET /consent/:visitorId/verify

Checks whether the stored consent record is still valid — i.e., the profile version matches and the record has not expired. The UI widget calls this on page load to decide whether to re-show the banner.

// GET /consenti/api/v1/consent/visitor-uuid/verify HTTP/1.1
💡The widget automatically calls POST /consent after the visitor accepts, then calls GET /consent/:visitorId/verify on subsequent page loads to skip the banner if consent is still valid.