# 错误码与请求 ID

本文是平台接口错误的参考：错误信封的字段、每个稳定错误码的含义与处理方式、开发者平台接口（`/api/dev`）的错误，以及怎样用请求 ID 查到一次调用。错误信封里的 `docUrl` 指向本页上对应错误码的位置。

## 错误信封

HTTP 状态码 ≥ 400 时，`/open/v1` 与 `/app/v1` 的接口都返回同一个嵌套的错误信封：

```json
{
  "error": {
    "code": "scope_required",
    "message": "this call needs the scope platform:ping",
    "requestId": "9f2c7a3b4e1d5f60",
    "docUrl": "https://dev.${DOMAIN}/docs/errors#scope_required",
    "details": {}
  }
}
```

- `code`：稳定的错误类型标识。程序按它分支，不按 `message` 分支。
- `message`：给人看的说明，语言随请求头 `Accept-Language`。
- `requestId`：这次调用的标识，与响应头 `X-Request-Id` 相同。
- `docUrl`：本页上这个错误码的地址，形如 `https://dev.${DOMAIN}/docs/errors#<code>`。
- `details`：结构化的补充信息；没有时是 `{}`。例如 `ambiguous_customer` 在 `details.candidates` 里给出候选客户。

两个 SDK 都把信封读成一个错误值：Go 是 `*raise.Error`（`err.Code`、`err.RequestID`），TypeScript 是 SDK 抛出的错误对象。

## 常见错误码

| `code` | HTTP | 含义 | 怎么办 |
|---|---|---|---|
| `invalid_request` | 400 | 请求格式非法或请求正文超过限制（如非 JSON 格式或超过 4 KiB） | 检查并修正请求正文格式，减小体积后重试。 |
| `unauthorized` | 401 | 缺少认证信息或身份验证未通过 | 检查认证头格式与凭据是否有效。 |
| `forbidden` | 403 | 权限不足或拒绝访问 | 检查当前凭据是否具备操作该资源的权限。 |
| `not_found` | 404 | 请求的资源或路径不存在 | 核对请求 URL 路径及资源 ID 是否正确。 |
| `method_not_allowed` | 405 | 该端点不支持当前 HTTP 方法 | 更换为端点允许的 HTTP 请求方法。 |
| `conflict` | 409 | 资源当前状态与该操作存在冲突 | 检查业务对象最新状态后重试。 |
| `payload_too_large` | 413 | 请求正文超出接口允许的最大限制 | 缩减正文大小或分批次提交。 |
| `validation_failed` | 422 | 请求参数或业务数据校验失败 | 查看 `details` 字段中的错误信息并修正字段。 |
| `rate_limited` | 429 | 凭证或接口调用频次超出限制 | 读取 `Retry-After` 头，等待指定秒数后再发起请求。 |
| `internal` | 500 | 服务端处理发生内部异常 | 稍后重试，持续失败时提供 `requestId` 联系平台排查。 |
| `not_implemented` | 501 | 该功能或接口在当前环境中未实现或未接入 | 确认当前系统部署是否已启用该服务依赖。 |
| `unavailable` | 503 | 服务暂时不可用或未能读取到凭据/客户数据 | 采用退避算法稍后重试。 |
| `token_required` | 401 | 请求未携带 `Authorization: Bearer` 令牌头 | 先调用令牌端点换取访问令牌，并在请求头中附带。 |
| `invalid_token` | 401 | 访问令牌无效、签名错误或已过期 | 重新换取有效的访问令牌后重试。 |
| `invalid_credential` | 401 | 客户端 ID 或密钥错误，或前缀格式不正确 | 检查传入的客户端 ID 与 Secret 是否正确。 |
| `invalid_client` | 401 | OAuth 端点客户端身份认证失败 | 检查 Basic 认证头或正文中的密钥参数。 |
| `unsupported_grant_type` | 400 | 不支持的授权类型参数 | 仅使用 `client_credentials` 或 `urn:raise:grant-type:customer`。 |
| `tenant_required` | 400 | 换取令牌时未提供租户参数 | 商店应用在换取令牌时正文必须包含 `tenant` 参数。 |
| `wrong_environment` | 403 | 凭证与目标租户的环境不匹配 | 测试环境密钥仅能访问测试企业，正式密钥仅能访问真实租户。 |
| `credential_ended` | 401 | 凭证已吊销、过期或租户已被暂停/关闭 | 更换有效凭证，或联系管理员恢复租户状态。 |
| `address_not_allowed` | 403 | 请求来源 IP 不在凭据的 IP 白名单中 | 确认来源 IP，在开发者平台将出口 IP 加入白名单。 |
| `not_installed` | 403 | 目标租户未安装该应用，或安装已被停用/卸载 | 请租户管理员在工作台确认已安装并启用应用。 |
| `no_scope` | 403 | 该凭证或应用当前未被授予任何有效权限范围 | 联系租户管理员在管理后台为应用配置并保存权限范围。 |
| `scope_required` | 403 | 当前调用缺少对应接口要求的权限范围 | 申请所需权限范围并由管理员同意，然后换取新令牌。 |
| `customer_token_required` | 403 | 该端点要求客户令牌，当前出示的是租户令牌 | 先通过客户手机号断言换取客户令牌后再调用。 |
| `assertion_stale` | 401 | 客户断言已超过 10 分钟（写操作安全限制） | 使用客户手机号重新换取客户令牌后重试写操作。 |
| `invalid_phone` | 422 | 手机号格式无法识别 | 提供大陆 11 位有效手机号或符合 E.164 规范的国际号码。 |
| `ambiguous_customer` | 409 | 该手机号在租户中匹配到多个客户档案 | 从 `details.candidates` 选取客户 ID，并在请求中带上 `customer_id`。 |
| `too_many_attempts` | 429 | 同一 IP 一分钟内认证失败次数满 30 次 | 等待 `Retry-After`（60 秒）后方可重新尝试。 |
| `idempotency_conflict` | 409 | 相同幂等键附带了不同的请求方法、路径或正文 | 更换全新的 `Idempotency-Key` 重新提交。 |
| `idempotency_in_progress` | 409 | 相同幂等键的首个请求仍在处理中 | 稍作等待，切勿使用不同参数重试。 |
| `condition_failed` | 412 | 并发写入或版本前置条件检查不满足 | 重新拉取最新数据版本后再提交更新。 |

