Errors
The one shape of an AdCrunch failure, the field to branch on, the order of the checks, and the codes that more than one operation sends.
The shape of a failure
Each AdCrunch failure has the same JSON body, in each of the four APIs:
{
"error": "not_found",
"message": "Skill not found"
}
erroris a stable code. Branch on it. A code does not change for a given failure.messageis for a person. Show it, or log it. AdCrunch rewrites a message when it can say it better, so never branch on it.
Some failures add a field that tells you what to do next:
error |
Added field | What it holds |
|---|---|---|
revision_mismatch |
currentRevision |
The revision that the row holds now. See Edit without overwriting. |
slug_conflict |
slug |
The slug that is already taken. |
unsupported_type |
allowed |
Each media type that the operation accepts. Only when you reserve a Document. |
invalid_request |
issues |
One entry for each field that does not match. See A request that does not match. |
too_large |
sizeBytes, limitBytes |
The size of the stored file, and the limit. Only when you finalize a Document. |
live_read_unsupported, provider_not_connected, provider_error |
provider |
The provider of the advertiser. See Live reads. |
provider_error |
platformError |
The error object of the provider, with no change. |
The page of each operation lists each status and each code that it sends. The overview lists each operation.
A request that does not match
A request that does not match the schema of its operation answers 400 with error: "invalid_request". The field is missing, it has the wrong type, or the body is not valid JSON. issues names each field. This is POST /assets/uploads with no filename:
{
"error": "invalid_request",
"message": "The request does not match the schema. Read issues for each field.",
"issues": [
{
"in": "body",
"path": "/filename",
"message": "Invalid input: expected string, received undefined"
}
]
}
inis the part of the request that holds the field:body,query,params,headersorcookie.pathis a JSON Pointer into that part. An empty string is the whole part, for example a body that is not valid JSON.messageis for a person, as above.
The answer never repeats what you sent. The schema of each operation is on its page.
400 and 422
400: the request does not have the right shape.invalid_requestis the code. Correct the request.422: the request has the right shape, and AdCrunch refuses what it says. The code says why, for exampleinvalid_amountoradvertiser_not_owned.
The order of the checks
Each operation checks the API key first, then the permission, then the request. So a request with no key and a bad body answers 401, and a key without the permission answers 403, before the body is read against the schema.
There is one exception. A body that is not valid JSON answers 400 before the API key is checked, because the body is read before any check runs.
A path that does not exist
A path that no operation answers gets 404 with error: "not_found", the same code as a thing that does not exist. The message tells the two apart.
Changed on 2026-09-24. Before this date, a request that did not match the schema answered 422 with the shape of the framework, and a path that did not exist answered the plain text NOT_FOUND. Neither had an error field. In Observe, Context, and Assets, the schema was checked before the API key.
The data of another organization
The API never tells you whether a thing exists in another organization.
- Mutations, Context, and Assets answer
404witherror: "not_found". A thing that never existed gets the same answer. So these APIs never answer403for it. - Observe answers
200. A list is empty, and a read of one entity has an empty body. In Observe, an advertiser of another organization is not a failure.
Codes that more than one operation sends
| Status | error |
Meaning | What to do |
|---|---|---|---|
400 |
invalid_request |
The request does not match the schema. issues names each field. Every operation that takes input. |
Correct the request. |
400 |
invalid_cursor |
AdCrunch cannot use the cursor: it cannot read it, or the cursor belongs to a different query. Every list of Observe, Mutations, Assets and Context. |
Send the nextCursor of the previous answer, with no change. |
401 |
unauthorized |
No API key, or a key that does not resolve. | See Authentication. |
403 |
forbidden |
The key does not hold the permission. | See Authentication. |
404 |
not_found |
The thing does not exist in your organization, or no operation answers the path. | Do not retry. |
409 |
revision_mismatch |
Somebody wrote first. | Read again, apply your change, and send it again. |
409 |
slug_conflict |
Another row in your organization has that slug. | Choose a different slug. |
413 |
too_large |
The stored file is larger than the limit. AdCrunch deleted it. | See Upload a file. |
415 |
unsupported_type |
The stored file has a type that AdCrunch does not accept. AdCrunch deleted it. | See Upload a file. |
415 |
type_mismatch |
The bytes of a stored Asset are a different type from the Content-Type of its upload. AdCrunch deleted it. |
Upload again with the real Content-Type. |
415 |
unreadable |
The stored Asset is no accepted type, or it ends before its header does. AdCrunch deleted it. | Upload an intact file of an accepted type. |
422 |
advertiser_not_owned |
That advertiser is not one of your organization. | Use an advertiserId of your organization. |
Live reads
Every Observe operation whose path names an advertiser, such as GET /observe/{advertiserId}/campaigns, is a live read: AdCrunch asks the provider itself, and not what AdCrunch holds. A live read can fail in three ways, and each needs a different action:
| Status | error |
Meaning | What to do |
|---|---|---|---|
501 |
live_read_unsupported |
AdCrunch cannot read this resource live from this provider. This does not change. | Read the stored copy: GET /observe/campaigns?advertiserId=acc_…. |
409 |
provider_not_connected |
The connection to the provider does not work. | A person must connect the advertiser again. The stored read, GET /observe/campaigns?advertiserId=acc_…, still answers what AdCrunch holds. |
502 |
provider_error |
The provider refused the request, or did not answer. | Retry. This failure is often temporary. |
What each provider supports lists which resource each provider can read live.