调用 API 时,401 Unauthorized、403 Forbidden 和 429 Too Many Requests 是最常见的三类错误。
它们都属于 4xx 客户端错误,但解决方法完全不同:
- 401:认证失败——服务器无法确认“你是谁”
- 403:访问被拒绝——服务器理解请求,但不允许执行
- 429:请求过多——调用速度或用量超过限制
如果不区分错误类型就直接重试,不仅无法解决问题,还可能制造重试风暴,甚至导致接口被进一步限制。
本文将通过排障清单、curl 命令和 Node.js 代码,说明遇到这三类错误时应该如何处理。
¶ 一张表看懂 401、403 和 429
| 状态码 | 核心含义 | 常见原因 | 是否应该立即重试 |
|---|---|---|---|
401 Unauthorized |
缺少有效认证凭证 | API Key 错误、Token 过期、认证头格式错误 | 不应该原样重试 |
403 Forbidden |
服务器拒绝处理请求 | 权限不足、IP 或地域限制、资源策略、WAF 拦截 | 通常不应该 |
429 Too Many Requests |
请求超过速率或配额限制 | 并发过高、QPS 超限、额度耗尽 | 等待后重试 |
需要注意,虽然 401 的英文名称是 Unauthorized,但在实际排障时,更适合把它理解为“未通过认证”;403 才通常与授权或访问策略有关。
¶ 一、401 Unauthorized:先检查认证凭证

HTTP 401 Unauthorized 表示请求缺少目标资源所需的有效认证凭证。标准的 401 响应通常会通过 WWW-Authenticate 响应头告诉客户端服务器期望使用的认证方案。
¶ 常见原因
- 没有发送
Authorization请求头 - API Key 或 Access Token 填写错误
- Token 已过期或被撤销
- 认证方案错误,例如遗漏
Bearer - 环境变量没有正确加载
- 使用了错误环境的密钥
- 请求被代理或网关转发时,认证头丢失
- 客户端与服务器时间偏差过大,导致签名失效
¶ 常见错误
接口要求 Bearer Token:
Authorization: Bearer YOUR_ACCESS_TOKEN
但客户端发送成了:
Authorization: YOUR_ACCESS_TOKEN
或者错误地写成:
Authentication: Bearer YOUR_ACCESS_TOKEN
这两种情况都可能返回 401。
¶ 使用 curl 排查
不要一开始只查看响应体。加上 -i,同时检查状态码和响应头:
curl -i "https://api.example.com/v1/profile" \
-H "Authorization: Bearer $API_TOKEN" \
-H "Accept: application/json"
重点检查:
- 是否确实发送了
Authorization - 认证方案是否正确
- Token 前后是否存在空格或换行
WWW-Authenticate返回了什么- 响应体中是否有
invalid_token、expired_token等错误信息
如需查看更详细的请求过程,可以使用:
curl -v "https://api.example.com/v1/profile" \
-H "Authorization: Bearer $API_TOKEN"
注意:调试日志可能包含 Token。不要把完整日志直接发到公开 Issue、群聊或工单中。
¶ 401 应该重试吗?
不要使用同一份无效凭证反复重试。
推荐流程:
- 检查请求是否携带认证信息
- 检查 Token 是否过期、撤销或复制错误
- 如果支持 Refresh Token,刷新一次访问令牌
- 使用新 Token 重试一次
- 如果仍然返回 401,停止重试并记录错误
如果使用固定 API Key,则应检查密钥状态或更换有效密钥,而不是循环重试。
¶ 二、403 Forbidden:认证可能成功,但请求仍被拒绝

