Skip to main content

Events API

Behavioral event ingestion and identity stitching. Authenticates with either credential tier — see the Server-Side Event Tracking guide for which key to use where. The machine-readable contract lives at docs/api/events-openapi.yaml.

POST /v1/events

Ingests a single event (bare object) or a batch ({"events": [...]}). Returns 202 once events are durably stored; segmentation runs asynchronously.

Event fields

FieldTypeNotes
event_namestringRequired, 1–128 chars
subscriber_idstringRegistry subscriber UUID
anonymous_idstringPre-identify actor
external_idstringServer tier only — your own user id, resolved via the registry; unknown → 422
platformstringFree-form; server SDKs default to server
propertiesobject≤ 50 keys; keys and string values ≤ 256 chars
occurred_atRFC3339Defaults to receipt time; window: 48 h (client) / 90 d (server)
idempotency_keystringUnique per tenant; generated when omitted

Exactly one actor identifier is required per event. The trust tier is recorded as source from the credential; a source field in the payload is ignored.

Limits

Client (X-SDK-Key)Server (Bearer hober_srv_…)
Events per request501000
Request body500 KB (413 beyond)
Per event32 KB
occurred_at window48 h90 days

Responses

StatusMeaning
202{ "accepted": N } — events stored
401Missing, malformed, or revoked credential
403Server key without the events:write scope
413Server-tier body over 500 KB
422Validation failure (batch caps, backdating window, unknown external_id, external_id on client tier, missing actor, property limits)

GET /v1/events/export

Bulk export of the tenant's behavioral event history (the data-portability policy's self-serve path). Server key only — client SDK keys are publishable and cannot read history.

Query parameters

ParamTypeNotes
from / toRFC 3339Time window on occurred_at; defaults to all history up to now
cursorstringOpaque keyset cursor from the previous page
limitint1..1000 (default 1000)

Response

200 with the cursor envelope — next_cursor is empty when the page is not full:

{
"items": [
{
"id": "…",
"subscriber_id": "…",
"anonymous_id": null,
"event_name": "order_placed",
"platform": "",
"properties": { "total_price": "19.99" },
"occurred_at": "2026-07-01T12:00:00Z",
"received_at": "2026-07-01T12:00:01Z",
"source": "server"
}
],
"next_cursor": ""
}

401 — missing/invalid credential. 403 — client-tier credential. 422 — malformed window, limit, or cursor.

Rows ordered by (occurred_at, id); pages are stable under concurrent ingestion. Erased subscribers' rows appear with both actor ids null (GDPR erasure anonymizes, it does not delete aggregates).

POST /v1/events/identify

Links an anonymous_id to a subscriber_id and backfills the subscriber onto previously stored anonymous events.

Request

{ "anonymous_id": "anon-9f2c", "subscriber_id": "8a2f6c1e-…" }

Response

{ "linked": true, "backfilled": 12 }