Skip to content

API errors

Every error code the Convultra API and MCP server return, what causes it, and what to do about it.

article |7 min |Updated Sep 2026
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

StatusCodeIn one line
400invalid_requestThe request is malformed.
400invalid_rangeThe date range cannot be served.
400unsupported_dimensionUnknown breakdown dimension.
400unsupported_filterReserved.
401unauthenticatedNo credential was sent.
401invalid_tokenThe credential is not valid.
401expired_tokenReserved.
403insufficient_scopeThe credential lacks a scope.
403read_only_roleA viewer attempted a write.
403plan_requiredThe plan does not include this.
403project_forbiddenReserved.
404not_foundNot found, or not yours.
409outside_retry_windowToo old for the ad platform to accept.
409export_in_progressReserved.
422integration_requiredReserved.
429rate_limitedToo many requests, or too many at once.
500internal_errorA fault on our side.
502upstream_errorA 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; details lists each offending field with a message
  • a credential was sent in the query string (token, api_key, apiKey, access_token or key) rather than the Authorization header; refused before authentication, because URLs leak through logs, proxies and browser history
  • project was omitted and the credential can see more than one project; details.available_projects names them
  • list_conversions was paged past 2000 rows in one range
  • get_funnel was given the same event type twice
  • a POST body is not JSON, or not sent as Content-Type: application/json
  • a method the path does not support, for example GET on 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_date and end_date was given
  • a date is not YYYY-MM-DD
  • start_date is after end_date
  • range is 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=hour over 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-After is 1 second. Send fewer in parallel.
  • No scope header: the per-minute or per-day quota is spent. Retry-After runs 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.