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
| Field | Type | Notes |
|---|---|---|
name | string | Required. 1–128 bytes. Names starting with $ are reserved for Maple’s SDKs and dropped, except $screen (mobile screen view, stored as Kind = screen). |
timestamp | string | RFC 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. |
source | string | server (default) or mobile. browser is reserved for the SDKs; other values drop the row. |
visitor_id | string | Anonymous/device id — the browser SDK cookie value or a persistent mobile install id. ≤ 256 bytes. |
user_id | string | Your user id after sign-in, matching what you pass to identify(). ≤ 256 bytes. |
group_id | string | Account / workspace / org id. ≤ 256 bytes. |
session_id | string | Optional link to a browser or mobile session. ≤ 256 bytes. |
service_name | string | The emitting service (maple-api, acme-ios). ≤ 128 bytes. |
url | string | Optional. host (lowercase) and page_path (pathname only) are derived from it. |
page_path | string | Optional explicit path; overrides the one derived from url. Mobile $screen events put the screen name here. |
attributes | object | Optional 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
| Status | Meaning |
|---|---|
200 | {"accepted": <n>} — rows durably queued. Malformed rows (bad name, source, timestamp) are dropped individually and not counted. |
400 | A line is not valid JSON, or not a JSON object. The whole batch is rejected. |
401 | Missing or invalid ingest key. |
402 | The organization is out of quota for product events (product_events is metered per event, separately from browser sessions). |
503 | Storage 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'