开发环境里一切正常。接下来,你要写的是会跑在生产环境中的那部分——catch 块。第三方 API 返回的 HTTP 错误,单看往往并不透明:一个红色状态码摆在那里,却回答不了凌晨 3 点最关键的那个问题——到底是我的请求、我的密钥、我的套餐,还是他们的服务器出了问题?又有哪些该重试,哪些该直接抛给用户?
Lingo.dev 用统一结构回答了这个问题。所有错误——无论来自哪个端点、同步还是异步——都会返回同一个 JSON 对象,状态码也都来自同一张固定表。状态码不是标签,而是指令:它会告诉你该修正请求、更换密钥、充值账户、放慢请求节奏,还是直接重试。读懂状态码,就知道下一步怎么走。 只要一个基于状态码分支的错误处理器,就能覆盖整个 API。
本页内容
错误结构#
所有非 2xx 响应的 body 都是同一种格式:一个只包含单个 message 字段的 JSON 对象,用来说明哪里出了问题。
{
"message": "Invalid API key"
}这就是完整的约定。没有额外的包装层要拆,也没有按端点区分、需要特殊处理的错误格式。来自 /process/localize 的 400 和查询任务时返回的 404,结构完全一致——区别只有状态码和 message 文本。
按状态码判断,不要依赖消息文本
HTTP 状态码才是稳定信号——你的错误处理逻辑应该围绕它来分支。message 字符串是写给人看日志时理解用的;请把它当作说明文字,而不是机器可读的错误码,也不要依赖它的具体措辞去做模式匹配。
状态码#
总共 7 个状态码覆盖了所有响应。这里按由谁来解决问题分组——因为这个分组同时也决定了你的重试策略。
你发出的请求本身有问题(修正请求,不要盲目重试):
| 状态 | 含义 |
|---|---|
400 Bad Request | 请求校验失败——可能是缺少字段、locale 无效、callbackUrl 使用了 HTTP 而非 HTTPS,或 payload 格式有误。 |
401 Unauthorized | 缺少 X-API-Key 请求头,或其值无效。参见 身份验证。 |
403 Forbidden | 密钥有效,但无权访问所请求的资源。 |
404 Not Found | 资源——例如引擎、任务或任务组——不存在。 |
你的组织已触及账户限制(需要在计费侧解决):
| 状态 | 含义 |
|---|---|
402 Payment Required | 该组织已达到信用额度上限。 |
429 Too Many Requests | 该组织已达到每日令牌配额。升级套餐可提高这一上限。 |
我们这边出了问题(瞬时性错误——可重试):
| 状态 | 含义 |
|---|---|
500 Internal Server Error | 发生了意外故障——例如数据库错误,或该引擎配置的所有模型都未能完成这次翻译调用。 |
401 和 403 看起来相近,但并不是一回事:401 表示我们根本无法识别调用方,403 表示我们识别出了这个密钥,但它无权访问。401 的修复点在密钥本身(轮换或检查它);403 的修复点则在密钥权限。
哪些错误需要重试#
任何错误码表,谨慎的集成方第一个会问的,往往都是它最常没说清楚的那个问题:哪些错误该重试?上面的分组就是答案。
- 4xx——不要盲目重试。
400、401、403或404说明问题出在 你的 请求上。对完全相同的请求反复重试,只会得到完全相同的错误。先修正输入、密钥或资源 id,再重新发送。 - 402 和 429——先退避,再解决限制。 它们在请求层面都不是瞬时性错误;只要底层限制没变,下一次请求还是会撞上同一堵墙。不要在紧密循环里反复重试,而是要把限制明确暴露出来,并去解决它(充值,或升级套餐)。
- 500——使用退避策略重试。 这是唯一真正具有瞬时性的错误类型。
500可能意味着这次调用里所有已配置模型都超时了;而下一次重试,可能就会落到一个健康模型上。请使用指数退避,并设置重试上限。
异步 API 的结果上报方式不同
这套重试策略适用于你自己发起的同步调用。异步本地化 API 不会用状态码告诉你任务最终结果:POST 在请求被接受后会返回 202,随后每个目标 locale 都会作为独立任务,通过持久化后台工作流运行。你需要轮询任务状态,或通过 webhook 接收结果,而不是在原始调用上捕获某个状态码。参见 异步任务错误在哪里查看。
402 与 429:两种不同的限制#
这两个账户级状态码看起来很像——都像是在说“额度用完了”——但把它们混为一谈,会把开发者引向错误的解决方向。它们对应的是两种不同的限制,也有两种不同的处理方式:
402 Payment Required——组织已达到其信用额度上限。这是计费层面的边界。在你们组织的计费状态发生变化之前,后续调用都会继续失败。429 Too Many Requests——组织已达到其每日令牌配额。这是一个会重置的使用上限,你可以通过升级套餐来提高它。
之所以要在处理器里把它们分开:402 对应的是由人执行的计费操作;429 对应的是一个要么等待重置、要么通过升级来提高的配额。如果把两者都统一归到一个笼统的“支付问题”提示里,反而会掩盖操作人员真正需要拉动的是哪一个杠杆。
402 的响应体和其他错误看起来完全一样——真正告诉你这是信用额度限制的,是状态码本身:
{
"message": "Organization has reached its credit limit"
}异步任务错误在哪里查看#
这里有一条值得明确划出的分界线,因为从这里开始,状态码处理器就不再是合适的工具了。
本页列出的状态码都是传输层级的:它们描述的是 API 是否接受了你的 HTTP 请求,以及是否能够处理它。异步 API 返回的 202 表示你的请求已被接受——并不代表翻译已经成功。异步任务完全可能先被顺利接受,但随后在执行过程中因为模型超时而失败。这个失败不会作为原始调用的 HTTP 状态码返回;它会记录在任务本身上。
因此,异步失败会出现在三个地方,而不在这张表里:
- 单个任务状态。 失败的 locale 会在任务上带有
status: "failed"和一个errorMessage。参见 任务状态。 - 任务组状态。 当部分 locale 成功、部分失败时,任务组会报告
partial——已成功的 locale 仍会继续交付。参见 跟踪任务组。 - Webhook 投递。 失败会以
translation.failed事件形式投递,并附带一个error字段。参见 webhook 投递。
还有一个常让人混淆的区别:某个非关键性的 pipeline 阶段失败,不会导致任务失败。任务会以 completed_with_warnings 完成,并附带逐步警告,而不是错误。这属于流水线可观测性问题,不是错误码问题——参见 观察流水线运行。
后续步骤#
一个清晰的错误处理器,应从你在集成阶段最先遇到的两个方面入手——身份验证,以及异步任务是如何上报自身结果的。
