Sitelet https://maple.dev/docs/product-events/api/
Skip to content
Maple Docs
Open app
Browse the docs
On this page

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 is posted directly to the ingest gateway: a signup_completed from a webhook handler, a plan_started from your billing worker, a screen view from a native app. 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 YOUR_INGEST_KEY        # or X-Maple-Ingest-Key: YOUR_INGEST_KEY
Content-Type: application/x-ndjson

EU organizations use https://ingest.eu.maple.dev. 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 or 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 metered as their own unit, one per accepted row. Browser track() calls count in the same unit.

Example

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

Verify

Send the example above. A 200 with {"accepted": 1} means the row was queued. Within a minute, the event is available in dashboard charts that use Product events as their data source. {"accepted": 0} means the row was dropped: check name, source and timestamp against the table above.