text/plain on purpose, that
lets sendBeacon work without a CORS preflight. Payloads are JSON
strings.
Fields marked optional are omitted from the body when they have no
meaningful value; do not send them as null.
POST /e, exposures and goals
The main beacon. Batched exposures and goal fires from a pageview.
s,u,v,t, always present. Site id, visitor id, config version, client ms since epoch.nr,nfor new (within 30-min gap),rfor returning.rf, bucketed traffic channel (direct/organic/paid/social).uc,utm_campaignverbatim.p, LCP in ms plus per-experiment attribution.e[], exposure records.g[], goal records.
bn is l for “bucketed on page load” or s for
“bucketed on an SPA route change”.
gn is the exclusion-group generation counter, if the experiment is
a member of a group.
For goals, eid is a UUIDv7 per fire, the ledger uses it for dedup
on retries.
Response: 200 OK with an empty body.
POST /e/validate, preview verdict
Same payload as /e. Server does not persist. Returns:
POST /err, snippet errors
First-occurrence-per-(phase, expId) per session. Deduped server-side.
POST /m, library telemetry
Per-library-load event from the dev libraries dependency manager.
Small, non-per-visitor.
GET /g, edge geo
Returns:
GET /preview-config?exp=<uuid>, one experiment for preview
Returns a draft or paused experiment’s config blob. CORS *. Used by
the preview modal to fetch experiments not yet in the site config.
CORS
Every visitor-facing beacon isAccess-Control-Allow-Origin: *.
sendBeacon with text/plain does not preflight, so wildcards work.
Auth
None on the visitor-facing endpoints. The snippet is public; anyone can read a config for a site they know the id of. That is deliberate clients cannot secure themselves. Dashboard endpoints under/api/* require a Clerk bearer token or an
API key. Rate-limited at 100 requests per minute per authenticated
user.