Errors
The one shape of a failed tool call — the code in structuredContent and the sentence in the text — the codes that each tool can send, and what to do next.
A tool call that fails does not fail over HTTP. The tool runs, refuses, and returns a normal tool result with isError set. That result has one shape for each tool. Errors in the API reference describes the REST API, which uses the same codes.
The shape
{
"content": [
{
"type": "text",
"text": "Brand \"acme-running\" was changed since you read it (current revision 7). Re-fetch with brand_get and retry with the latest base_revision."
}
],
"isError": true,
"structuredContent": {
"error": "revision_mismatch",
"message": "Brand \"acme-running\" was changed since you read it (current revision 7). Re-fetch with brand_get and retry with the latest base_revision.",
"currentRevision": 7
}
}
isErroristrue. The tool ran and refused.- The text block holds one sentence for a person. The agent reads it, as it reads a successful result.
structuredContentholds the same failure as data, in the shape of each AdCrunch API:
| Field | What it is | Branch on it? |
|---|---|---|
error |
A stable code, lower snake_case. | Yes. Only this. |
message |
The sentence of the text block. | Never. |
| Other fields | Facts that make the failure actionable, such as currentRevision above. |
Yes, for the code that sends them. |
message carries no contract. AdCrunch rewrites a sentence when it can say it better. That is why the code is a separate field. A client that reads only the text still gets the whole failure.
The codes that each tool can send
Each tool can send these three codes:
error |
Other fields | What it means | Do this |
|---|---|---|---|
forbidden |
— | The token of your client does not hold the scope of this tool. | Authorize your client again, and grant the scope. Auth & scopes lists each scope. |
invalid_request |
issues |
The arguments do not match the input schema of the tool, or they break a rule over several arguments. issues names each argument. Nothing changed. |
Correct the arguments. The same call fails again. |
internal_error |
— | AdCrunch could not answer, or its answer did not match the output schema of the tool. The sentence never holds the details of the fault. | Try again later. |
Each tool also sends codes of its own, such as not_found or revision_mismatch. The page of each tool lists all the codes that it can send, under its reference part. The tool list gives the same codes in the _meta of each tool, under dev.adcrunch/errors.
One failure has one code on MCP and on the REST API. When a REST operation sends the same failure, the tool sends the same code, with the same other fields.
An argument that does not match
issues has one entry for each argument that does not match the schema:
"issues": [
{
"path": "/base_revision",
"message": "Invalid input: expected number, received undefined"
}
]
pathis a JSON Pointer into the arguments. An empty string is the whole set of arguments, for a rule over several arguments such as “at most one ofdailyBudgetandlifetimeBudget”.messageis for a person. Never branch on it.
A tool call has one part, its arguments. So an entry has no in field, which the REST API uses to name a part of the HTTP request.
An edit that collides
revision_mismatch is the one failure that the agent handles, and does not only report. It means that the object changed after the agent read it, and currentRevision gives the revision of now. Nothing changed.
The agent reads the object again, checks that its edit still applies to the new version, and sends the edit again with the new revision. It does not read the object and start again from zero, because that erases what the author wrote. What to expect gives the full rule.
A missing thing and a thing that is not yours
Each tool reads your organization only. So an id that does not exist and an id of another organization get the same answer: not_found, or an empty list.
This is deliberate. An answer that told the two apart would let a caller find which ids exist.
Two results follow:
not_founddoes not prove that the thing was deleted. It can exist in an organization that you are not in.- An empty result from
query_insightshas two meanings. Either your organization does not own that advertiser, or the advertiser has no data for that period. The result cannot tell you which.
The agent usually recovers by itself. It reads the list again, and it retries with a real id.
Two failures that are not tool results
Two failures stop each tool at once, before a tool runs. They are HTTP answers of the endpoint, and your client shows them as a connection problem:
401: the token is missing, has expired, or does not verify.403withinsufficient_scope: the token holds no AdCrunch scope at all.
Both answers carry a WWW-Authenticate header that tells your client how to authorize again. For both, authorize AdCrunch again from the settings of your client. Another client gives the details for a developer.
A change that fails is not a failed tool call
A change on Meta runs after the tool call, in a queue. get_mutation_status reports how it ended. When AdCrunch or Meta refused the change, the status is complete, and result.error holds the code, for example budget_cap_exceeded.
That call is a success: it read the status, and the status says that the change failed. So isError is not set. The agent reads the code in the result, and tells you why the change did not happen.
What’s next
- Auth & scopes: what a token can touch.
- What to expect: the rules that each tool follows.
- Errors in the API reference: the same codes on the REST API.