403 Forbidden 表示服务器已经理解请求,但拒绝处理。对于典型的权限错误,即使重新认证,也不会自动解决问题;如果不修改请求或权限配置,重复请求通常仍会失败。
¶ 常见原因
- 当前账号或密钥缺少所需权限
- Token 的 Scope 不足
- 访问了不属于当前项目或租户的资源
- 来源 IP 不在白名单内
- 请求来源地域受到限制
- WAF、防火墙或反滥用系统拦截
- 账号、项目或资源被停用
- 接口仅向特定套餐或角色开放
- 签名有效,但签名中的资源范围不匹配
- 浏览器场景下受到 CSRF、Origin 或 Referer 策略限制
¶ 401 与 403 的区别
可以用两句话快速判断:
- 401:服务器无法确认你是谁
- 403:服务器知道或理解你的身份与请求,但不允许你这么做
例如,一个普通成员携带有效 Token 调用“删除组织”接口:
curl -i -X DELETE \
"https://api.example.com/v1/organizations/org_123" \
-H "Authorization: Bearer $API_TOKEN"
如果 Token 有效,但账号不是组织管理员,服务器可能返回:
HTTP/1.1 403 Forbidden
Content-Type: application/json
{
"error": "insufficient_permissions",
"message": "Administrator role is required."
}
¶ 403 排障顺序
¶ 1. 阅读响应体
优先寻找错误代码、缺少的角色或权限、所需 Scope、被拒绝的资源 ID,以及 IP、地域或安全策略提示。
¶ 2. 检查 Token Scope
例如,读取数据可能只需要 data:read,但创建数据可能需要 data:write。Token 有效不代表拥有全部权限。
¶ 3. 检查资源归属
确认项目 ID、组织 ID、租户 ID、用户 ID、区域或环境与当前凭证属于同一个账号或授权范围。
¶ 4. 检查网络和安全策略
如果代码在本地运行正常,部署后却返回 403,应重点检查服务器出口 IP、IP 白名单、代理配置、CDN、WAF、请求来源地域和相关请求头。
¶ 不要只凭状态码下结论
现实中的 API 实现不一定完全一致。某些平台在触发限流时可能返回 403,而不是 429。例如,GitHub REST API 可能在超过速率限制时返回 403 或 429,并通过 x-ratelimit-remaining、x-ratelimit-reset 或 retry-after 等响应头说明限制状态。
因此,遇到 403 时还要综合检查:
- 响应体中的业务错误码
Retry-AfterX-RateLimit-RemainingX-RateLimit-Reset- 平台官方文档
¶ 403 应该重试吗?
对于权限不足、IP 限制或资源策略导致的 403,原样重试没有意义。
正确做法是修改权限或 Token Scope、使用正确账号或项目、调整白名单与安全策略、修正资源 ID,并确认服务或套餐是否支持该接口。只有在平台明确说明该 403 实际代表临时限流时,才应按限流规则等待后重试。
¶ 三、429 Too Many Requests:先减速,再重试

