错误、限流与重试
错误信封、稳定的 reason、限流与幂等重试。
更新于 10月28日
下面的错误信封和标为实测的 reason 都来自验收时的实测。每个已开放操作的状态码以 API 参考为准。
错误信封
公开 API 与 MCP 的 HTTP 错误共用一个 JSON 信封。下面是超出已授 scope 时真实返回的 403:
{
"statusCode": 403,
"code": "forbidden",
"reason": "external.scope.denied",
"message": "external.scope.denied",
"requestId": "fa027664-2221-4273-858b-a28e317431a0"
}
按 reason 分支;联系支持时附上 requestId。不要解析 message。401 还会带 WWW-Authenticate: Bearer。OAuth 端点返回标准的 {"error": "…"}。MCP 工具失败以 isError 结果返回,文本是 reason。日志里记 reason 和请求 id,不记令牌、原始材料或学生姓名。
reason 一览
验收实测出现过的:
| reason | 状态码 | 含义与下一步 |
|---|---|---|
external.token.invalid |
401 | 令牌缺失、过期或已撤销,或同时带了 Cookie。重新授权 |
external.audience.denied |
403 | 令牌是为另一个资源签发的。按需换用 public-api 或 mcp 令牌 |
external.workspace.denied |
403 | X-Workspace-Id 与授权不符。去掉该头,或为那个工作区另行授权 |
external.scope.denied |
403 | 未授该 scope。请教师重新授权 |
external.school.disabled |
403 | 学校关闭了外部客户端。须由学校开放 |
external.disabled |
404 | 本服务尚未开放 API |
public.not_found |
404 | 对象不存在,或本授权看不到 |
public.ingestion.file_unavailable |
409 | 要解析的文件还没就绪或已经用过。换一份新上传的文件 |
authorization_pending、slow_down |
400 | 设备轮询:继续等,或间隔加 5 秒 |
access_denied、invalid_grant |
400 | OAuth:被拒绝、学校已关闭,或授权码与 refresh 令牌已失效。停止 |
契约里定义、但验收没有触发的:external.rate_limited 与 public.quota.exhausted(429);public.idempotency.required、public.idempotency.invalid 与 public.request.invalid(400);public.idempotency.conflict、public.idempotency.expired、public.revision.stale、public.grade.preview_expired 与 public.file.not_uploaded(409);external.unavailable 与 public.unavailable(503)。
限流
公开 API 读取默认按授权(每分钟 60 次)、按教师(每分钟 120 次)和按工作区(每分钟 600 次)限流。超限返回 429 external.rate_limited,Retry-After 给出秒数;至少等这么久,不要并发重试。OAuth 端点按 IP、client 与凭据限流。授权用的密码重新验证按账号限次。验收没有触到这些上限;上面的数值来自默认配置,不是实测的 429。
幂等与重试
改变状态的写操作要带 Idempotency-Key 头,值为 UUID;MCP 写工具改用 idempotencyKey 参数。预览不需要。同一个键配同一个请求,返回原回执,不会执行第二次;同一个键配不同请求,返回 409 冲突。写请求超时或断线后,先用原来的键重试;换新键可能重复产生副作用。预览或场次版本过期时,重新读取并预览,再请教师确认当前的改动。重试不会让无权访问的对象变得可见。实际扣费与最终结果以服务端账本和任务回执为准。