Skip to content

Errors

The one shape every AdCrunch failure arrives in, every status code you can meet, what is safe to retry, and how the same failure reaches an MCP client.

On this page

Every published AdCrunch API answers every failure with the same shape. Read this page once and you can write your error handling before you write your first call.

The shape

{
  "error": "revision_mismatch",
  "message": "This brand changed since you opened it",
  "currentRevision": 7
}

Two fields are always there, and they do different jobs.

Field What it is Branch on it?
error A stable code, lower snake_case. Yes. Only this.
message One sentence, written for a person to read. Never.

message carries no contract. AdCrunch rewords it whenever it can be said better, and that is the whole reason the two are separate fields. If you match on prose, an improvement to the wording breaks your integration.

Some codes carry an extension field beside the two, and that field is what makes the failure actionable — currentRevision above tells you what to read before you retry.

Tip

Always write a default branch

A code you have never seen is what a new failure looks like. AdCrunch may add a code to an operation at any time — that is additive, and it is why every operation’s list of codes is open. Renaming or removing a code is breaking, and it does not happen without notice.

The codes

Status error Extension What it means
400 invalid_request The body or query does not match the operation. Nothing was started.
400 invalid_cursor That pagination cursor could not be read. Pass back the nextCursor you received, unchanged.
401 unauthorized No credential, or one that does not resolve.
403 forbidden Your credential resolved, and it does not hold the permission this operation needs.
403 missing_write_access The advertiser is connected without write access. Reconnect the provider to grant it.
404 not_found No such thing for your organization. See below.
404 not_attached That advertiser is not attached to this brand.
409 revision_mismatch currentRevision Someone changed it since you read it.
409 slug_conflict slug Another row of the same kind already uses that slug.
409 no_object The upload was reserved and the bytes never arrived.
409 currency_frozen currency The Campaign Plan is denominated, and a line item already carries a budget. Clear those budgets to change the currency. Nothing is converted.
409 persona_not_in_brand lineItemIds You re-pointed a Campaign Plan at another brand, and the listed line items still name a persona of the old one. Clear those personas first.
413 too_large limitBytes, sizeBytes The file is over the limit.
415 unsupported_type allowed That media type is not accepted. allowed lists the ones that are.
422 advertiser_not_owned That ad account is not connected to your organization. See below.
422 invalid_age_range A persona’s age bounds are whole numbers, and age_min may not exceed age_max.
422 invalid_amount Amounts are whole numbers of minor units, and never negative.
422 invalid_countries Countries are ISO 3166-1 alpha-2 codes, such as ["FR", "BE"].
422 invalid_currency A currency is a three-letter ISO 4217 code, such as EUR.
422 invalid_logo The document you nominated as a logo is not one this brand owns.
422 invalid_slug The slug you sent holds no letter or digit.
422 invalid_window Dates are real YYYY-MM-DD dates, and the start may not follow the end.
422 persona_not_in_brand The persona you named on a line item belongs to another brand. A plan with no brand has none available.

Two of these codes carry two statuses, because they answer two different questions:

  • persona_not_in_brand is 422 when you name the wrong persona on one line item, and 409 — with lineItemIds — when you move a whole plan to another brand and its existing rows still point at the old one.
  • not_found is what you get for a thing you named that your organization does not have. advertiser_not_owned is 422, not 404, because an ad account is an input to an operation rather than the thing the operation reads.

This page covers what AdCrunch publishes. Brand enrichment is reachable and deliberately undocumented, and it sends two codes that are not listed here.

The one failure that looks different

A request that fails the operation’s own schema is rejected by the framework before any AdCrunch code runs. That body is the framework’s, not ours:

{
  "type": "validation",
  "on": "body",
  "property": "budget",
  "message": "Expected number",
  "found": { "…": "…" },
  "errors": [{ "…": "…" }]
}

It arrives as 422 and it has no error field. Documented rather than translated, so that you meet it here rather than in production. Treat a 422 with a type of validation as “I sent the wrong shape”, and read property to find out where.

What to retry

Status Retry? Do this instead
400, 422 No Fix the request. Retrying sends the same wrong thing.
401 Not as-is Refresh the token, or authorize AdCrunch again from your client’s settings. Then retry once.
403 No The credential is right and its permissions are not. Change the scope or reconnect the provider.
404 No Re-read the parent listing. The id may be stale, or it may never have been yours.
409 revision_mismatch Yes, after a read Read the object again, apply your change to the version that came back, and send it with the new revision.
409 (any other) No slug_conflict, no_object, currency_frozen and persona_not_in_brand each describe a state you must change first. The same request will fail again.
413, 415 No Change the file.
5xx Yes, with backoff Something failed on our side or on the provider’s. Nothing was necessarily left half-done — check the status of what you started before you start it again.

A 409 revision_mismatch is the one worth handling rather than surfacing. Read the object again, fold your edit into the version that came back, and send it with the new revision. To read the whole object again and start over discards what the author had typed.

A missing thing and a thing that is not yours

Read one thing, and the two answers are the same. Every read is scoped to your organization, in the query itself, so an id that does not exist and an id that belongs to another organization both answer 404 not_found — or, for a list, an empty list.

This is deliberate. A surface that told the two apart lets a caller find which ids exist by asking for them.

Two consequences:

  • A 404 is not proof the thing was deleted. It can be alive in an organization you are not in.
  • An empty result from query_insights has two meanings. Either your organization does not own that advertiser, or the advertiser reports no data for that range. The response cannot tell you which.

Name an ad account, and the answer is different. An operation that takes an advertiserId as an input — attach it to a brand, put it on a line item, record an execution — answers 422 advertiser_not_owned rather than 404.

That is not a hole in the rule above. The answer states one fact about your organization: this ad account is not connected to it. It says nothing about whether the account exists anywhere else, so there is nothing for a caller to find by asking.

How a failure reaches an MCP client

An MCP tool call is not an HTTP request from your agent’s point of view, so there is no status code to read. A tool that fails returns a normal tool result with isError set, and a sentence in its text content:

{
  "content": [
    { "type": "text", "text": "Entity not found: meta/campaign/123." }
  ],
  "isError": true
}

Your agent reads that sentence, the same way it reads a successful result. In practice this means:

  • The sentence is the whole error. There is no code beside it on the MCP surface today.
  • The agent will usually recover on its own — it re-reads the parent list and tries again with a real id. That is the behaviour to expect, not a bug.
  • A tool result is not a failed call. isError: true means the tool ran and refused. A transport-level failure is something else, and your client reports it as one.

An OAuth token that has expired is the one failure that stops every tool at once. Authorize AdCrunch again from your client’s settings.

What’s next

  • Auth & scopes — what a credential is allowed to touch in the first place.
  • API reference — every operation, with the codes it can send.
  • Concepts — the words these messages use.