1.7K App
Session Replay / Product events API

Product events API

Post product events from a backend or mobile app to POST /v1/events on the Maple ingest gateway — the raw NDJSON contract every server-side track() call uses.

Browser page views and track() calls reach Maple through the session SDKs. Everything else — a signup_completed from a webhook handler, a plan_started from your billing worker, a screen view from a native app — is posted directly to the ingest gateway. Rows land in the same product_events table as the browser events, so one funnel can span the marketing site, the app, and the backend.

Endpoint

POST https://ingest.maple.dev/v1/events
Authorization: Bearer <ingest key>        # or X-Maple-Ingest-Key: <ingest key>
Content-Type: application/x-ndjson

The body is NDJSON: one JSON object per line, any number of lines. The organization is resolved from the ingest key — an org_id in the body is ignored.

{"name":"signup_completed","user_id":"user_01H…","service_name":"maple-api"}
{"name":"plan_started","user_id":"user_01H…","group_id":"org_01H…","attributes":{"plan":"startup"}}
{"name":"$screen","source":"mobile","visitor_id":"install-8f3…","page_path":"Checkout"}

Fields

FieldTypeNotes
namestringRequired. 1–128 bytes. Names starting with $ are reserved for Maple’s SDKs and dropped, except $screen (mobile screen view, stored as Kind = screen).
timestampstringRFC 3339 (2026-08-17T10:15:30.123Z) or YYYY-MM-DD HH:MM:SS[.fff] (UTC). Defaults to the time the gateway received the batch. Stored as UTC.
sourcestringserver (default) or mobile. browser is reserved for the SDKs; other values drop the row.
visitor_idstringAnonymous/device id — the browser SDK cookie value or a persistent mobile install id. ≤ 256 bytes.
user_idstringYour user id after sign-in, matching what you pass to identify(). ≤ 256 bytes.
group_idstringAccount / workspace / org id. ≤ 256 bytes.
session_idstringOptional link to a browser or mobile session. ≤ 256 bytes.
service_namestringThe emitting service (maple-api, acme-ios). ≤ 128 bytes.
urlstringOptional. host (lowercase) and page_path (pathname only) are derived from it.
page_pathstringOptional explicit path; overrides the one derived from url. Mobile $screen events put the screen name here.
attributesobjectOptional properties. ≤ 32 keys, key ≤ 64 bytes, value ≤ 1024 bytes; non-string values are stringified.

Over-long strings are truncated at the caps above; unknown fields are discarded.

Responses

StatusMeaning
200{"accepted": <n>} — rows durably queued. Malformed rows (bad name, source, timestamp) are dropped individually and not counted.
400A line is not valid JSON, or not a JSON object. The whole batch is rejected.
401Missing or invalid ingest key.
402The organization is out of quota for product events (product_events is metered per event, separately from browser sessions).
503Storage temporarily unavailable — retry with backoff.

Product events are not metered separately: they are covered by the browser-sessions entitlement.

Example

curl -X POST https://ingest.maple.dev/v1/events \
  -H "Authorization: Bearer $MAPLE_INGEST_KEY" \
  -H "Content-Type: application/x-ndjson" \
  --data-binary $'{"name":"plan_started","user_id":"user_123","attributes":{"plan":"startup"}}\n'