Skip to main content

Reference

Errors

The single error shape every endpoint returns, and what each code means.

Every error is one shape:

{
  "error": {
    "code": "insufficient_scope",
    "message": "This key does not have access to that site."
  }
}

code is a symbolic string, not a repeated HTTP status. Branch on code — it is stable, and it distinguishes cases that share a status.

message is for humans. It never contains anything from your request — no field name, no value, no site id. That is deliberate: an endpoint that echoes input back is both a way to inject content and a way to probe. Do not parse it.

400 — the request is malformed

codeMeaning
invalid_queryA query parameter is wrong: an unknown period, a bad timezone, from without to, period together with from/to, a limit over 1000, a minutes outside its range
unknown_dimensionA breakdown dimension that does not exist. The message lists the valid ones.
organisation_required/account and /usage describe one account. A personal login can belong to several, so only an API key can call them.
invalid_jsonThe body is not valid JSON
invalid_bodyThe body parsed but a field is wrong

The message names what was wrong and, where there is a fixed set, lists it. Retrying without changing the request will not help.

401 — we do not know who you are

codeMeaning
unauthenticatedNo Authorization header
invalid_credentialThe key is wrong, revoked or expired

invalid_credential covers all three on purpose. Distinguishing "revoked" from "never existed" would tell somebody probing that a key had once been real.

402 — the credential is fine, the plan is not

This is the status most people do not expect, and it is the accurate one: you are who you say you are and you are allowed to ask, but the plan does not cover the answer.

codeMeaningWhat fixes it
subscription_suspendedThe subscription ended, or a payment could not be collectedRenew
quota_exhaustedThis period's events went past the plan's allowance, beyond the grace bandMove up a step
subscription_requiredThe tier does not include this featureChange plan

subscription_suspended and quota_exhausted both send you to billing and they are not the same problem. One account has to renew; the other has outgrown its plan, which is good news. Do not collapse them into one message.

quota_exhausted deserves a paragraph

Collection never stops. Past the ceiling your events keep landing — what pauses is your ability to read the period through the API and the dashboard, which close together.

That is the deliberate trade. A gap in the data would be permanent, silent, and impossible to buy back later; a locked screen is none of those things. So nothing is lost while this lasts, and upgrading opens the period you already collected. At your limit covers what to expect.

There is a 10% grace band: the gate turns on past the ceiling, not at 100%, so an ordinary busy month does not read as a failure.

The API closes exactly what the dashboard closes. Past the ceiling the dashboard shows you billing and nothing else — not your analytics, not your site settings, not even the list of which sites you have. The API mirrors that:

Past the quota
Analytics, AI, live, search, goals402
/sites, /sites/{id}402
/account, /usagestill answer

The last two are the equivalent of the billing screen staying open. /usage in particular is the answer to a 402 — it tells you how far past the allowance you are. Refusing it on the quota would lock the door and take down the sign explaining why.

403 — we know who you are, and no

codeMeaning
insufficient_scopeThe key does not carry the scope this endpoint needs
site_not_accessibleThe site is not yours, or does not exist
site_collection_stoppedThe site exists and is yours, but collection is switched off
insufficient_roleThe action needs a higher role than this credential's owner has
owner_onlyOwners only

site_not_accessible covers "not yours" and "does not exist" with the same message, and a malformed id gets it too. Verified: a real site belonging to another organisation, a well-formed id nobody owns, and not-a-uuid all return identical responses — so the API cannot be used to discover which site ids are real.

404 — no such thing

codeMeaning
not_foundThe named resource does not exist

403 vs 404

A site you cannot see answers 403, never 404 — and so does a site that genuinely does not exist.

That is the point of it. If "not yours" and "no such site" gave different answers, the difference would be a way to ask whether any given id is real, one guess at a time. 404 is used only for resources you already have access to, where a missing one tells nobody anything.

500 — our fault

codeMeaning
request_failedSomething went wrong on our side
write_failedThe write could not be completed

The detail stays on the server. A stack trace or a database message in a public response is a map of the internals.

Retrying is reasonable, with a delay. If it persists, the status of the underlying store is the first thing we check.

503 — temporarily unable

codeMeaning
not_configuredEvent storage is unavailable

Distinct from 500 on purpose: this one is expected to clear on its own, so retrying with backoff is the right response rather than an escalation.

Was this page helpful?

Last updated September 16, 2026