Collection API
The public endpoints the SDK and the hook use. You rarely call them yourself, but they are stable, small and documented here in full.
#Conventions
- Request bodies are JSON objects of at most 16 KB, sent with
Content-Type: application/json. - Errors answer
{"error":"code"}with an HTTP status. See Error codes. - Browsers may call the
/v1/endpoints only from a website's registered origins: its primary origin, additional domains and registered pages. Credentials are never sent (credentials: 'omit'). - Requests are rate limited. See Limits.
- When a Free organisation has used its monthly quota, visits and installs still get their normal answer, with
"recorded": false, and nothing is stored. - Every request is recorded in Logs with its exact body, rejected ones included; receipts and tokens appear there only as keyed hashes.
| Endpoint | Called by | Purpose |
|---|---|---|
GET /browser.js | Website | The SDK. |
GET /v1/ | SDK | GA4 measurement IDs and providers. |
POST /v1/visits | SDK | Records a visit and issues a receipt. |
POST /v1/visits/activate | SDK | Confirms the marker was written. |
POST /v1/handoffs | SDK | Issues a handoff token. |
GET /v1/ | Installer | An install path’s hook, with the path ID written in. A path the website’s organisation does not have answers 404 install_path_not_found. |
GET /v1/ | Installer | The website’s hook without a path ID; its installs count toward the first install path. |
POST /v1/events | Hook | Reports an install. |
GET /report.sh | Anyone | The hook's source as plain text, for review. |
GET /api/limits | Anyone | Every plan’s limits, the prices and the service limits as JSON, cacheable for 5 minutes. See Limits. |
GET /skills/ | Coding agents | The setup skill: SKILL.md, its references, manifest.json and install.sh. |
GET /health | Monitoring | Returns {"ok":true,: delivery is live once any destination sends live. |
#GET /v1/projects/{website}/browser-config
{
"measurementIds": ["G-XXXXXXXXXX"],
"providers": ["ga4", "google_ads", "meta", "tiktok", "x"]
}
The measurement IDs of the website's GA4 destinations, and its providers setting.
#POST /v1/visits
Must come from a registered origin; the page URL must be on that origin.
| Field | Type | Description |
|---|---|---|
project | string | Website ID. |
url_id | string | url_ plus the first 16 hex characters of SHA-256(origin + path), or a registered page ID. |
page_url | string | The page's URL. Stored as origin and path. |
referrer | string | document.referrer. Stored as origin and path. |
identities | object | { ga4: { "G-…": { client_id, session_id } }, google_ads: { gclid, wbraid, gbraid, gcl_aw, session_attributes }, meta: { fbp, fbc, fbclid }, tiktok: { ttp, ttclid, ttclid_at }, x: { twclid, twclid_at }, custom: { … } }. Google Ads, Meta, TikTok and X fields for providers the website does not use are dropped; GA4 IDs are kept whenever sent. |
previous_receipt | string or null | The receipt this page's marker held before. It is revoked when the new one is activated. |
source | object | { referrer, utm: { source, medium, campaign, term, content, id }, landed_at } |
gpc | boolean | true when the browser sends Global Privacy Control. |
client | object | Coarse hints: the user-agent client hints, architecture, bitness, brave, touch, timezone, language. See Browsers and systems. |
{
"marker": {
"format": "opfs-install-attribution",
"version": 1,
"project": "p_04de14934e8005ed97dd",
"url_id": "url_96f86d390b35cfa7",
"origin": "https://acme.dev",
"receipt": "5GodFohsqck6lAXWFydmiZTBXvtQnU2WzbpsB8tq-_k",
"created_at": 1790171920120,
"expires_at": 1792763920120
}
}
#POST /v1/visits/activate
Body { "project", "receipt" }, from the same origin as the visit. Answers {"ok":true}. Until activated, a receipt never matches.
#POST /v1/handoffs
Either { "project", "receipt" } for a visit that already has a receipt, or the fields of /v1/visits (without previous_receipt) to record a visit that exists only for the handoff. Both accept gpc. For a handoff-only visit the answer also carries its receipt, which the SDK keeps in memory to link further installation scopes of the same page view; such a receipt can never be activated or matched as a browser receipt.
{
"ok": true,
"token": "hV0c3t8G2mIqzW1b6Xy9kQ4rLpE7uN5sJ0aTfDgC2vY",
"expires_at": 1792763920120
}
#POST /v1/events
Called by the hook, never by a browser: a request with an Origin header is refused with native_endpoint.
| Field | Type | Description |
|---|---|---|
project | string | Website ID. |
event_id | string | 8 to 128 letters, digits, _ or -. Idempotency key. |
installation_id | string | 8 to 128 letters, digits, _ or -. |
event_type | string | install_started, install_completed or first_run. |
receipts | string[] | Up to 64 receipts of 43 characters. |
handoff | string | Optional handoff token. receipts must then be empty. |
platform | string | Optional: darwin or linux. |
install_path | string | Optional: the install path ID. Missing or unknown: the organisation’s first path. |
arch, browsers, default_browser, timezone, language | string, string[] | Optional facts about the machine; see What it sends. |
{
"ok": true,
"event_id": "6f2da9610246dec311f2b793f7c32902",
"duplicate": false,
"resolution": "matched",
"candidate_count": 1,
"selection_policy": "latest_visit",
"ga": {
"status": "pending",
"deliveries": [{
"destination": "d_2f7c7e7eac2f8f79c5e2",
"status": "pending",
"session_mode": "original_session_window"
}]
}
}
On Free, and for a paused install path, resolution is counted: receipts and tokens are not read. When Free has used its monthly quota, the answer is the same shape with "recorded": false, and nothing is stored. The response never contains identifiers or referring pages. ga is kept for older clients: it lists the delivery queued for each destination, with the first one's status as status (not_queued when there is none).
#Dashboard API
The dashboard uses JSON endpoints under /api/, authenticated by the session cookie and accepted only from the service's own origin. They serve the dashboard and may change; build integrations on webhooks and the endpoints above.