Authentication
Every request carries a bearer token. Server keys (sk_live_…) can write events and read data; public keys (pk_live_…) can only write events and are safe in client code. Keys are scoped to one project and can be rotated without downtime — the old key keeps working for one hour.
Authorization: Bearer sk_live_2f7a9c41d8e60b3f Content-Type: application/json Cadence-Version: 2026-05-12
Ingest
Records a single event. Returns 202 as soon as the event is durably queued — typically visible in queries within one second.
Body
eventstring | Event name in object_verb form.required |
user_idstring | Your identifier for the user. One of user_id or anonymous_id is required. |
anonymous_idstring | Pre-signup identifier. Stitched onto the user on the next identify call. |
propertiesobject | Flat map of scalars. Nested objects are flattened one level. |
occurred_atISO 8601 | Defaults to receipt time. Accepted up to 48 hours in the past. |
idempotency_keystring | Deduplicated for 48 hours. Safe to retry. |
curl https://api.cadence.dev/v1/events \ -H "Authorization: Bearer sk_live_…" \ -H "Content-Type: application/json" \ -d '{ "event": "checkout_completed", "user_id": "u_1842", "properties": { "plan": "business", "seats": 12, "amount_cents": 18300 }, "idempotency_key": "chk_9f31c0" }'
HTTP/1.1 202 Accepted { "id": "evt_01J8QK4RZ2M7XW", "status": "queued", "received_at": "2026-08-11T09:42:18.204Z" }
Up to 1,000 events or identify calls in one request, 5 MB maximum. Partial success is possible — the response lists the index and reason for anything rejected, and everything else is still accepted.
curl https://api.cadence.dev/v1/batch \ -H "Authorization: Bearer sk_live_…" \ -d '{ "events": [ { "event": "board_created", "user_id": "u_1842" }, { "event": "invite_sent", "user_id": "u_1842" }, { "event": "board_shared", "user_id": "u_2011" } ] }'
HTTP/1.1 207 Multi-Status { "accepted": 2, "rejected": [ { "index": 2, "reason": "unknown_user" } ] }
Attaches traits to a user and stitches any anonymous history onto them. Traits are merged, not replaced — send only what changed. Pass null to unset a trait.
user_idstring | Your identifier for the user.required |
anonymous_idstring | If present, all events under this ID are re-attributed retroactively. |
traitsobject | Flat map. Values matching PII patterns are hashed at the edge. |
Read
Runs a saved funnel and returns per-step counts. Results are computed live — there is no cache to invalidate — so expect 0.6–2.0s depending on volume.
from, toISO 8601 date | Window. Defaults to the funnel's saved range. |
breakdownstring | Property or trait to split by, e.g. plan. |
window_daysinteger | Conversion window, 1–90. Defaults to 30. |
{
"funnel_id": "fnl_activation",
"window_days": 30,
"steps": [
{ "name": "visited_site", "users": 128400, "rate": 1.000 },
{ "name": "account_created", "users": 41220, "rate": 0.321 },
{ "name": "activated", "users": 18940, "rate": 0.147 }
],
"computed_in_ms": 1140
} Lists saved cohorts with their current size. Add ?include=members to page through user IDs, 10,000 per page via the cursor parameter.
Queues a raw event export as Parquet or NDJSON and returns a job. Poll /v1/exports/{id} for a signed download URL, valid for 24 hours. Available on every plan, including Free.
Rate limits
Limits are per project, measured over a sliding minute. Every response carries X-RateLimit-Remaining and X-RateLimit-Reset. On 429, honour the Retry-After header — the SDKs do this for you.
| Endpoint group | Free | Team | Business | Enterprise |
|---|---|---|---|---|
| Ingest (events, batch, identify) | 600 / min | 6,000 / min | 60,000 / min | Negotiated |
| Read (funnels, cohorts) | 20 / min | 120 / min | 600 / min | Negotiated |
| Exports | 2 / day | 20 / day | 200 / day | Unlimited |
Errors
Errors are JSON with a stable machine-readable code. Never parse the message — it is written for humans and we reword it.
| Status | Code | What to do |
|---|---|---|
| 400 | invalid_event_name | Name is empty, over 128 chars, or not UTF-8 |
| 401 | invalid_key | Key is wrong, revoked, or from another project |
| 403 | insufficient_scope | Read endpoint called with a pk_ key |
| 413 | payload_too_large | Split the batch; the limit is 5 MB |
| 422 | property_quarantined | A value looked like PII and was dropped — the event was still recorded |
| 429 | rate_limited | Back off for Retry-After seconds |
| 503 | region_failover | Retry with backoff; ingest is queued, nothing is lost |