API errors
Every error code the Convultra API and MCP server return, what causes it, and what to do about it.
On this page · 9 sections
The Convultra API and MCP server are in Beta. Every failure has the same shape, and the HTTP status is always the signal:
{
"error": {
"code": "invalid_range",
"message": "That range is 500 days. The maximum is 400 days (about 13 months) in one request; page through with narrower ranges for more.",
"doc_url": "https://convultra.com/docs/api/errors#invalid_range",
"details": { }
}
}
code is stable and safe to branch on. message is written for a developer to read and may change. details appears only when there is something structured worth having: the scopes an operation needs, the dimensions it accepts, the projects a credential can see, a retry delay.
doc_url links to the reference page at /docs/api/errors, anchored on the code.
All codes
| Status | Code | In one line |
|---|---|---|
| 400 | invalid_request | The request is malformed. |
| 400 | invalid_range | The date range cannot be served. |
| 400 | unsupported_dimension | Unknown breakdown dimension. |
| 400 | unsupported_filter | Reserved. |
| 401 | unauthenticated | No credential was sent. |
| 401 | invalid_token | The credential is not valid. |
| 401 | expired_token | Reserved. |
| 403 | insufficient_scope | The credential lacks a scope. |
| 403 | read_only_role | A viewer attempted a write. |
| 403 | plan_required | The plan does not include this. |
| 403 | project_forbidden | Reserved. |
| 404 | not_found | Not found, or not yours. |
| 409 | outside_retry_window | Too old for the ad platform to accept. |
| 409 | export_in_progress | Reserved. |
| 422 | integration_required | Reserved. |
| 429 | rate_limited | Too many requests, or too many at once. |
| 500 | internal_error | A fault on our side. |
| 502 | upstream_error | A service we depend on failed. |
“Reserved” codes are defined and documented so a client can handle them, but no operation in this version returns them.
400 Bad Request
invalid_request
The request is malformed. Common causes:
- a parameter has the wrong type or an unknown value;
detailslists each offendingfieldwith amessage - a credential was sent in the query string (
token,api_key,apiKey,access_tokenorkey) rather than theAuthorizationheader; refused before authentication, because URLs leak through logs, proxies and browser history projectwas omitted and the credential can see more than one project;details.available_projectsnames themlist_conversionswas paged past 2000 rows in one rangeget_funnelwas given the same event type twice- a
POSTbody is not JSON, or not sent asContent-Type: application/json - a method the path does not support, for example
GETon a retry
What to do: read message and details, fix the request. Retrying unchanged fails identically.
invalid_range
The date range cannot be served. The message says which rule it broke:
- only one of
start_dateandend_datewas given - a date is not
YYYY-MM-DD start_dateis afterend_daterangeis not a known preset- the range is longer than 400 days
- the range is longer than 90 days in a half-hour timezone (Kolkata, Adelaide, Kathmandu)
granularity=hourover a range longer than 7 days
What to do: narrow the range, split it into several requests, or ask in UTC. See Date ranges and timezones.
unsupported_dimension
get_breakdown was asked for a dimension it does not have.
What to do: choose one from details.supported, which lists all 20.
unsupported_filter
Reserved. Not returned by any operation in this version.
401 Unauthorized
A 401 means we do not know who you are. No scope or project can fix it. Every 401 carries a WWW-Authenticate header; on the MCP server that header is what makes an assistant show its “Connect” prompt.
unauthenticated
No credential was sent.
What to do: send Authorization: Bearer sk_..., or connect through OAuth.
invalid_token
A credential was sent but cannot be used. It is unrecognized, malformed, revoked or expired, or it is a proj_... project tracking key, which is public and write-only and can never read data. The message says which as far as it safely can; for a tracking key it says so explicitly.
What to do: create a secret sk_ key under Settings → Developer, or reconnect the OAuth client. If a key that used to work now fails, check whether it expired or was revoked.
expired_token
Reserved. Expired credentials currently return invalid_token.
403 Forbidden
A 403 means we know who you are, and the answer is no. Change the credential’s permissions, not the request.
insufficient_scope
The credential is valid but does not hold a scope the operation requires. details.required_scopes names every scope the operation needs, not only the missing one, and the WWW-Authenticate header repeats them, so an OAuth client can step up in a single round trip.
What to do: create a key with the scope, or reconnect the OAuth client and approve it. Missing spend:read does not produce this error on get_overview, get_timeseries or get_breakdown: those succeed and explain the absent spend fields in spend_omitted_reason.
read_only_role
The user behind an OAuth connection is a viewer on this project and attempted a write.
What to do: ask an owner or admin to perform the action, or to raise the role.
plan_required
The account’s plan does not include this surface, or the credential is an agency-wide key on a plan without agency keys. details.upgrade_url points at billing.
What to do: upgrade, or use an account-level key. Every paid plan includes both the API and the MCP server, so outside agency keys this is rare.
project_forbidden
Reserved. A project the credential cannot reach returns not_found, never a 403. See below.
404 Not Found
not_found
The endpoint does not exist, the resource does not exist, or it exists and this credential cannot reach it. Also returned by get_conversion for a conversion older than its lookback_days scan, and for a conversion the customer has removed.
What to do: check the path and id. If you are certain the id is right, the question is about access: which account or agency the key belongs to, and whether it is restricted to particular projects. GET /me lists what the credential can reach.
Why a project you cannot see is 404, not 403
A 403 on an unknown id is an existence oracle: anyone holding any key could enumerate ids and learn which ones are real projects belonging to other customers. So the API never confirms that a project exists unless you are entitled to see it, and “wrong id” and “not yours” are deliberately indistinguishable from outside.
The reverse is also guaranteed: a 404 never means “our lookup broke”. A database failure while looking up a project returns internal_error, with a message saying it is a fault on our side.
409 Conflict
outside_retry_window
retry_delivery was called on a conversion that has aged past the ad platform’s own acceptance window: 90 days after the click for Google Ads and Microsoft Ads, 7 days for Meta.
What to do: stop. The platform will refuse the conversion however many times it is sent.
export_in_progress
Reserved for data exports, which this version does not implement.
422 Unprocessable Content
integration_required
Reserved. A missing ad platform integration currently surfaces as available: false with a reason on get_spend, or as null spend fields with spend_unavailable_reason elsewhere, not as an error.
429 Too Many Requests
rate_limited
The credential exceeded its per-minute or per-day request quota, or sent more requests at once than its plan’s concurrency limit allows. The response carries Retry-After in seconds, and details.retry_after_seconds repeats it.
X-RateLimit-Scope: concurrency: too many requests at once.Retry-Afteris 1 second. Send fewer in parallel.- No scope header: the per-minute or per-day quota is spent.
Retry-Afterruns to the end of the minute, or to midnight UTC for the daily quota.
What to do: wait at least Retry-After seconds before retrying. Retrying sooner fails again and consumes quota. See the rate limit table in the API overview.
5xx Server errors
internal_error
Something went wrong on our side.
What to do: retry with exponential backoff. If it persists, email support@convultra.com with the X-Request-Id response header.
upstream_error
A service the API depends on, the analytics store or the delivery log, did not answer correctly. details.service names which. The API returns this rather than a partial answer: a delivery status reported without the delivery log would be a false statement about your ad account.
What to do: retry with exponential backoff, and keep the X-Request-Id.