Skip to main content
Ingests events from SDKs and custom integrations. Events are processed for real-time aggregation and stored for optimization.

Request

Headers

Body

An events array (maximum 1000 events per request):
Tenant fields are derived from the API key: the organization always comes from the key (an orgId in the payload is ignored if it differs), and projectId comes from the key when the key is project-scoped. Only org-scoped keys need to set projectId on each event. Events are validated individually. An invalid event is skipped and reported in validationErrors — it never fails the rest of the batch.

Event schemas

Exposure event

Tracked automatically by SDKs when parameters are resolved. LayerAssignment:

Track event

Custom events sent by your code for analytics and optimization. If the project defines a property schema for an event, the payload is validated against it. In warn mode the event is still stored and the violations appear in schemaWarnings; in reject mode the event is dropped and reported in validationErrors.

Decision event

Recorded automatically when a decision is made — by POST /v1/resolve and by server-mode SDKs. You don’t normally send these yourself.

Response

200 OK

An empty events array returns { "accepted": 0 }.

400 Bad request

Also returned for an invalid JSON body (Invalid JSON body) and a missing events array (Missing events array).

401 Unauthorized

403 Forbidden

Sending events requires the sdk:write scope. A key without it — for example, a read-only key — gets a structured error, not a bare 403:

503 Events not stored

Returned when event storage is temporarily unavailable and nothing from the batch was stored. The body sets retryable: true and the response carries a Retry-After header (currently 2 seconds) — resend the same batch; since nothing was stored, a retry cannot duplicate events.

Batch

Identical to POST /v1/events — same request body, same response, same authentication, same 1000-event limit. Browser SDKs post their queued events to this path, including a final keepalive request during page unload, so events from a closing tab still arrive with the same delivery guarantee as navigator.sendBeacon (but with an Authorization header, which sendBeacon itself cannot send). Use whichever path you prefer in custom integrations; always batch events rather than sending one request per event.