API and app error codes
Every error the API or the app can answer with, what each code means and what to do about it.
Every error from the dashboard API, the public REST API and the app's own forms has the same shape: an HTTP status, a stable code, a human-readable message, an optional details list, and a requestId you can quote to support.
{
"error": {
"code": "VALIDATION_FAILED",
"message": "Enter a full http(s):// URL.",
"details": [{ "path": "destinationUrl", "message": "Enter a full http(s):// URL." }],
"requestId": "1d929651-566f-41e8-9b61-763956dc5065"
}
}Request problems (4xx)
- BAD_REQUEST (400): malformed JSON or a body over 256 KB. Send valid JSON with
Content-Type: application/json. - VALIDATION_FAILED (422): a field is invalid;
detailslistspathandmessagefor each one. Fix the listed fields and resend. - LIMIT_EXCEEDED (422): a plan limit was reached (sites, seats, funnels, uploads).
details[0]carrieslimit,maxandcurrent. Remove something or move to a bigger plan. - CONFLICT (409): the change clashes with the current state: duplicate slug, a role still in use, an invoice already paid. Reload and apply the change again.
- DOMAIN_ALREADY_CLAIMED (409): another organization has verified that hostname. Use a different hostname or ask support.
- NOT_FOUND (404): unknown id, or something outside your organization or the API key's sites. Customer360 never answers 403 for other tenants' data, so this is also what you get for an id that belongs to someone else.
Who you are (401, 403, 423)
- UNAUTHENTICATED (401): no session, or a missing, invalid, expired or revoked API key. Send
Authorization: Bearer exk_…with a live key, or sign in again. - TWO_FACTOR_REQUIRED (401): the sign-in has not finished its 2FA step. Complete the challenge on the two-factor page.
- SESSION_EXPIRED (401): the session passed its idle limit (7 days by default), its 30-day absolute limit, or was revoked from Account security. Sign in again.
- EMAIL_NOT_VERIFIED (403): the account's email is not verified yet. Open the verification email or request a new code.
- FORBIDDEN (403): the user or key lacks the permission, or a browser call came from a foreign origin (CSRF check). Pick a key with the right permission; browser calls must come from the app itself.
- STEP_UP_REQUIRED (403): a sensitive change (exports, API keys, Product Admin writes) needs a 2FA confirmation from the last 15 minutes. Confirm the code and retry.
- FEATURE_UNAVAILABLE (403): the feature is not in your plan or is switched off platform-wide;
details[0].messagenames the feature. Upgrade, or ask us to enable it. - LICENCE_INACTIVE (403): the organization's licence is suspended or cancelled. Tracking keeps accepting hits; reports and changes wait until the licence is active again.
- ACCOUNT_LOCKED (423): too many failed sign-ins. The lock lifts on its own (the email says when) or you can reset the password.
Load and availability (429, 5xx)
- RATE_LIMITED (429): too many requests for this key, user, email or IP. Wait for the seconds in the
Retry-Afterheader;X-RateLimit-Limittells you the window's allowance. API keys get 300 requests per minute, 600 per minute per IP; sign-in, password reset and the contact form have much tighter limits. - UPSTREAM_UNAVAILABLE (503): the database was unreachable for that request. Nothing was changed; retry after
Retry-After. - BYO_DB_UNREACHABLE (503): your own MongoDB (bring-your-own storage) is down, so analytics reads fail until it is back. Check it under Workspace → Data storage.
- MAINTENANCE (503): a planned maintenance window; see the status page for the end time.
- INTERNAL (500): something unexpected. Retry once; if it repeats, send us the
requestId.
Tracking endpoints
The public tracker endpoints (/v1/collect, /v1/consent, widgets) use the same envelope but never block a visitor's page: an unknown site key answers NOT_FOUND, a hostname that is not on the site's domain list answers FORBIDDEN and is listed under Install & domains as a rejected host, a suspended licence answers 202 and discards the hit, and bursts beyond 60 hits per 10 seconds from one IP answer RATE_LIMITED.
Tip: Every response also carries an x-request-id header. Log it next to your own request id so support can find the exact server entry.