supapulsedocs
API referenceREST API

REST API

The ingest endpoint the SDKs use. Call it directly from any language or platform.

POST https://supapulse.app/api/ingest

JSON in, JSON out, rate limited to 2,000 requests per hour per IP address. To read your analytics back out, use the stats API or the MCP server.

Authentication

Send your token in the X-API-Token header, or as a token field in the body. The header wins if both are set. Which token you need depends on the event type.

TokenPrefixAllowed typesWhere it lives
Publicpub_pageview, event, identify, engagementBrowser. Requests with an Origin or Referer must come from the site's domain or a subdomain.
Secretsec_pageview, event, identify, engagement, revenueServer only. Never ship it to the client.

Request body

Every request has a type and a data object. The fields of data depend on the type. Fields not listed are ignored.

{
  "type": "pageview",
  "data": { "url": "https://acme.com/pricing", "session_id": "6f1c…" }
}

The body can be sent as application/json, or as text/plain containing JSON. The browser tracker uses text/plain with sendBeacon to avoid a CORS preflight.

pageview

FieldTypeRequiredDescription
urlstringYesFull URL of the page. Path and UTM parameters are read from it.
referrerstringNodocument.referrer. Its domain becomes the source.
session_idstringNoFrom getSessionId(). Up to 64 characters.
localestringNoBrowser language, like en-US.

The browser tracker also sends path, title, referrer_source and utm_*; the server derives these itself and doesn't use the values sent.

event

FieldTypeRequiredDescription
event_namestringYesUp to 255 characters, like signup.
urlstringYesPage the event happened on.
event_dataobjectNoProperties, stored as JSON.
session_idstringNoFrom getSessionId().

engagement

Scroll depth and engaged time for one page view. The tracker sends it when the visitor leaves the page, with cumulative values. Supapulse stores them on the latest pageview with the same session_id and path from the last 24 hours, keeping the highest values. It never counts toward usage.

FieldTypeRequiredDescription
urlstringYesURL of the page view. Its path must match the pageview.
session_idstringYesThe session ID sent with the pageview.
scroll_depthintegerYesDeepest point reached, 0 to 100 percent.
engaged_secondsintegerYesSeconds the page was visible and focused, up to 86400.

identify

Links a visitor to your customer ID so revenue can be attributed. Works with either token.

FieldTypeRequiredDescription
customer_idstringYesYour user or customer ID. Up to 255 characters.
session_idstringNoFrom getSessionId(). Up to 64 characters.
visitor_fingerprintstringNoUp to 64 characters. Secret token only; ignored with a public token.
source_customer_idstringNoUp to 255 characters. Secret token only. Copies the attribution stored for another customer ID.

With a public token the server builds the visitor fingerprint from the request's IP address and user agent. Identify events never count toward usage.

revenue (secret token)

FieldTypeRequiredDescription
customer_idstringYesYour customer key, up to 255 characters. The same ID you pass to identify().
amountnumberYesIn major units, like 49.90. From 0. Refunds are sent as positive and stored as negative.
transaction_idstringYesUnique per payment and event_type within a site. Repeats are ignored.
event_typestringNopurchase (default), subscription, renewal, upgrade, downgrade, churn or refund.
currencystringNoThree letters. Defaults to USD. Not converted.
visitor_fingerprintstringNoFrom getVisitorFingerprint(). Up to 64 characters.
session_idstringNoFrom getSessionId(). Up to 64 characters.
subscription_idstringNoYour subscription reference.
mrr_impactnumberNoChange in monthly recurring revenue.
is_recurringbooleanNoDefaults from event_type.
metadataobjectNoUp to 50 keys.
payment_processorstringNoFree text, like stripe or paddle. Defaults to stripe.
occurred_atdateNoISO 8601. Defaults to now. Up to a day in the future.

Every rule is listed in the Revenue API reference. Revenue is validated before it's queued, so a bad field returns 422.

The server builds the visitor hash for pageviews and events from the request's IP address and user agent. For browser traffic, send requests from the browser so those are the visitor's.

Example

curl -X POST https://supapulse.app/api/ingest \
  -H "X-API-Token: sec_9d…41" \
  -H "Content-Type: application/json" \
  -d '{
  "type": "revenue",
  "data": {
    "customer_id": "user_123",
    "amount": 49.90,
    "currency": "USD",
    "transaction_id": "in_1Q8x…",
    "event_type": "renewal",
    "payment_processor": "stripe"
  }
}'
202 Accepted
{
  "success": true
}

202 means the event passed the token, origin, plan and validation checks and was queued. A worker stores it shortly after. A transaction_id that repeats with the same event_type is ignored by the worker.

Other endpoints

POST /api/fingerprint

Returns the visitor fingerprint for the caller: the same hash Supapulse stores on their pageviews. Send the token as X-API-Token or a token body field. getVisitorFingerprint() calls it for you.

200 OK
{ "visitor_fingerprint": "9b74c9…" }

It answers 401, 403 and 402 like /api/ingest, and is never limited by the monthly event quota. It shares the rate limit.

POST /api/webhooks/stripe/{publicToken}

Receives a site's Stripe events. Stripe calls it, not your code. See the Stripe guide and the Revenue API reference.

Errors

Errors return JSON with an error message, or Laravel's validation format for 422. Retry 429 and 5xx with backoff; the browser tracker already does.

StatusErrorWhat to do
401Missing API tokenSend the X-API-Token header.
401Invalid token formatTokens start with pub_ or sec_. Check for stray spaces.
401Invalid API tokenThe token was regenerated or the site was deleted. Copy it again from the site's settings.
402This site's team has no active plan. with code: "plan_inactive"Start a plan or trial for the site's team.
402This team reached its monthly event limit. with code: "quota_exceeded"Wait for the monthly reset or upgrade. Never returned for revenue.
403Origin not allowedThe request came from a domain that doesn't match the site. Fix the domain in the site's settings.
422Validation failedA field broke a rule, or type isn't allowed for this token. The response lists each field.
429Too many requestsWait for the Retry-After header, then retry.

Details and fixes are in Error codes.

SDK compatibility

@supapulse/core sends pageview, event and identify. @supapulse/revenue sends revenue and works without a fingerprint or session ID.