> ## Documentation Index
> Fetch the complete documentation index at: https://apidocs.ipxo.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Errors

> The status codes the API returns, and what to do about each one.

Every request passes through the IPXO API gateway before it reaches a service. The
gateway handles authentication, authorization and rate limiting, so a request can
fail before the service ever sees it. Gateway failures and service failures look
slightly different, and it is worth being able to tell them apart.

## Gateway errors

The gateway returns a JSON problem document:

```json theme={null}
{
  "type": "about:blank",
  "title": "Rate limit is exceeded. Try again in 30 seconds.",
  "status": 429,
  "context": {},
  "errors": {}
}
```

Read `title` for the human-readable reason and `status` for the HTTP status code.

### Authentication and authorization

| Status | Meaning                                                                                                                       | What to do                                                           |
| ------ | ----------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| `400`  | No `Authorization` header was sent. The response carries `WWW-Authenticate: Bearer error="invalid_request"`.                  | Send `Authorization: Bearer <access_token>`.                         |
| `401`  | The token is expired, revoked, or not an access token. The response carries `WWW-Authenticate: Bearer error="invalid_token"`. | Request a new token. See [Authentication](/guides/auth).             |
| `401`  | The token is valid, but it is not permitted to perform this action on this resource.                                          | Check that the `tenant_uuid` in the path is one your app belongs to. |

<Note>
  A missing token returns `400`, not `401`. This is unusual, and worth handling
  explicitly if you are branching on status codes.
</Note>

The `WWW-Authenticate` header distinguishes the two cases: `error="invalid_request"`
means nothing was sent, `error="invalid_token"` means what was sent is no longer
good. Both are worth retrying only after obtaining a fresh token.

### Rate limiting

A `429` means you have exceeded the request rate. If the response carries a
`Retry-After` header, wait that many seconds; the value is also interpolated into
`title`. Otherwise back off before retrying.

```json theme={null}
{
  "type": "about:blank",
  "title": "Rate limit is exceeded. Try again in 30 seconds.",
  "status": 429,
  "context": {},
  "errors": {}
}
```

## Service errors

Once a request is authenticated and authorized, the service handles it and returns
its own status codes:

| Status | Meaning                                                           |
| ------ | ----------------------------------------------------------------- |
| `400`  | The request was malformed or a parameter failed validation.       |
| `403`  | The action is not allowed for this resource in its current state. |
| `404`  | The resource does not exist, or does not belong to this tenant.   |
| `406`  | The requested representation is not available.                    |
| `422`  | The request was well-formed but semantically invalid.             |
| `500`  | The service failed. Safe to retry idempotent requests.            |

<Warning>
  Error response bodies are not yet uniform across services. Where a service
  documents a body shape, it is shown on that endpoint's own reference page — do
  not assume one shape across all of them. Parse defensively and rely on the HTTP
  status code as the primary signal.
</Warning>

Some services return a body of the form:

```json theme={null}
{
  "status": "string",
  "code": 0,
  "error": "string"
}
```

## Retrying safely

`GET` requests are safe to retry. For requests that create or modify something —
adding a cart line, checking out, creating a ROA — a `500` or a network timeout
leaves the outcome unknown. Re-read the resource to check whether the change
landed before retrying, rather than sending the same write twice.
