โ† Crumbs

DEVELOPER REFERENCE ยท V1

Analytics you can build on.

The dashboard and integrations use the same backend. The REST API is served by the backend canister (or an optional Node collector); the canister also exposes generated Candid bindings.

Download OpenAPI 3.1

1. Send an event

Install the browser tracker or post events directly. Browser collection has no secret key. A stable random event ID prevents retries from being counted twice.

curl https://analytics.example.com/api/v1/events \
  -H 'Content-Type: application/json' -A 'CrumbsExample/1.0' \
  -d '{"id":"96887746-95ba-4c33-a3dd-ab07d96562aa","site":"main","url":"https://example.com/pricing","kind":"pageview"}'

Native 202 responses use durability: "canister": accepted events are already committed. They contain numeric accepted, duplicates and ignored counts. The optional Node mode instead responds with durability: "collector" and a queued count; canister delivery then happens asynchronously. Monitor /api/v1/collector-health. Transport retries retain the original event ID. Automation user-agents are excluded; configure an appropriate application user-agent for server-side events. Server-originated events describe the server. The gateway supplies X-Real-IP; callers must not set it. Direct Candid callers can forge headers, so visitor counts are not authenticated identities.

Replace the example origin with the Collection API URL from Website settings, normally https://BACKEND_CANISTER_ID.icp.net. The frontend serves this documentation and the tracking script. Native mode has no GeoIP lookup. Keep one collection mode per website.

2. Query a report

Create a read key in Crumbs โ†’ API & exports. Keys are limited to one website and expire. Send credentials only in the Authorization header.

curl https://analytics.example.com/api/v1/query \
  -H "Authorization: Bearer $CRUMBS_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"site":"main","from":1789776000,"until":1789862400,"dimension":"source","limit":20}'

from is inclusive and until exclusive, in Unix seconds. Reports accept intervals up to 366 days. Candid-derived integers in report/resource responses are decimal strings; collection acknowledgment and collector-health counters are JSON numbers. Empty traffic produces zero metrics; oversized scans return an error rather than sampled totals.

3. Filter and group

{
  "site": "main", "from": 1789776000, "until": 1789862400,
  "dimension": "path",
  "filters": [{"dimension":"country","values":["CH","DE"],"exclude":false}],
  "limit": 100
}

Filters are ANDed; values within a filter are ORed. Dimensions include path, source, medium, campaign, content, term, country, region, city, device, browser, os, hostname, entryPath, exitPath, event, day, hour and explicitly allowed prop:NAME fields.

Metrics and counting rules

MetricDefinition
VisitorsDistinct daily, website-specific estimates. No recognition across days or devices. Shared networks and changing IPs affect counts.
VisitsA new visit after at least 30 minutes without a pageview or custom event. Daily identifier rotation also starts a new visit.
BouncesVisits with at most one pageview and no interactive custom event. Non-interactive and engagement events do not remove a bounce.
Visit durationTime between first and last non-engagement event. Single-event visits contribute zero. Divide durationSeconds by visits.
Conversion rateVisitors matching the goal divided by all visitors under the selected filters.
RevenueInteger minor units grouped by currency; no conversion or cross-currency sum.
Time and scrollActive time from engagement events. Scroll reports the maximum per visit/page, averaged over available samples.

Day/hour buckets are UTC. The interval limits event inclusion; session context is loaded from the beginning of its UTC day. Filtered page reports count matching events but use the full loaded visit for bounce and duration. These choices are explicit and require comparison with your existing Plausible reporting before switching.

Access and operations

All read/manage/share keys stay bound to their issuer's current website management rights and fail closed when the Hub directory is stale. Demotion deletes their keys, including shared links. Manage keys can update site settings, content and exports. Website membership, Hub user search and key creation/revocation require a signed-in website Manager/Admin. Creating or deleting websites remains restricted to app-wide Admins. GET /api/v1/sites includes your effective accessRole. Read the current revision from GET /api/v1/sites/{id}/access, then PUT the revision and named readers/managers to change membership. GET /api/v1/sites/{id}/people?search=... searches Hub names/emails. Use stable person IDs returned by that endpoint; leave the deprecated Site.viewers input empty.

HTTP errors: 400 invalid request, 401 missing credential, 403 denied/expired/stale access, 404 missing resource, 409 conflicting ID, 422 capacity/report boundary, 429 throttle, 503 unavailable. Honor Retry-After; retry events with identical IDs. Never log Authorization headers.

Import Plausible history

Ask your IT operator to export the CSV files from Plausible and run the importer from the Crumbs source distribution. Start with a validation-only run:

node crumbs/tools/import-plausible.mjs --dir /path/to/csv-export --site WEBSITE_ID --timezone UTC

After reviewing the validation, set CRUMBS_URL to your Collection API URL and CRUMBS_API_TOKEN to a Manage key, then repeat with --send. Match the export timezone to UTC. Imported history remains separate from live reports. See the operator guide for supported tables and retention requirements.

Endpoint reference