Skip to content
TraceLead

Site configuration contract

What the public configuration endpoint returns and how it is cached.

GET /api/config/{siteId} returns the settings the browser snippet needs. It is public: site IDs are identifiers, not secrets, and the response never contains tenant, billing, user or visitor data. Schema: /schemas/site-config.v1.json.

Enabled response

{
  "schemaVersion": 1,
  "siteId": "tl_k3j9x2m8n4p7q1r5s6t0",
  "version": 3,
  "enabled": true,
  "attributionLifetimeDays": 90,
  "consentMode": "explicit",
  "consentPreset": "custom",
  "lastTouch": false,
  "fieldMappings": [
    { "field": "utm_source", "selector": "input[name=\"tl_utm_source\"]" },
    { "field": "channel", "selector": "input[name=\"tl_channel\"]" }
  ],
  "suppressedQueryParams": ["email", "token", "password"],
  "collect": { "sessions": true, "leads": true }
}

Disabled response

{ "schemaVersion": 1, "siteId": "tl_k3j9x2m8n4p7q1r5s6t0", "version": 3, "enabled": false, "reason": "site_paused" }

reason is one of site_paused, subscription_inactive, domain_not_allowed, not_found (HTTP 404). The snippet must stay inert whenever enabled is false or the request fails.

Semantics

  • Domain check: the host from the Origin header (fallback Referer) must match a registered domain (include_subdomains honoured). Requests without either header are served. This is abuse reduction, not authentication.
  • Subscription: served when the team has an active/grace-period subscription or a generic trial. TRACELEAD_REQUIRE_SUBSCRIPTION=false disables the check for local development.
  • Caching: Cache-Control: public, max-age=300, s-maxage=300 (configurable via TRACELEAD_CONFIG_MAX_AGE), ETag (304 on If-None-Match), Vary: Origin. A paused or cancelled site is therefore served for at most 5 minutes after the change. Not-found responses cache for 60 s.
  • CORS: Access-Control-Allow-Origin: * so the snippet can always read the response, including the disabled reason.
  • Rate limit: 120 requests/minute per IP (TRACELEAD_CONFIG_RATE_LIMIT), HTTP 429 beyond that.
  • Collection flags: collect.sessions / collect.leads tell the snippet which consent-gated events it may send to POST /api/collect (see docs/collect-api.md, ADR-021). Absent flags mean "send nothing".
  • Consent preset: consentPreset names the consent manager whose marketing category the snippet follows (ADR-032): custom (the page calls tracelead('consent', …) itself), cookiebot, onetrust, cookieyes, complianz, iubenda, usercentrics or consent-mode. Absent or unknown values mean custom. See Consent integration.
  • Last touch: lastTouch (boolean) tells the snippet to keep a second, most-recent-non-direct record under tl_lt_<siteId> and to fill the last_* fields (ADR-026). Absent means off.
  • Field contract: field values are the App\Enums\AttributionField cases (the first-touch fields and their last_* counterparts); the default selector is input[name="tl_<field>"]. The snippet fills only existing elements matching a selector (ADR-004).