Skip to main content

Events API

A read-only feed of recent notable events in the platform — message sent, message delivered, agent run, flow step, contact updated, conversation created, webhook endpoint updated, etc. Two access patterns:
  • PolledGET /api/v1/events pages over a bounded recent-window replay buffer (see below).
  • LiveGET /api/v1/events/stream opens an SSE connection that delivers events in real time.
This is not durable history. The Events API is a bounded, in-memory replay buffer (a per-tenant Redis Stream), not a system of record. It retains only the most recent ~5000 events per tenant, and the buffer expires after ~15 minutes of tenant inactivity. Older events are evicted and are gone — they cannot be paged back to. GET /api/v1/events/{id} resolves only ids still inside the current window; an evicted id returns 404. If the real-time store is briefly unavailable the surface degrades open — the list returns an empty page and a single-event lookup returns 404 rather than erroring.For a durable, complete, retry-handled record of every event — anything you’d build a system of record, audit log, or reconciliation job off of — use Webhooks instead. Do not treat this endpoint as an event log. If you do resolve event ids, handle the evicted-id 404 as a skip, not a failure — the single-event lookup sample below shows the shape.
Base path: /api/v1/events Authentication: API key (X-API-Key) or session JWT.

Using the SDKs

Returns the typed ApiResponse envelope. See the SDK index at SDK quickstart.

GET /api/v1/events response

The list endpoint returns pages of recent events inside the standard paginated envelope. Each data[] entry is one event envelope: the canonical stream id (pass it back as cursor to page forward, or as Last-Event-ID on the SSE stream to resume), the event type, the publish time ts (epoch milliseconds), and the type-specific data object.
meta.pagination.cursor is the id of the last event on the page. When has_more is true, pass it back as ?cursor= to fetch the next page; when it is null, you are at the head of the retained window.

GET /api/v1/events/{id} response

A single-event lookup returns the same event envelope under data, un-wrapped from data[]:

Live stream — SSE

Events arrive as data: lines (one JSON object per line). Reconnect on error with the Last-Event-ID header set so you don’t lose events. A consumer that just opens the stream and dies with it will miss every event emitted between the last delivered event and the reconnect. Track the last id you received and send it back as the Last-Event-ID header (or the ?lastEventId= query parameter, for clients like browser EventSource that can’t set headers) — the server replays the buffered window from that id before switching to the live tail.
Either pattern is enough to survive a dropped connection, but only if the last id is stored durably enough to survive a process restart. For anything you’d call a system of record, use Webhooks instead — the bounded-window warning above explains. The ?token= query parameter is promoted to X-API-Key server-side so browsers (which can’t set arbitrary headers on EventSource) can authenticate. As with every Orbit endpoint, query strings are stripped from access logs platform-wide before they’re written, so the token value is never persisted to log storage.

Schema registry — GET /api/v1/events/schema-registry

Before you build a pipeline off the event stream or webhooks, you need to know the shape of each event — and when that shape changes. The schema registry returns the versioned catalog of every event type Orbit emits.
Each entry in data.schemas carries:
  • event_type — the wire-level type, e.g. message.delivered.
  • json_schema — the Draft-07 JSON Schema for the event envelope, with the type literal pinned. Use it to validate incoming events or to generate types.
  • example_payload — a realistic example envelope that validates against json_schema.
  • fingerprint — a sha256:<hex> content fingerprint of json_schema, so you can pin one event’s schema and detect drift.
The response also includes a top-level catalog_version (a single sha256:<hex> over every (event_type, fingerprint) pair), an event_count, and the JSON Schema dialect.
Polling for drift. Store the catalog_version you last processed. Poll this endpoint on a schedule: while catalog_version is unchanged, nothing in the contract moved and you can skip the work. When it changes, compare each entry’s fingerprint against the one you stored to find exactly which event schemas changed, and regenerate only those types or validators. A Node generation step that does exactly that:
The same loop in Python:
The catalog is read-only, takes no query parameters, and is identical for every account — so you can cache it and check it cheaply.

Event type samples

Every event shares the same envelope — id, type, ts, and a data object whose shape is specific to the event type. The tabs below show the data shape for three common types. For the full catalog of every event type and its JSON Schema, see Webhook events, or fetch the machine-readable schema registry above.
A failed delivery replaces delivered_at with carrier detail in error_code / error_message.

Handling 404 on evicted ids

A single-event lookup (GET /api/v1/events/{id}) that returns 404 is not an error for a consumer pipeline — it means the id aged out of the bounded window described above. The body looks like this:
Treat it as a skipped event, not a failed request:
The same guard applies anywhere you resolve ids you haven’t verified inside the current window. If the resolve path is the critical path of what you rebuild — don’t rebuild from this feed; subscribe to Webhooks instead.

Ingesting events — POST /api/v1/events/track

To record your own product events (rather than read the feed above), POST to the ingestion endpoint with an event name, an optional contact identifier (contact_email or contact_phone — omit both to store the event without linking it to a contact), and an optional properties object:
properties payload ceiling — 1 MiB. The serialized properties JSON for a single event must not exceed 1 MiB (1,048,576 bytes). An event over the ceiling is rejected with HTTP 413 and error.code = EVENT_PAYLOAD_TOO_LARGE — nothing is stored and the contact timeline is unchanged. Keep individual attributes small: store large blobs (raw documents, base64 media, full request dumps) in your own object storage and reference them from properties by URL or id rather than embedding them inline.

See also

  • Webhooks — for delivered, durable, retry-handled push of the same events