Errors
Every way the Level API can say no — thirteen problem types, what each one means, and which of them are worth retrying.
Errors are returned as RFC 9457 problem documents
with the content type application/problem+json:
{
"type": "https://docs.marketing-bar.com/api/problems/insufficient-scope",
"title": "Insufficient scope",
"status": 403,
"detail": "This endpoint requires reports:data.",
"instance": "/v1/reports/8a1b0c2d…/data",
"traceId": "0HN7…",
"retryAfterSeconds": null
}
Branch on type, never on title or detail. The catalogue below is closed —
there are thirteen types and there will not be a fourteenth without a version bump —
while the human wording may be improved at any time.
traceId identifies the request in Level's logs. Quote it when you contact support.
The catalogue
| Type | HTTP | Retry? |
|---|---|---|
invalid-token | 401 | After getting a new token |
insufficient-scope | 403 | No |
plan-feature-required | 403 | No |
subscription-locked | 403 | No |
not-found | 404 | No |
invalid-parameter | 400 | No |
rate-limit-exceeded | 429 | Yes, after Retry-After |
rate-limiter-unavailable | 503 | Yes |
billing-unavailable | 503 | Yes |
identity-unavailable | 503 | Yes |
data-source-unavailable | 503 | Later |
request-timeout | 503 | With a smaller request |
internal-error | 500 | Yes, once |
The caller's side
invalid-token — 401 {#invalid-token}
The token is missing, malformed, expired, belongs to a revoked client, is a signed-in person's token rather than an API client's, or was issued for a different audience.
Level does not say which: telling a caller why a credential was refused is help offered to whoever is guessing.
What to do. Exchange the client secret for a fresh token and retry once. If the new token is refused too, the client or its secret has been revoked — check the API clients screen.
insufficient-scope — 403 {#insufficient-scope}
A valid token whose scopes don't cover this endpoint — asking for report data with
only reports:read, for instance.
What to do. Two possibilities: you asked for too few scopes in the token request,
or the client was never granted the scope. GET /v1/me shows what the token actually
holds. Granting a new scope means editing the client — no code change fixes it.
plan-feature-required — 403 {#plan-feature-required}
The organization owner's plan doesn't include the public API (Business and above).
detail names the plan.
What to do. Nothing technical. See Plans & limits.
subscription-locked — 403 {#subscription-locked}
The owner is over their plan limits and everything they own is read-locked. On /v1
this blocks reads too, unlike in the app — but /v1/me, /v1/health,
/v1/docs and /v1/openapi.json keep answering, so an integration can always read
that it is locked.
What to do. The owner has to delete down to fit the plan or upgrade. See what happens if you go over the limits.
not-found — 404 {#not-found}
No such resource — or one that exists but is outside this client's allow-list, or
belongs to another organization. All three answer the same way on purpose: a 403
would confirm that somebody else's row exists.
What to do. Check the id, then check the client's allow-list in the API clients screen.
invalid-parameter — 400 {#invalid-parameter}
A parameter is missing, malformed or out of range: pageSize over 200, a date range
wider than 400 days, an unknown metric name, a groupBy this report can't answer.
detail says which — and for groupBy, which ones it can.
What to do. Fix the request. Retrying it unchanged will fail identically.
rate-limit-exceeded — 429 {#rate-limit-exceeded}
The hourly budget of the subscription is spent, or this client has reached its 50 %
fair share while others still have room. Retry-After carries the seconds.
What to do. Wait out Retry-After, then look at
Quotas & rate limits — ETag caching and wider, rarer pulls are
usually the whole fix.
Level's side
rate-limiter-unavailable — 503 {#rate-limiter-unavailable}
Level can't meter requests right now, and an unmetered request is not allowed through. Fail-closed, one mode only.
What to do. Retry after Retry-After. /v1/me still answers and reports
quota.state: "unavailable".
billing-unavailable — 503 {#billing-unavailable}
The owner's plan couldn't be established — not even from a snapshot, which is served for up to 60 minutes when the billing store is briefly unreachable.
What to do. Retry. /v1/me reports plan.state: "unknown" while this lasts.
identity-unavailable — 503 {#identity-unavailable}
Only on the client-registration path, and therefore only ever seen in the app, not by an integration: the service that holds credentials didn't answer.
What to do. Try creating or rotating the client again in a minute.
data-source-unavailable — 503 {#data-source-unavailable}
An ad platform connection expired or lost a permission, so there is nothing to serve — and it isn't your request's fault.
What to do. Someone has to reconnect the platform in Level. See Managing connections. Retrying earlier than that won't help.
request-timeout — 503 {#request-timeout}
The request hit the thirty-second deadline and was cancelled.
What to do. Ask for less: a shorter date range, fewer ad accounts via
adAccountIds, a smaller pageSize. Repeating the same call will time out again.
internal-error — 500 {#internal-error}
Anything else. It carries no detail, ever.
What to do. Retry once. If it persists, contact support with the traceId from
the response.