错误与状态码
调用在开发环境中一切正常。现在,你要编写生产环境中的错误处理逻辑——catch 块。单看第三方 API 返回的 HTTP 错误,往往让人摸不着头脑:只有一个红色状态码,却回答不了凌晨三点最关键的问题——到底是我的请求、密钥、套餐出了问题,还是对方的服务器出了问题?这些错误中,哪些该重试,哪些该展示给用户?
Lingo.dev 用统一的结构给出答案。本组页面所述端点返回的每个错误,无论同步还是异步,都采用相同的 JSON 对象,状态码也都来自同一张固定的表。/v2 下的平台 API 则使用标准的问题详情格式,详见平台 API 错误。状态码不只是标签,更是行动指引:它告诉你应该修正请求、轮换密钥、为账户充值、退避等待,还是重试。读懂状态码,就知道下一步。一个按状态码分支处理的错误处理器,就能覆盖整个 API。
本页内容
错误结构#
每个非 2xx 响应都采用相同的响应体结构:一个 JSON 对象,仅包含一个描述出错原因的 message 字段。
{
"message": "Invalid API key"
}完整约定就这么简单。无需拆解外层封装,也无需针对不同端点的错误格式做特殊处理。/process/localize 返回的 400 与查询任务时返回的 404 结构相同——区别仅在于状态码和 message 文本。
按状态码匹配,而不是按消息文本
HTTP 状态码才是稳定可靠的信号,错误处理逻辑应以它为依据进行分支。message 字符串是写给查看日志的人看的;请将它视为描述,而不是机器可读的错误码,不要按其具体措辞进行模式匹配。
状态码#
七个状态码涵盖所有响应。这里按由谁解决分组,因为这也决定了你的重试策略。
你发送的请求存在问题(修正请求,不要盲目重试):
| 状态 | 含义 |
|---|---|
400 Bad Request | 请求验证失败:缺少字段、语言区域无效、callbackUrl 使用了 HTTP 而非 HTTPS,或请求载荷格式不正确。 |
401 Unauthorized | X-API-Key 标头缺失或无效。请参阅身份验证。 |
403 Forbidden | 密钥有效,但无权访问所请求的资源。 |
404 Not Found | 所请求的资源(引擎、任务或任务组)不存在。 |
你的组织已达到账户限制(需通过账单管理解决):
| 状态 | 含义 |
|---|---|
402 Payment Required | 组织已达到信用额度上限。 |
429 Too Many Requests | 组织已达到每日 token 配额。升级套餐可提高上限。 |
我们这边出现了故障(暂时性错误,可以重试):
| 状态 | 含义 |
|---|---|
500 Internal Server Error | 发生意外故障:数据库出错,或翻译调用在引擎配置的所有模型上均失败。 |
401 和 403 看似相近,实际却是不同的问题:401 表示我们完全无法识别调用方,403 则表示我们识别出了密钥,但该密钥无权访问。解决 401 要从密钥本身入手(轮换或检查密钥);解决 403 则要调整密钥的访问权限。
哪些错误应该重试#
审慎的集成开发者看到错误表时,首先会问一个通常被忽略的问题:这些错误中,哪些应该重试?上面的分组就是答案。
- 4xx——不要盲目重试。
400、401、403或404表示你的请求存在问题。原样重试,只会得到相同的错误。先修正输入、密钥或资源 ID,再重新发送。 - 402 和 429——先退避等待,再解决限制。从请求层面看,这些并非暂时性错误;只要相应的限制没有变化,下一个请求仍会碰壁。不要在循环中连续重试,而应展示限制信息,并解决问题(充值或升级套餐)。
- 500——采用退避策略重试。这是唯一真正属于暂时性错误的类别。
500可能意味着此次调用中所有已配置的模型都超时了;重试则可能调用到运行正常的模型。请使用指数退避,并设置重试次数上限。
异步 API 以不同方式报告结果
此重试策略适用于你自行发起的同步调用。异步本地化 API 不会通过状态码告知任务的最终结果:请求被接受后,POST 就会返回 202,随后每个目标语言区域都会通过持久化后台工作流,作为独立任务运行。你需要轮询任务或接收 webhook 来获取结果,而不是在原始调用中捕获状态码。请参阅在哪里查看异步任务错误。
402 与 429:两种不同的限制#
这两个账户级状态码看起来很像,都像是在说“用完了”。但混淆二者,就会让开发者找错解决方向。它们代表不同的限制,也需要不同的解决方法:
402 Payment Required——组织已达到信用额度上限。这是计费限制。在组织的计费状态发生变化之前,后续调用仍会失败。429 Too Many Requests——组织已达到每日 token 配额。这是会重置的用量上限,可通过升级套餐提高。
错误处理器需要区分二者,原因在于:402 需要人工处理计费问题;429 则是配额问题,可以等待配额重置,也可以通过升级套餐提高配额。如果一律显示笼统的“付款问题”,操作人员就无法知道究竟该采取哪种措施。
402 的响应体与其他错误相同,只有状态码能表明这是信用额度限制:
{
"message": "Organization has reached its credit limit"
}在哪里查看异步任务错误#
这里需要划清一条界限:越过这条界限,状态码处理器就不再适用了。
本页的状态码属于传输层:它们说明 API 是否接受了你的 HTTP 请求,以及能否处理该请求。异步 API 返回 202 只意味着请求已被接受,并不代表翻译成功。异步任务可能顺利被接受,却在后续运行中因模型超时而失败。这类失败不会体现为原始调用的 HTTP 状态码,而是记录在任务本身。
因此,异步失败会在以下三个地方体现,而不在本表中:
- 单个任务状态。某个语言区域处理失败时,对应任务会带有
status: "failed"和errorMessage。请参阅任务状态。 - 任务组状态。部分语言区域成功、其他语言区域失败时,任务组会报告
partial,成功的语言区域仍会正常交付。请参阅跟踪任务组。 - Webhook 投递。失败会以带有
error字段的translation.failed事件投递。请参阅webhook 投递。
还有一个容易混淆的区别:非关键的流水线阶段失败,不会导致任务失败。任务仍会以 completed_with_warnings 完成,并附带各步骤的警告,而不是错误。这属于流水线可观测性问题,而非错误码问题。请参阅观测流水线运行。
平台 API 错误#
https://api.lingo.dev/v2 下的平台 API 采用不同的错误返回方式:它使用 RFC 9457 问题详情格式,并以 application/problem+json 发送:
{
"type": "https://lingo.dev/docs/api/errors/glossary-in-use",
"status": 409,
"title": "Glossary in use",
"detail": "Glossary gls_1 cannot be deleted while 2 engines apply it. Detach it from those engines first.",
"id": "gls_1",
"engineCount": 2
}type用于标识问题,是指向该问题专属页面的链接。该页面列出了问题的字段,以及会返回该问题的端点。请根据type进行分支处理。status重复列出 HTTP 状态码。title是简短摘要,同一类型的问题每次出现时,摘要都相同。detail说明本次发生了什么,以及接下来该怎么做。它是写给人看的,因此应直接展示,而不是解析。- 其他成员包含本次问题的具体信息,例如上面的
id和engineCount。detail仅由这些信息生成,因此客户端可以根据type和这些字段,用自己的语言生成文本。
请求验证失败时会返回 400,并在 errors 下以 { "path", "message" } 对的形式列出每个无效值。每个端点还可能返回 service-unavailable(503),这类错误可以安全地采用退避策略重试;也可能返回 internal-server-error(500)。
各状态码分别对应以下规则:
| 状态 | 含义 |
|---|---|
401 | 未能识别调用方。 |
402 | 套餐或权益限制,可通过升级或充值解除。 |
403 | 已识别调用方,但不允许执行此操作。 |
404 | 资源不存在,或调用方无法查看该资源。 |
409 | 请求与当前状态冲突,例如资源仍在使用中。 |
422 | 请求格式正确,但被某项规则拒绝。 |
429 | 会随时间重置的限制。 |
502 | 上游提供方发生故障。 |
503 | 我们这边出现了暂时性故障。请采用退避策略重试。 |
后续步骤#
要写好错误处理器,可以先从集成时最早遇到的两个问题入手:身份验证,以及异步任务在何处报告自身结果。