---
title: Errors
description: The one shape of an AdCrunch failure, the field to branch on, the two answers that do not use that shape, 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:

```json
{
  "error": "not_found",
  "message": "Skill not found"
}
```

- **`error` is a stable code.** Branch on it. A code does not change for a given failure.
- **`message` is 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](/api/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. |
| `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](#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](/api) lists each operation.

## Two answers that do not use this shape

Both come from the framework that serves the API, and not from AdCrunch.

1. **A request that does not match the schema of an operation** answers `422`, with the shape of the framework. It has no `error` field. This is `POST /assets/uploads` with no `filename`:

   ```json
   {
     "type": "validation",
     "on": "body",
     "property": "filename",
     "message": "Invalid input: expected string, received undefined",
     "found": { "x": 1 },
     "errors": [
       {
         "expected": "string",
         "code": "invalid_type",
         "path": ["filename"],
         "message": "Invalid input: expected string, received undefined"
       }
     ]
   }
   ```

   Some operations also send a `422` with the AdCrunch shape, for example `invalid_slug`. So read a `422` in this order: if the body has `error`, branch on it. If it has `type: "validation"`, the request did not match the schema. `POST /mutations` is different: it answers a request that does not match with `400` and `error: "invalid_request"`.

   In Observe, Context, and Assets, the framework checks the schema before AdCrunch checks the API key. So a request with no key and a bad body gets this `422`, not `401`.

2. **A path that does not exist** answers `404` with the plain text `NOT_FOUND`, of type `text/plain`. An Observe operation that reads one entity answers the same text when AdCrunch knows the entity but holds no stored copy of it.

## The data of another organization

The API never tells you whether a thing exists in another organization.

- **Mutations, Context, and Assets** answer `404` with `error: "not_found"`. A thing that never existed gets the same answer. So these APIs never answer `403` for 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. `POST /mutations` and `GET /mutations` only. | Correct the request. |
| `400` | `invalid_cursor` | AdCrunch cannot read the `cursor`. `GET /mutations` only. | Send the `nextCursor` of the previous answer, with no change. |
| `401` | `unauthorized` | No API key, or a key that does not resolve. | See [Authentication](/api/authentication). |
| `403` | `forbidden` | The key does not hold the permission. | See [Authentication](/api/authentication). |
| `404` | `not_found` | The thing does not exist in your organization. | 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](/api/upload-a-file). |
| `415` | `unsupported_type` | The stored file has a type that AdCrunch does not accept. AdCrunch deleted it. | See [Upload a file](/api/upload-a-file). |
| `422` | `advertiser_not_owned` | That advertiser is not one of your organization. | Use an `advertiserId` of your organization. |

## Live reads

The list operations for one advertiser, such as `GET /observe/{advertiserId}/campaigns`, take `fresh=true`. With it, AdCrunch reads 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. | Send the same request without `fresh`. |
| `409` | `provider_not_connected` | The connection to the provider does not work. | A person must connect the advertiser again. Without `fresh`, the request 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](/connect/providers) lists which resource each provider can read live.