表里没有的 `code` 按 HTTP 状态处理：4xx 先改请求，5xx 用退避重试。

## 开发者平台接口的错误

开发者平台接口（`https://dev.${DOMAIN}/api/dev/…`，门户、`k3` 命令行和 CI 调用的接口）的错误也用上面的信封，`code` 按 HTTP 状态取通用的几个：

| HTTP | `code` | 常见原因 | 怎么办 |
|---|---|---|---|
| 400 | `invalid_request` | 请求正文不是合法的 JSON | 修正正文。 |
| 401 | `unauthorized` | 没有令牌，或令牌过期、无效 | 重新登录门户，或用 API 凭证重新换令牌，见 [认证与凭证](/developers/guide/auth)。 |
| 403 | `forbidden` | 角色不够，或账号不属于一个启用中的开发者组织 | 见下文「角色不够」。 |
| 404 | `not_found` | 资源不存在，或属于别的开发者组织（两种情况答得一样） | 核对 ID 和所在的组织。 |
| 409 | `conflict` | 与当前状态冲突，例如应用 ID 已被别的组织占用、测试企业里还没装这个应用 | 读 `message`，先做它说的前一步。 |
| 409 | `app_id_reserved` | 应用 ID 格式正确但被保留：平台名称，或只能由平台运营转交的目录应用（如 `distribution`） | 换一个自己的 ID；要接手目录应用，请平台运营转交。 |
| 422 | `validation_failed` | 字段不合规则，例如应用 ID 的格式，或测试企业管理员的邮箱已是平台的账号（`adminEmail "…" is already in use`） | 读 `message` 修正字段。 |
| 501 | `not_implemented` | 这个部署没有开这项服务 | 联系平台运营。 |
| 503 | `unavailable` | 依赖的服务暂时不可用，例如部署不能发邮件时的自助注册 | 稍后重试；自助注册见 [快速开始](/developers/guide/quickstart)。 |

`message` 写明具体原因。较早的平台版本在 `/api/dev` 上返回平铺的 `{"error": "…"}`，没有 `code` 和 `requestId`，请求 ID 只在响应头 `X-Request-Id` 里；要兼容这样的平台，`error` 是字符串时把它当作 `message`。

### 角色不够

开发者平台接口只接受开发者组织的成员，并且要有开发者角色。HTTP 403 的 `message` 有两种：

- `requires one of: isv.developer`：令牌里没有在你所属组织生效的开发者角色。角色只在你账号所属的那个组织里算数，在别的组织授予的不算。请组织管理员在门户的「团队」页确认你的账号有「开发者」角色。API 凭证建出来就带开发者角色。
- `your account does not belong to an active developer organisation`：你的账号所在的组织不是开发者组织，或者这个开发者组织已被停用。

令牌里有 `isv.developer`、平台仍然答 403 时，把 `requestId`（或响应头 `X-Request-Id`）交给平台运营排查。

## 用请求 ID 排查

每个响应都带 `X-Request-Id`，出错时它也在信封的 `requestId` 里。向平台报障时附上它。你自己也可以用它查这次调用：

- 开发者门户：应用详情的「日志」页签列出每次调用和它的请求 ID，「错误」页签列出应用的错误。
- 命令行：`k3 logs <应用 ID>` 列出调用，`k3 logs <应用 ID> --errors` 列出错误分组，见 [本地开发与测试企业](/developers/guide/local-dev)。
- 接口：`GET https://dev.${DOMAIN}/api/dev/apps/${APP_ID}/requests/${REQUEST_ID}` 返回带这个请求 ID 的错误记录与调用日志，两个环境都查；查不到时是两个空列表。

```bash
k3 api GET /api/dev/apps/${APP_ID}/requests/${REQUEST_ID}
```

正式环境的记录不写是哪家企业；测试环境只写出你自己组织的测试企业。
