呼叫 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 和伺服器端日誌,把「介面報錯」變成一套可觀察、可恢復、可維護的工程機制。

