Error codes
Every error the Supapulse ingest API returns, what causes it, and how to fix it.
Format
Most errors are JSON with one error field:
{ "error": "Invalid API token" }Validation errors (422) use Laravel's format:
{
"message": "The selected type is invalid.",
"errors": {
"type": ["The selected type is invalid."]
}
}Status codes
| Status | Error | Cause | Fix |
|---|---|---|---|
202 | none | Accepted and queued. | Nothing. The body is { "success": true }. |
401 | Missing API token | No X-API-Token header and no token field. | Send the token. |
401 | Invalid token format | The token doesn't start with pub_ or sec_. | Check for quotes, spaces or a truncated value. |
401 | Invalid API token | No active site has this token. It was regenerated, mistyped, or the site was deleted. | Copy the token again from the site's settings. |
402 | This site's team has no active plan. | The site's team has no active plan or trial. The body also has "code": "plan_inactive". | Start a plan or trial. |
402 | This team reached its monthly event limit. | The team used more than its plan's monthly events plus a 10% grace. The body has "code": "quota_exceeded". Not returned for revenue. | Upgrade, or wait for the monthly reset. |
403 | Origin not allowed | The Origin (or Referer) host isn't the site's domain or a subdomain of it. | Fix the domain in the site's settings, or send from the right domain. |
422 | Validation errors | type missing or not allowed for the token, data missing or not an object, or a revenue or identify field broke a rule. | See below. |
429 | Too Many Attempts | More than 2,000 requests in an hour from one IP address. | Wait for Retry-After. |
5xx | A server problem. | Retry with backoff. |
402 responses
A 402 has two fields, error and code:
{ "error": "This site's team has no active plan.", "code": "plan_inactive" }code | Meaning |
|---|---|
plan_inactive | The team's trial ended or it has no plan. Statuses that can still ingest: active, trial, past due, unpaid and grace period. |
quota_exceeded | The team's monthly event count reached its plan limit plus 10%. Pageviews, events and identify are rejected until the count resets or the plan grows. |
Revenue is never rejected for quota, only for plan_inactive. POST /api/fingerprint checks the plan only. The Stripe webhook answers 200 with { "ignored": "plan_inactive" } instead of 402. The SDKs treat 402 as final and don't retry it. Plan checks are cached for up to a minute, so a change takes about that long to apply.
Validation
| Field | Rule |
|---|---|
type | Required. pageview, event or identify with a public token; those plus revenue with a secret token. |
data | Required. An object. |
Sending revenue with a public token fails here with 422, because revenue isn't an allowed type for it.
Revenue
| Field | Rule |
|---|---|
data.customer_id | Required string, up to 255 characters. |
data.amount | Required number from 0 to 99999999.99. |
data.transaction_id | Required string, up to 255 characters. |
data.event_type | One of purchase, subscription, renewal, upgrade, downgrade, churn, refund. |
data.currency | Three letters. |
data.visitor_fingerprint, data.session_id | Strings, up to 64 characters. |
data.subscription_id | String, up to 255 characters. |
data.mrr_impact | Number from -99999999.99 to 99999999.99. |
data.is_recurring | Boolean. |
data.metadata | Object with up to 50 keys. |
data.payment_processor | String, up to 32 characters. |
data.occurred_at | A date, not more than a day in the future. |
Identify
| Field | Rule |
|---|---|
data.customer_id | Required string, up to 255 characters. |
data.session_id, data.visitor_fingerprint | Strings, up to 64 characters. |
data.source_customer_id | String, up to 255 characters. |
A 422 response names each failing field in errors.
Errors after the 202
Revenue and identify are validated before they're queued. Pageviews and events aren't, and the worker needs more than the API checks. It drops the event silently if this is missing:
| Type | Needs |
|---|---|
pageview | data.url |
event | data.event_name and data.url |
The browser tracker always sends what pageviews and events need. A revenue event with the same transaction_id and event_type as an earlier one is ignored, not an error.
Checklist
- Is the token the right kind? Public for the browser, secret for revenue.
- Is the site's domain set to the domain the requests come from?
- Does the request have
typeand adataobject? - For revenue: are
customer_id,amountandtransaction_idset, and istransaction_idnew for that event type? - A
402: does the site's team have an active plan, and is it under its monthly limit? - Still stuck? See Debugging.