---
url: /docs/api-docs/v1/oauth/error-codes.md
---
# OAuth 错误码矩阵（40101xxx）

> ⚠️ **所有业务错误 HTTP 状态码都是 200**（除非用户被封禁返回 403）。前端必须按 `body.code` 而非 HTTP status 判断业务错误类型。
>
> HTTP 400 仅用于 **入参校验失败**（Pydantic 首 issue），HTTP 403 仅用于 **账号被封禁**。

## 错误响应统一格式

```json
{
  "code": "40101003",
  "msg": "短信验证码错误",
  "data": null
}
```

## 错误码全量矩阵

| code | 含义 | 触发 action | 前端 UI 文案 | 后续动作 |
|---|---|---|---|---|
| `40001001` | 入参校验失败（HTTP 400） | 所有 | `<字段路径>: <原因>`（如 `phone: invalid phone`） | 修正表单 |
| `40101001` | 短信码不存在 / 已过期 / 已被 3 次错作废 | sms-verify, sms-login, bind-phone | 「验证码已失效，请重新获取」 | 引导回 1.2 重发 |
| `40101003` | 验证码错误（图形码 或 短信码，msg 区分） | sms-send, sms-verify, sms-login, captcha 校验 | 图形码：「图形验证码错误」短信码：「短信验证码错误」 | 图形码：刷新 captcha短信码：可继续试（接近 3 次要警告） |
| `40101004` | 图形验证码已过期（>120s） | sms-send | 「图形验证码已过期」 | 静默刷新 captcha |
| `40101005` | 限流（三层任一：IP 30/min / 手机号 60s/1 / 手机号 24h/10） | sms-send | msg 区分：「操作过于频繁」/「60s 内已发送」/「已达日上限」 | 按 msg 提示，倒计时保持禁用 |
| `40101006` | SMS 通道故障 / **凭据未配置期** | sms-send | 「短信通道暂不可用，请稍后再试」 | 重新拉 captcha + 退到登录页 |
| `40101007` | 微信绑定失败 / **unbind 解绑后无登录方式** | callback, unbind | 「绑定失败，请重试」 / 「至少保留一种登录方式」 | callback：重新走 SDK 授权unbind：UI 提前禁用 |
| `40101008` | 协议未同意（agreementAccepted 非 literal true） | sms-login, callback | 「请先同意用户协议」 | 提示勾选 |
| `40101010` | 微信接口失败 / **微信凭据未配置** | callback | 「微信登录暂不可用」 | 手机号登录兜底 |
| `40101011` | rotate 验签失败 / binding 非 active / 密文缺失 / 用户不存在 | rotate | 「登录已失效」 | **清空本地会话 → 登录页，禁止重试** |
| `40101012` | rotate 重放（签名合法但与库内不匹配 = 旧 refresh 二次使用） | rotate | 「登录已在其他设备更新」 | ⚠️ 服务端已**连坐吊销该设备会话**，前端唯一动作：清会话进登录页 |
| `40301001` | 账号被封禁（HTTP 403） | 所有需鉴权 action | 「账号已被封禁，请联系客服」 | 不允许重试 |
| `50001001` | 服务异常（HTTP 200） | 所有 | 「服务异常，请稍后重试」 | 退到登录页 |

## 短信码「错」vs「不存在」必须区分

| code | 语义 | UI 文案 |
|---|---|---|
| `40101003` | 码错误（可继续试，attempts+1） | 「短信验证码错误，还剩 X 次机会」 |
| `40101001` | 码不存在 / 过期 / 已被 3 次错作废 | 「验证码已失效，请重新获取」 |

混淆这两个 code 会导致：

* 用户看到「错误」却必须重新发码（体验差）
* 用户看到「不存在」却继续尝试（白白消耗次数）

## OATH-M-006 历史教训

**背景**：早期生产环境 errorHandler 分支顺序错误，导致业务码被吞成 HTTP 状态码（`{code:400/401}`），前端所有业务码判断失效。

**修复**：

* 业务异常**最先**注册 ExceptionHandler
* HTTPException handler 只能兜底
* 业务错误固定 HTTP 200（封禁除外）

**前端验证**：服务端联调时务必打印原始响应 JSON，确认 `code` 字段是字符串 `40101003` 而非 HTTP status。

***

## 错误码分配规则

| 区段 | 用途 | 备注 |
|---|---|---|
| `40001001-40001999` | 入参校验 | Zod / Pydantic 通用 |
| `40101001-40101099` | OAuth 业务码 | 内部占用，外部对接仅看到字符串值 |
| `40101100-40101999` | 其他 auth 模块预留 | 密码登录、未来手机号+密码双因子等 |
| `40301001-40301999` | 账号状态 | 封禁 / 锁定 / 注销 |
| `50001001-50001999` | 服务异常 | 兜底 |

**新增错误码约定**：

1. 必须先在 [🟢 Internal Wiki → 错误码体系](/internal-wiki/architecture/error-codes) 登记
2. msg 字段必须能直接展示给用户
3. code 区段不可跨模块复用
4. 任何 code 变更需要同步更新 [Internal Wiki → 错误码体系](/internal-wiki/architecture/error-codes) + 本文件 + 单测 fixture
