呼叫 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:API 憑證缺失、無效或已經過期

HTTP 401 Unauthorized 表示請求缺少目標資源所需的有效驗證憑證。標準的 401 回應通常會透過 WWW-Authenticate 回應標頭告訴用戶端伺服器預期使用的驗證方案。

常見原因

  1. 沒有傳送 Authorization 請求標頭
  2. API Key 或 Access Token 填寫錯誤
  3. Token 已過期或被撤銷
  4. 驗證方案錯誤,例如遺漏 Bearer
  5. 環境變數沒有正確載入
  6. 使用了錯誤環境的金鑰
  7. 請求被代理或閘道轉送時,驗證標頭遺失
  8. 用戶端與伺服器時間偏差過大,導致簽名失效

常見錯誤

介面要求 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 應該重試嗎?

不要使用同一份無效憑證反覆重試。

建議流程:

  1. 檢查請求是否攜帶驗證資訊
  2. 檢查 Token 是否過期、撤銷或複製錯誤
  3. 如果支援 Refresh Token,重新整理一次存取權杖
  4. 使用新 Token 重試一次
  5. 如果仍然回傳 401,停止重試並記錄錯誤

如果使用固定 API Key,則應檢查金鑰狀態或更換有效金鑰,而不是迴圈重試。

二、403 Forbidden:驗證可能成功,但請求仍被拒絕

HTTP 403:請求因權限或存取策略被拒絕

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-After
  • X-RateLimit-Remaining
  • X-RateLimit-Reset
  • 平台官方文件

403 應該重試嗎?

對於權限不足、IP 限制或資源策略導致的 403,原樣重試沒有意義。

正確做法是修改權限或 Token Scope、使用正確帳號或專案、調整白名單與安全策略、修正資源 ID,並確認服務或方案是否支援該介面。只有在平台明確說明該 403 實際代表暫時限流時,才應依限流規則等待後重試。

三、429 Too Many Requests:先減速,再重試

HTTP 429:API 請求受到限流,應等待並退避重試

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

用戶端最好同時相容這兩種格式。

為什麼不能立即重試?

如果請求被限流後立即重試,新請求仍會被限流;多個用戶端還可能同時重試,形成「驚群」。更穩妥的方法是:

  1. 優先遵守 Retry-After
  2. 沒有明確等待時間時,使用指數退避
  3. 在等待時間中加入隨機抖動
  4. 設定最大重試次數
  5. 達到上限後回傳明確錯誤

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

參考資料