# Errors

> One machine-readable problem document for every failure, and what each type means for your retry logic.

Source: https://developers.backresto.com/en/docs/errors

Every failure (including a route that does not exist) answers with
`application/problem+json`, the shape defined by RFC 9457. There is no path
through this API that returns an HTML error page, so a parser that assumes JSON
on a non-2xx response is safe.

```json
{
  "type": "https://api.backresto.com/problems/forbidden",
  "title": "Forbidden",
  "status": 403,
  "detail": "The authenticated client lacks the required scope.",
  "instance": "/v1/restaurants/restaurant-1/collections/delivery-pictures/records/8f2c/assets",
  "requestId": "b1c8b0a4-6c1e-4f6b-9e1c-3b2a5d7f0e91"
}
```

`type` is the stable identifier: branch on it, not on `detail`, which is prose
and may be reworded. `requestId` is the value to quote in a support message.
It is also returned in the `X-Request-ID` response header, and you can set that
header yourself to correlate with your own traces.

## The catalogue

| `type` suffix | Status | What happened | Retry? |
| --- | --- | --- | --- |
| `invalid-request` | 400 | A parameter is missing or malformed: a `limit` out of range, an unknown collection, or a cursor that does not match the restaurant, collection and `limit` you are paging with. | No. Fix the request. |
| `unauthorized` | 401 | No `Authorization` header, or a key that is malformed, unknown, revoked, expired, or left with no grants at all. | No. The key needs attention. |
| `forbidden` | 403 | The key holds a grant for this restaurant but not the scope this endpoint needs. | No. A key carrying that scope has to be requested. |
| `not-found` | 404 | No grant for this restaurant, or no such record. The cases are deliberately indistinguishable. | No. |
| `conflict` | 409 | The request conflicts with an existing resource. | No. |
| `payload-too-large` | 413 | The request body exceeds the limit. | No. |
| `rate-limit-exceeded` | 429 | Your client passed its request budget. | Yes, after `Retry-After`. |
| `dependency-unavailable` | 503 | A component the API needs is briefly unavailable. | Yes, with backoff. |
| `internal-error` | 500 | An unexpected failure, already recorded on our side. | Once, with backoff; then tell us the `requestId`. |

## What a client should do

Three buckets, and nothing else is worth the code:

**Do not retry**: 400, 401, 403, 404, 409, 413. The request will fail again
identically. Log the `type` and the `requestId`, and surface it: a `401` in
particular means a human at your customer's end has revoked something, and a
retry loop will only hide that for a week.

**Retry with backoff**: 429, 503. Honour `Retry-After` on a 429; it is the
number of seconds until your budget refills, and it is not a suggestion. For
503, exponential backoff with jitter, and give up rather than hammering.

**Retry once, then escalate**: 500. If it recurs on the same request, it is
not transient.

Every endpoint in this API is a `GET`, so retrying is always safe from the
API's point of view; the reason not to retry a 4xx is that it wastes your
budget, not that it risks a double write.

## Correlating with your own logs

Send your own identifier and it comes back:

```bash
curl "https://api.backresto.com/v1/restaurants/$RESTAURANT_ID/collections/deliveries/records?limit=100" \
  -H "Authorization: Bearer $BACKRESTO_PARTNER_API_KEY" \
  -H "X-Request-ID: 3f7a1c9e-2b4d-4a86-9f0c-5e1d8b6a2c40" \
  --include
```

The value is echoed in the response header and in the `requestId` of a problem
document, which makes a single grep answer "what did BackResto see when my job
failed at 04:12?".
