# Errors

> How YoPrint reports a failed request, what each status means and how to fix it. Treat success false as a failure whatever the status, and know the operations that refuse with 200 or 500.

Web page: https://developer.yoprint.com/concepts/errors/

## The rule

A request failed when **either** of these is true:

- The HTTP status is `4xx` or `5xx`.
- The JSON body has `"success": false`.

Check both. A few operations refuse with status `200` and `"success": false`, and some refuse a change with `500`. They're listed under [Known exceptions](#known-exceptions).

Send `Accept: application/json` on every request. Matched paths always answer JSON, but a path that doesn't exist (`404`) or a method it doesn't take (`405`) answers an HTML page without that header. A missing second `/v2/` is the usual cause. There's no request id header to quote: when you [ask for help](https://developer.yoprint.com/start/support.md), send the method, path, time and answer instead.

## The two error bodies

A failed request with `Accept: application/json` answers JSON in one of two shapes. Your code must handle both.

**1. The standard error body.** Most errors look like this:

```json
{
  "status": 422,
  "success": false,
  "error": {
    "code": "validation_failed",
    "message": null,
    "fields": {
      "customer.name": ["The customer.name field is required."]
    }
  }
}
```

| Field                 | Meaning                                                                                                                                                                                                                |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `status`              | Usually the HTTP status, repeated. On the `200` refusals listed under [Known exceptions](#known-exceptions), it says `500` while the HTTP status is `200`. Branch on the HTTP status and `success`, not on this field. |
| `success`             | `false` on an error.                                                                                                                                                                                                   |
| `error.code`          | A short machine-readable reason, such as `validation_failed` or `user_is_not_authorized`. Can be `null`.                                                                                                               |
| `error.message`       | A human-readable explanation. Often `null`.                                                                                                                                                                            |
| `error.fields`        | On a validation error, the problems for each field, keyed by the field's path in your body.                                                                                                                            |
| `error.field_reasons` | Sometimes, with `fields`: a machine-readable reason for each field.                                                                                                                                                    |

**2. A bare message.** A few errors have only `message`, with no `status`, `success` or `error`:

```json
{ "message": "Insufficient Permission" }
```

You get this shape from:

- A `403` when your role lacks a permission checked before the operation runs, such as Manage Settings: `"Insufficient Permission"`.
- A `422` when the team's plan doesn't include a feature: `"This feature is not available to your team's plan."`
- A `500` from an unexpected failure: `"Server Error"`.
- A few operation-specific refusals, which their reference pages show, such as a product that isn't found when you add a line.

Without `Accept: application/json`, a path that doesn't exist (`404`) or a method it doesn't take (`405`) answers an HTML page instead. A request over 100 MB is refused with `413` before YoPrint reads it, so that answer isn't JSON either.

So read `error.code` when `error` is an object, and fall back to `message`:

```javascript
const code = typeof body?.error === 'object' && body.error !== null ? body.error.code : null;
const message = body?.error?.message ?? body?.message ?? null;
```

Handle errors in this order:

**Diagram:** Don't parse the message: its wording can change.

1. **HTTP status and success**: Did it fail?
2. **error.code**: Why, when it isn't `null`
3. **error.fields**: Which input to fix
4. **error.message or message**: To log or show a person

## Statuses

| Status | What it means                                                                                                                                                           | What to do                                                                                                                                                                                   |
| ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | A value YoPrint checks before anything else isn't one it knows, such as the event name in Show a sample event.                                                          | Fix the value. The operation's reference page lists the ones it takes.                                                                                                                       |
| `401`  | The API key is missing or wrong, or it was regenerated. `error.code` is `unauthenticated`.                                                                              | Send `Authorization: Bearer YOUR_API_KEY` with the current key. See [Authentication](https://developer.yoprint.com/concepts/authentication.md).                                              |
| `402`  | A payment for your YoPrint subscription failed.                                                                                                                         | Follow `next_action`: `use_different_card`, `update_card_then_retry`, or `refresh_billing` when the subscription ended during a retry.                                                       |
| `403`  | Your team's plan doesn't include API access (`api_access_not_in_plan`), your role doesn't allow this operation, or the call manages the key itself (`self_credential`). | For `api_access_not_in_plan`, use a team on the Pro plan or higher. Otherwise check your role. See [Roles and permissions](https://developer.yoprint.com/concepts/roles-and-permissions.md). |
| `404`  | The team slug is wrong, you're not on that team, or the record doesn't exist on this team. `error.code` is `page_not_found` or `null`.                                  | Check the slug and the id. Ids are the `id` field, not document numbers. See [IDs](https://developer.yoprint.com/concepts/ids.md).                                                           |
| `405`  | The path exists, but not with this method.                                                                                                                              | Check the method on the operation's reference page.                                                                                                                                          |
| `409`  | Not saved: the record changed since you read it, or the id is an older, replaced version of it (some addresses and locations).                                          | Read it again and retry. When there's a newer version, the answer points at it, such as `current_address`.                                                                                   |
| `410`  | Gone for good: a file or an attempt that expired, such as an import's error file.                                                                                       | Start again.                                                                                                                                                                                 |
| `413`  | The request is over 100 MB. The web server refuses it before YoPrint reads it, so the body isn't JSON.                                                                  | Send a smaller file.                                                                                                                                                                         |
| `422`  | The request was understood but refused: a field is missing or invalid, or a business rule stops it.                                                                     | Read `error.fields` or `error.message`, fix the request, and send it again.                                                                                                                  |
| `429`  | Your team already has the most data exports running at once.                                                                                                            | Wait for one to finish, then start yours. See [Limits](https://developer.yoprint.com/concepts/limits.md).                                                                                    |
| `500`  | Something failed inside YoPrint, or one of the business refusals listed below.                                                                                          | Check the list below. Otherwise read the record back before you retry. See [Retries and duplicates](https://developer.yoprint.com/concepts/retries-and-duplicates.md).                       |
| `503`  | YoPrint is briefly unavailable, or exports are paused (`export_creation_paused`, with `Retry-After`).                                                                   | Wait and retry, reading back first if it was a change.                                                                                                                                       |

## The two kinds of 422

**`validation_failed`: a field is wrong.** `error.fields` names each field with its problems, and `error.message` is `null`.

**`validation_error`, or another code: a business rule refuses the change.** The fields are fine, but the record's state doesn't allow it. `error.message` says why. For example, recording a payment on a draft order answers:

```json
{
  "status": 422,
  "success": false,
  "error": {
    "code": "validation_error",
    "message": "Payment Action Not Allowed - due to order internal state"
  }
}
```

Fix the state first (here, publish the order), then retry.

## Error codes

These are the codes you can get on most operations. Operation-specific codes, such as `payment_below_minimum` or `stale_address`, are on each operation's reference page under Errors.

| Code                                                                           | Status         | What it means                                                                                                                     | What to do                                                                                                                                                                       |
| ------------------------------------------------------------------------------ | -------------- | --------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `unauthenticated`                                                              | `401`          | The key is missing or wrong, or it was regenerated.                                                                               | Send `Authorization: Bearer YOUR_API_KEY` with your current key.                                                                                                                 |
| `api_access_not_in_plan`                                                       | `403`          | The key works, but the team's plan doesn't include API access. Every request to this team answers this.                           | Use a team on Pro or higher, or upgrade this one. See [Authentication](https://developer.yoprint.com/concepts/authentication.md#when-your-teams-plan-doesnt-include-api-access). |
| `self_credential`                                                              | `403`          | The call reads, creates or resets the key itself. A key can't manage itself.                                                      | Manage the key in **Profile**.                                                                                                                                                   |
| `user_is_not_authorized`                                                       | `403`          | Your role doesn't allow this action on this record.                                                                               | Ask the team owner to change your role, or use a key whose role allows it.                                                                                                       |
| `unauthorized`                                                                 | `403`          | A role or ownership check refused it. `message` is `null`.                                                                        | As above.                                                                                                                                                                        |
| No code: `{ "message": "Insufficient Permission" }`                            | `403`          | Your role lacks a permission, such as Manage Settings.                                                                            | As above.                                                                                                                                                                        |
| `page_not_found`                                                               | `404`          | The team slug is wrong, you're not a member of the team, or an id in the path isn't one of this team's.                           | Check the slug against **Team ID** in Profile, and the id.                                                                                                                       |
| `null`, with a message starting "No query results for"                         | `404`          | No record with that id on this team.                                                                                              | Check the id. Ids are the `id` field, not document numbers.                                                                                                                      |
| `validation_failed`                                                            | `422`          | A field is missing or invalid. `error.fields` names each one.                                                                     | Fix the fields and send the request again.                                                                                                                                       |
| `validation_error`                                                             | `422`          | A business rule refused the change. `error.message` says why.                                                                     | Fix the record's state, then retry.                                                                                                                                              |
| `bulk_validation_payload_failed`                                               | `422`          | One item of a bulk request is invalid. `error.bid` names it. Nothing was saved.                                                   | Fix that item and send the whole request again.                                                                                                                                  |
| `amount_out_of_range`                                                          | `422`          | The save would answer with an amount the older number format can't carry exactly. Nothing was saved.                              | Send `X-Yoprint-Decimal-Format: exact-v1` and retry.                                                                                                                             |
| `upload_not_allowed`                                                           | `422`          | The file is a web page, a script or a program.                                                                                    | Send another file type, or zip it.                                                                                                                                               |
| `mockup_type_not_allowed`, `image_not_allowed`                                 | `422`          | A mockup isn't an image YoPrint can preview, or an image is over a size limit.                                                    | Upload it as a production file, or send a smaller image.                                                                                                                         |
| `duplicate_report`                                                             | `422`          | An export of the same kind is already running.                                                                                    | Wait for it to finish.                                                                                                                                                           |
| `connect_timeout`                                                              | `422`          | Another service YoPrint called, such as Shippo, Shopify or QuickBooks, didn't answer in time. The change may have happened there. | Read the record back before you retry.                                                                                                                                           |
| No code: `{ "message": "This feature is not available to your team's plan." }` | `422`          | The team's plan doesn't include this feature.                                                                                     | Upgrade the plan, or leave the feature out.                                                                                                                                      |
| `validation_error`, "You are sending too many emails…"                         | `422`          | The team went over an email limit. **All of the team's email is now blocked.**                                                    | Stop sending, and contact YoPrint support to lift the block. See [Limits](https://developer.yoprint.com/concepts/limits.md#email-sending).                                       |
| `email_feature_not_enabled_in_trial`                                           | `422`          | The team is on a trial, and YoPrint hasn't turned email on for it. Nothing was sent.                                              | Contact YoPrint support, or send once the subscription is paid.                                                                                                                  |
| `export_capacity`                                                              | `429`          | The team already has 3 exports running.                                                                                           | Wait for one to finish, then start yours.                                                                                                                                        |
| `action_not_allowed`                                                           | `200` or `500` | The record's state doesn't allow the action.                                                                                      | See [Known exceptions](#known-exceptions). Don't retry without fixing the state.                                                                                                 |
| `delete_fail`                                                                  | `500` or `422` | The record can't be deleted: it's a default, made by YoPrint, a primary contact, or a customer with an open quote or order.       | Change the default or the primary first, or close the open orders.                                                                                                               |
| `export_creation_paused`                                                       | `503`          | Exports are paused for a few minutes while YoPrint updates. `Retry-After` says how long.                                          | Wait that many seconds, then send it again.                                                                                                                                      |
| No code: `{ "message": "Server Error" }`                                       | `500`          | Something failed inside YoPrint.                                                                                                  | Read the record back before you retry. See [Retries and duplicates](https://developer.yoprint.com/concepts/retries-and-duplicates.md).                                           |

`error.code` is sometimes not a short code but a sentence or a number, such as `"Insufficient Order Balance"` on some payment refusals. Branch on the HTTP status and `success` first, and treat any code you don't know as a refusal.

## Not found

A `404` for a record means YoPrint found nothing with that id on this team. The usual causes:

- The id is a document number such as `SO1001`, or a value from another team.
- The record was deleted.
- The slug is wrong, so no record on any path will be found.

The message of a record `404` can name an internal class. Ignore it: it isn't part of the API.

## Known exceptions

These operations don't follow the rule that a refusal is a `4xx`. Your code must check `success`, and must not retry the `500`s below without fixing the cause: they fail the same way every time.

### Refused with `200` and `"success": false`

These answer HTTP `200` with a body like this. They change nothing, **except Delete a job**, which deletes the job anyway. "Neither a draft nor published" only happens on some older orders, made before YoPrint's current order system. Orders made today are always one or the other.

```json
{ "status": 500, "success": false, "error": { "code": "action_not_allowed", "message": null } }
```

| Operation                                                                              | When                                                                                                                                                              |
| -------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Publish a sales order or quote                                                         | It's already published.                                                                                                                                           |
| Restore a sales order or quote                                                         | It isn't deleted.                                                                                                                                                 |
| Create a job, or add one from a job preset                                             | The order is neither a draft nor published.                                                                                                                       |
| Reorder an order's jobs                                                                | The order is neither a draft nor published.                                                                                                                       |
| Update a job                                                                           | The order is neither a draft nor published.                                                                                                                       |
| Change a job's status                                                                  | The order isn't published (a draft).                                                                                                                              |
| Delete a job                                                                           | The order is neither a draft nor published. **The job is deleted anyway**, then this refusal comes back. Don't retry: read the order's jobs, and the job is gone. |
| Add or list a job's tasks (work pipeline)                                              | The order is neither a draft nor published.                                                                                                                       |
| Integration data for Square with `type` `create_device_code` (a terminal pairing code) | Square refuses. Here `error.code` is a number, `422`.                                                                                                             |

### Refused with `200` and `"success": true`, but nothing done

Publishing a sales order or quote answers `200` with `"valid": false` when the order isn't ready. Check `data.valid` after you publish:

```json
{
  "status": 200,
  "success": true,
  "data": {
    "id": "V4keXwwxMQyXw7",
    "valid": false,
    "validation": {
      "has_customer": true,
      "has_order_items": false,
      "has_sellable_items": true,
      "unsellable_items": []
    }
  }
}
```

### Refused with `500`

| Operation                                                                                                | When                                                                                                                                                                  |
| -------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Delete a customer's or vendor's contact                                                                  | It's the primary contact (`delete_fail`).                                                                                                                             |
| Delete a currency, payment method, payment term, shipping box, shipping method, tax preset or fee preset | It's the team's default, or made by YoPrint (`delete_fail`).                                                                                                          |
| Create, delete or sync statuses                                                                          | The status type isn't allowed, such as `pre_production`, or a `start` or `pre_production` status is deleted.                                                          |
| Delete a sales order approval                                                                            | The approval is completed.                                                                                                                                            |
| Delete a system message template                                                                         | Always (`delete_fail`).                                                                                                                                               |
| Filter shipments                                                                                         | You filtered on `ship_due_date`, `order_external_type` or `shipment_source`.                                                                                          |
| Buy a GoShippo label                                                                                     | Shippo failed or timed out during the purchase. **A label may have been bought.** Don't retry. See [Ship an order](https://developer.yoprint.com/guides/shipping.md). |
| Get rates, carriers or estimates from GoShippo, or track a package                                       | Shippo refused the request or couldn't be reached.                                                                                                                    |

Each operation's reference page lists its own errors, including these.

> **Note:** If you get a status not listed here, treat it like a `503`: wait, then retry, reading back first if it was a change.
