Skip to content

Errors

Standard HTTP status codes plus a single JSON envelope for the body. If your client knows how to read error.code, it has everything it needs to decide whether to retry, surface, or fix.


Error envelope

Every non-2xx response returns:

{
  "error": {
    "code": "site_not_found",
    "message": "Site does not exist or is not accessible",
    "details": { "website_id": 999 }
  }
}

code is a stable machine-readable identifier. Match on it. message is human-friendly and may be reworded. details is optional and varies per error.


HTTP status codes

StatusMeaningTypical cause
400 Bad RequestMalformed requestMissing required param, unparseable JSON body, bad period value.
401 UnauthorizedNo / expired authCookie missing, JWT expired and refresh failed, logged out elsewhere.
402 Payment RequiredPlan limit exceededMonthly pageview cap hit on Free / Hobby tier.
403 ForbiddenAuth ok, access deniedSite is not owned by the authenticated user and not public.
404 Not FoundResource does not existWrong website_id, deleted goal, unknown path.
422 Unprocessable EntityValidation failedBody parsed but semantically invalid (e.g. duplicate goal name).
429 Too Many RequestsRate limit hitPlanned. No per-plan limit is enforced today.
500 Internal Server ErrorBug or infra issueInvestigate. Please report with request_id if you have one.

Common error codes

codeHTTPWhen
invalid_params400Required param missing or has an unsupported value.
invalid_body400JSON body could not be decoded.
auth_required401No auth_token cookie present.
token_expired401Access token expired and refresh token also failed.
forbidden403Authenticated user has no permission on this resource.
site_not_found404website_id does not match any site you own or share.
goal_not_found404Goal id not found on this site.
plan_limit_exceeded402Monthly pageview quota for the plan exhausted.
validation_failed422Semantic validation failed (see details for fields).
rate_limit_exceeded429Planned. Will ship with Rate limits.
internal_error500Unhandled server error.

Examples

Missing auth

curl 'https://statable.com/api/site/top-stats?website_id=42&period=7d'
{
  "error": {
    "code": "auth_required",
    "message": "Invalid authentication token"
  }
}

Log in (see Authentication) and replay with the cookie jar.

Wrong site

curl 'https://statable.com/api/site/top-stats?website_id=99999&period=7d' -b cookies.txt
{
  "error": {
    "code": "site_not_found",
    "message": "Site does not exist or is not accessible"
  }
}

The site either doesn't exist, was deleted, or belongs to another user (and isn't public).

Bad period

curl 'https://statable.com/api/site/top-stats?website_id=42&period=99x' -b cookies.txt
{
  "error": {
    "code": "invalid_params",
    "message": "Invalid period",
    "details": { "param": "period", "allowed": ["1r","1h","24h","7d","30d","12m","c","a"] }
  }
}

Plan cap hit

{
  "error": {
    "code": "plan_limit_exceeded",
    "message": "Monthly pageview limit reached on Free plan",
    "details": { "limit": 10000, "used": 10000 }
  }
}

Upgrade in Settings → Plan & Billing or wait for the next billing month.


Retry guidance

  • 5xx: exponential backoff. Start at 1 s, double up to 60 s, give up after a few attempts. Usually transient.
  • 429: when rate limiting ships, it will return Retry-After (seconds). Honor it. Don't retry until that wall-clock has passed.
  • 401 with token_expired: refresh and retry once. The cookie auto-refresh middleware handles this for you on subsequent calls. Explicit Bearer auth (when shipped) will require an explicit refresh round-trip.
  • 400 / 403 / 404 / 422: don't retry. The request is wrong. Fix the payload, the website_id, or your permissions.
  • 402: don't retry until the plan is upgraded or the next billing month starts.

Reporting bugs

For a 500 you can't explain, please share:

  • Endpoint and full URL (with params).
  • Request body (sanitized of secrets).
  • Approximate timestamp (UTC).
  • Any request_id returned in the response headers.

Email [email protected] or open an issue on the public roadmap. Including request_id is the fastest path to a fix. It lets us pull the exact log entry server-side.


Ready to take control of your web analytics? Try Statable free for 30 days — no credit card required, full feature access, GDPR-compliant by default. Start your free trial or view a live demo.