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.11. 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
| Metric | Definition |
|---|---|
| Visitors | Distinct daily, website-specific estimates. No recognition across days or devices. Shared networks and changing IPs affect counts. |
| Visits | A new visit after at least 30 minutes without a pageview or custom event. Daily identifier rotation also starts a new visit. |
| Bounces | Visits with at most one pageview and no interactive custom event. Non-interactive and engagement events do not remove a bounce. |
| Visit duration | Time between first and last non-engagement event. Single-event visits contribute zero. Divide durationSeconds by visits. |
| Conversion rate | Visitors matching the goal divided by all visitors under the selected filters. |
| Revenue | Integer minor units grouped by currency; no conversion or cross-currency sum. |
| Time and scroll | Active 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.