← 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 supports operator-loaded, expiring GeoIP/ASN/hosting datasets without per-visitor external lookups. 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 1827 days. Native REST uses atomic full-report update execution, covering the retained event store rather than the 50,000-event fast-query cap. Direct Candid clients can retry report, goalRows, goalReport, funnel and journeys using the matching *Full update method. Request and response types and authorization are identical; no daily-unique summing or sampling is used. 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 asn, network, screen, trafficClass, geoSource, networkSource, hostingSource, trackingMode, channel, aiSource, minute, path, source, medium, campaign, content, term, country, region, city, device, browser, os, hostname, entryPath, exitPath, event, day, hour and explicitly allowed prop:NAME fields.

Cookie and cookieless data in the API

Compare collection modes. All report, goal, funnel, journey and export endpoints include both modes subject to website access. Group or filter using trackingMode: cookie and cookieless.

{
  "site":"main", "from":1789776000, "until":1789862400,
  "dimension":"trackingMode", "limit":100
}

To query only cookie traffic, use "filters":[{"dimension":"trackingMode","values":["cookie"],"exclude":false}] with any supported breakdown. Existing events remain cookieless. Do not sum daily cookie visitor rows to calculate unique browsers across days; query totals for the complete interval.

Custom collectors may send cookieId (random UUID) and cookieConsent:true only after affirmative consent. Prefer the tracker’s lifecycle handling. Consent flags are not proof of consent and do not grant report access.

GET /api/v1/export paginates normalized events with a Manage key (maximum 500 per page). The visitor field is a pseudonym: c: plus 64 hex characters for cookie mode, 64 hex characters for cookieless. Raw cookie IDs, IP addresses, User-Agent strings, original URL queries and consent evidence are not export fields. Device/browser fields are coarse derived families. No new/returning cohort, personal identity, heatmap or replay endpoint is implemented.

REST covers collection, aggregate reports, goals, funnels, journeys, normalized export, imports, annotations, site access and key operations. Saved report configuration, source-attributed goal rows and Search Console configuration/snapshots currently use Candid methods in the dashboard; they are not REST routes advertised by this OpenAPI document.

Metrics and counting rules

MetricDefinition
VisitorsDistinct site-specific estimates: cookieless IDs rotate daily; consented cookie IDs recognize one browser across days until expiry/deletion. Neither identifies people or links devices.
VisitsA new visit after at least 30 minutes without a pageview or custom event. Both modes split visits at UTC midnight.
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.

Daily buckets use supplied local-midnight dayStarts, otherwise UTC. Hour/minute buckets use 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.

Traffic context

Normalized event exports add nullable context with screen, automation, asn, network, hosting and dataset source fields. Old events have null context. Hosting and browser automation are evidence, not proof of a bot; unmatched traffic is not proven human. Raw addresses are never exported. Dataset administration is controller-only Candid; trafficDatasets() reports source and expiry. Operation and migration guide.

Import Matomo history

Daily Matomo aggregate reports can be preserved with their original metrics and timezone. Run export-matomo.mjs, validate with import-matomo.mjs, then use a Manage key to import. History uses calendar date labels; it stays separate from native totals. See the Matomo operator guide for limits and supported reports.

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

Bundled dashboard reports (Candid)

reportBundle and reportBundleFull accept the existing token and report request plus an ordered array of (dimension, row limit) pairs. They return the same ordered Report records, sharing one event/session preparation and exact whole-period totals. Limits: 1–8 dimensions, 1–1,000 rows each, 2,000 rows total. Access is checked on every call; no persistent result cache. The Full method is an update calculation for query capacity/instruction errors. These endpoints are Candid-only; REST query and SDK response contracts are unchanged.

Bundles are bounded to 200,000 scanned-event × dimension units before aggregation. On this work limit or a full-bundle instruction limit, the browser client splits dimensions, not time ranges, and displays results only after all groups succeed. Each group is an authorized snapshot; collection may continue between groups.