429 Too Many Requests 表示客户端在一段时间内发送了过多请求,也就是触发了速率限制。服务器可以通过 Retry-After 告诉客户端应等待多长时间再重试。
¶ 常见原因
- 短时间内请求次数过多
- 并发任务数量过高
- 多个服务共享同一 API Key
- 重试代码没有等待机制
- 定时任务在同一时刻集中执行
- 请求没有缓存,重复获取相同数据
- 单个账号、IP 或项目的配额已耗尽
- 套餐用量或余额限制
¶ 首先检查响应头
curl -i "https://api.example.com/v1/resources" \
-H "Authorization: Bearer $API_TOKEN"
重点关注:
Retry-After: 30
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1710000000
不同平台的响应头名称可能不同,应以平台文档为准。
¶ 正确处理 Retry-After
Retry-After 常见的两种格式是秒数或 HTTP 日期:
Retry-After: 30
Retry-After: Wed, 21 Oct 2026 07:28:00 GMT
客户端最好同时兼容这两种格式。
¶ 为什么不能立即重试?
如果请求被限流后立即重试,新请求仍会被限流;多个客户端还可能同时重试,形成“惊群”。更稳妥的方法是:
- 优先遵守
Retry-After - 没有明确等待时间时,使用指数退避
- 在等待时间中加入随机抖动
- 设置最大重试次数
- 达到上限后返回明确错误
¶ Node.js:安全处理 429
下面的示例使用 Node.js 内置的 fetch,优先解析 Retry-After;如果服务器没有提供该响应头,则使用带随机抖动的指数退避。
const sleep = (ms) =>
new Promise((resolve) => setTimeout(resolve, ms));
function parseRetryAfter(value) {
if (!value) return null;
const seconds = Number(value);
if (Number.isFinite(seconds) && seconds >= 0) {
return seconds * 1000;
}
const timestamp = Date.parse(value);
if (!Number.isNaN(timestamp)) {
return Math.max(0, timestamp - Date.now());
}
return null;
}
async function fetchWithRetry(url, options = {}, config = {}) {
const {
maxRetries = 3,
baseDelayMs = 1000,
maxDelayMs = 30_000,
} = config;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const response = await fetch(url, options);
if (response.status !== 429) {
return response;
}
if (attempt === maxRetries) {
throw new Error(
`请求持续受到限流,已达到最大重试次数:${maxRetries}`
);
}
const retryAfterMs = parseRetryAfter(
response.headers.get("retry-after")
);
const backoffCap = Math.min(
maxDelayMs,
baseDelayMs * 2 ** attempt
);
const jitterMs = Math.floor(Math.random() * backoffCap);
const delayMs = retryAfterMs ?? jitterMs;
console.warn(
`收到 429,第 ${attempt + 1} 次等待 ${delayMs}ms 后重试`
);
await sleep(delayMs);
}
throw new Error("请求失败");
}
调用示例:
const response = await fetchWithRetry(
"https://api.example.com/v1/resources",
{
headers: {
Authorization: `Bearer ${process.env.API_TOKEN}`,
Accept: "application/json",
},
},
{
maxRetries: 3,
baseDelayMs: 1000,
maxDelayMs: 30_000,
}
);
if (!response.ok) {
const errorBody = await response.text();
throw new Error(`API 请求失败:${response.status} ${errorBody}`);
}
const data = await response.json();
console.log(data);
¶ POST 请求重试时,还要考虑幂等性
并非所有请求都适合自动重试。GET、HEAD 等读取请求通常更容易安全重试。对于创建订单、扣款、发送消息等 POST 请求,客户端可能已经成功提交,但因为网络中断没有收到响应。此时直接重试,可能造成重复操作。
如果平台支持,应为写操作设置幂等键:
curl -i -X POST "https://api.example.com/v1/orders" \
-H "Authorization: Bearer $API_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-20260331-0001" \
-d '{
"product_id": "prod_123",
"quantity": 1
}'
幂等键的请求头名称和有效期由具体平台决定,应参考相应 API 文档。
¶ 生产环境排障清单
¶ 收到 401
- 请求中是否包含认证头
-
Bearer等认证方案是否正确 - Token 是否过期、撤销或复制错误
- 是否使用了正确环境的密钥
- 代理或网关是否删除了认证头
- 客户端时间是否准确
- 是否可以安全地刷新一次 Token
¶ 收到 403
- Token 是否具有所需 Scope
- 当前账号是否具备所需角色
- 资源是否属于当前项目或租户
- 出口 IP 是否在白名单中
- 是否受到地域、WAF 或安全策略限制
- 响应体是否明确提示权限或限流
- 响应头中是否存在限流信息
¶ 收到 429
- 是否读取了
Retry-After - 是否检查限流相关响应头
- 是否使用指数退避和随机抖动
- 是否设置最大重试次数
- 是否限制并发
- 是否缓存重复查询结果
- 是否可以合并或批量发送请求
- 是否达到套餐配额或账户用量上限
¶ 建议记录哪些诊断信息?
建议在日志中记录请求时间、API 主机名和路径、HTTP 方法、状态码、业务错误代码、request-id 或 trace-id、Retry-After、限流剩余额度与重置时间、重试次数和实际等待时间。
但不要记录完整 API Key、完整 Access Token、Refresh Token、用户密码或请求体中的敏感个人信息。
¶ 最后的判断口诀
401:先修凭证,不要拿旧 Token 重试。
403:先修权限或策略,不要原样重复请求。
429:尊重等待时间,退避、降速、限并发。
状态码只是排障入口,不是全部答案。可靠的处理方式,是同时结合 HTTP 状态码、响应体中的业务错误代码、相关响应头、平台文档、请求 ID 和服务端日志,把“接口报错”变成一套可观察、可恢复、可维护的工程机制。

