# 错误

> 每一次失败都返回同一种机器可读的 problem 文档，以及每种类型对你的重试逻辑意味着什么。

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

每一次失败（包括访问一条并不存在的路由）都以 `application/problem+json` 作答，
也就是 RFC 9457 定义的那种结构。这个 API 里没有任何一条路径会返回 HTML 错误页面，
所以一个在非 2xx 响应上直接假定是 JSON 的解析器是安全的。

```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` 是那个稳定的标识符：按它做分支，而不是按 `detail`。后者是散文，措辞可能会
改。`requestId` 是写支持邮件时值得附上的值。它同时也在 `X-Request-ID` 响应头里
返回，而且你可以自己设置这个请求头，用来和你自己的追踪对应起来。

## 类型一览

| `type` 后缀 | 状态码 | 发生了什么 | 要重试吗？ |
| --- | --- | --- | --- |
| `invalid-request` | 400 | 某个参数缺失或格式不对：一个超出范围的 `limit`、一个未知的集合，或者一个与你正在翻页的餐厅、集合和 `limit` 对不上的游标。 | 不要。把请求改对。 |
| `unauthorized` | 401 | 没有 `Authorization` 请求头，或者密钥格式不对、未知、已吊销、已过期，又或者一份授权都不剩了。 | 不要。这把密钥需要处理。 |
| `forbidden` | 403 | 密钥对这家餐厅有授权，但没有这个端点需要的权限范围。 | 不要。需要另外申请一把带这个权限范围的密钥。 |
| `not-found` | 404 | 对这家餐厅没有授权，或者没有这条记录。这几种情形被有意做成无法区分。 | 不要。 |
| `conflict` | 409 | 请求与一个已有的资源冲突。 | 不要。 |
| `payload-too-large` | 413 | 请求体超出了上限。 | 不要。 |
| `rate-limit-exceeded` | 429 | 你的客户端用超了它的请求额度。 | 要，等过了 `Retry-After` 再重试。 |
| `dependency-unavailable` | 503 | API 需要的某个组件短暂不可用。 | 要，带退避。 |
| `internal-error` | 500 | 一次意外的失败，我们这边已经记录下来了。 | 重试一次，带退避；之后把 `requestId` 告诉我们。 |

## 客户端该怎么做

三类，除此之外的代码都不值得写：

**不要重试**：400、401、403、404、409、413。这个请求会以完全一样的方式再失败一次。
把 `type` 和 `requestId` 记下来，并把它显示出来：尤其是 `401`，它意味着你客户那边
有个人吊销了什么，而一个重试循环只会把这件事掩盖一个星期。

**带退避重试**：429、503。在 429 上遵守 `Retry-After`；它是你的额度重新充满还需要
的秒数，不是一个建议。对 503，用带抖动的指数退避，而且宁可放弃也不要一直猛敲。

**重试一次，然后上报**：500。如果它在同一个请求上再次出现，那就不是暂时性的。

这个 API 里的每个端点都是 `GET`，所以从 API 的角度看，重试永远是安全的；不重试
4xx 的理由是它浪费你的额度，而不是它有写两次的风险。

## 与你自己的日志对应起来

发出你自己的标识符，它会原样回来：

```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
```

这个值会在响应头里、也会在一份 problem 文档的 `requestId` 里回显出来，这让一次
grep 就能回答「我的任务在 04:12 失败的时候，BackResto 看到的是什么？」。
