# HTTP 狀態碼：設計 API 時，這個該回哪一個？

不背整張表，只記三個問題 —— 寫給每次都在猶豫「回 400 還是 422」的人

先講一句話
  
狀態碼是回應的第一行摘要，寫給不會讀 body 的人看的。

誰不讀 body？瀏覽器、CDN／快取、SDK 的自動重試、你的監控告警、負載平衡器。它們只看那三個數字就決定要不要重送、要不要快取、要不要把你叫醒。

為什麼你需要它
  
想像一間餐廳，不管你點的菜有沒有做出來，服務生都笑著說「好的沒問題」，然後在收據角落用小字寫「其實廚房失火了」。

這就是「全部回 200，錯誤寫在 body 裡」的 API。後果不是理論上的：

- 監控面板永遠 100% 成功率 —— 掛了三天沒人知道。
- CDN 把「錯誤頁」當成正常內容快取起來，發給所有人。
- SDK／閘道的自動重試不會啟動，因為它以為成功了。
- 前端只好每支 API 都拆 body 自己判斷，錯一支就少一個錯誤處理。

狀態碼給機器看，body 給人看。兩個都要，但別把機器要的那份藏在 body 裡。

核心對比：全場最重要的一條線
  
五個家族只是開胃菜（1xx 還在處理、2xx 成功、3xx 換個地方拿、4xx 請求有問題、5xx 伺服器有問題）。真正每天在用的是這條線：

4xx ＝ 「你要改」
一模一樣的請求**再送一百次還是會失敗**。是呼叫方要改參數、補權限、換做法。重試沒有意義。

5xx ＝ 「我要修」
請求本身沒問題，**是我這邊爆了**。同樣的請求等一下可能就成功。該被叫醒的是我。

這條線不只是語意潔癖 —— 它決定了誰的告警會響、重試機制會不會自動重送、SLA 的錯誤率算在誰頭上。把自己的 bug 回成 400，等於把自己的鍋丟給呼叫方；反過來把參數錯回成 500，你會被自己的告警半夜叫醒去看一個根本沒壞的服務。

  那 1xx 和 3xx 呢？（點開看）
  1xx 是「收到了，還沒完」，日常自己寫 API 幾乎用不到（`101` 是升級成 WebSocket 時用的）。3xx 是「東西不在這，去那邊拿」：`301/308` 永久搬家、`302/307` 暫時的、`304 Not Modified` 是快取專用（配 `ETag`／`If-None-Match`，代表「你手上那份還新鮮，我不用再傳一次」）。分類定義見 RFC 9110 §15。

把抽象變成動作 ⚙️
  
設計時不要從「有哪些碼」開始想，那是查表。從三個問題開始問，答完就只剩兩三個候選：

手上這個回應該回什麼？照著 ①②③ 問一輪，答案自己會浮出來

手把手：三步挑一個碼
1. **成功了嗎？ → 挑 2xx**

      有東西要回 → `200 OK`。

      建立了一筆新資源 → `201 Created`，順手在 `Location` 標頭放新資源的網址。

      收下了但還沒做完（丟進佇列、非同步處理）→ `202 Accepted`，回一個可以查進度的位置。

      做完了但沒東西好回（刪除、純更新）→ `204 No Content`，**body 必須是空的**。
2. **失敗了，是誰的錯？ → 4xx 還是 5xx**

      判準就一句：同樣的請求再送一次，有機會成功嗎？

      沒機會（少參數、沒權限、東西不存在）→ 4xx。

      有機會（DB 剛好斷線、上游超時、我沒接到的例外）→ 5xx。

      拿不定主意時想：**這個錯該進誰的待辦清單？**
3. **選定家族後，挑最貼切的那個**

      別只會 `400` 和 `500`，但也**不必用滿**。挑一組你團隊講得清楚的常用碼（下面那張表就夠用），寫進規範，全站一致 —— 一致比精準更值錢。

      挑不到貼切的？回家族的通用碼（`400`／`500`）就好，然後把細節寫在 body。

📋 常用碼速查（會用這些就夠了）
  | 碼 | 白話 | 什麼時候回 |
| --- | --- | --- |
| 200 OK | 好了，東西在這 | 查詢成功、更新成功且要回內容 |
| 201 Created | 幫你建好了 | 新增資源成功；附上 `Location` |
| 202 Accepted | 收下了，還在做 | 非同步任務、排入佇列 |
| 204 No Content | 做完了，沒東西回 | 刪除成功、無回傳的更新 |
| 400 Bad Request | 你這包我讀不懂 | JSON 壞掉、少必填、型別錯、格式不合 |
| 401 Unauthorized | 你是誰？ | 沒帶憑證或憑證過期／無效。**要附 `WWW-Authenticate`** |
| 403 Forbidden | 知道你是誰，但不行 | 身分沒問題，就是沒這個權限 |
| 404 Not Found | 沒這東西 | 路徑不存在、ID 查無資料 |
| 405 Method Not Allowed | 路徑對，動詞錯 | 對只讀端點送 `DELETE`。要附 `Allow` 列出可用方法 |
| 409 Conflict | 跟目前狀態打架 | 重複註冊、樂觀鎖版本衝突、狀態機不允許的轉換 |
| 415 Unsupported Media Type | 這格式我不吃 | 送了 XML 但我只收 JSON |
| 422 Unprocessable Content | 看得懂，但不合理 | 格式完全正確，是**商業規則**不通過（生日在未來、金額超上限） |
| 429 Too Many Requests | 太快了，慢點 | 觸發限流。**附 `Retry-After`** 告訴對方等多久 |
| 500 Internal Server Error | 我壞了，還不知道為什麼 | 沒接住的例外。**別把細節或堆疊吐給外面** |
| 502 Bad Gateway | 我後面那台回了垃圾 | 上游回了無效回應／連不上 |
| 503 Service Unavailable | 暫時不能服務 | 維護中、過載、熔斷開啟。附 `Retry-After` |
| 504 Gateway Timeout | 我後面那台太慢 | 等上游等到超時 |
  
標色的那幾條是**八成情況會用到的**；其餘先知道存在，用到再查。

  那 body 要長什麼樣？（點開看）
  狀態碼只有三位數，講不出「哪個欄位錯了」。錯誤細節放 body，格式別每支自己發明 —— 有現成標準 **RFC 9457 Problem Details**（`application/problem+json`，欄位如 `type`／`title`／`detail`／`instance`），照它做，前端和第三方接起來都省事。

🥊 最常吵的四組
  
400 還是 422？
**400**＝我連讀都讀不懂（JSON 壞了、少必填、型別錯）。
**422**＝我讀懂了，但內容不合規則（生日填未來、轉帳金額超過餘額上限）。
分不清就**全站統一用 400**，比一半一半好。

401 還是 403？
**401**＝**還不知道你是誰**（沒登入、token 過期）→ 前端該做的是跳登入。
**403**＝**知道你是誰，就是不給**（一般用戶想開管理頁）→ 跳登入沒用，重登一百次還是不行。

403 還是 404？
「這筆資料存在，但不是你的」——照實回 **403** 等於告訴對方**這個 ID 真的存在**，可以被拿來掃描猜測。
敏感資源常刻意回 **404**（裝作不存在）。這是**安全取捨**，不是語意錯誤，但要全站一致，別半套。

502 / 503 / 504 差在哪？
都是 5xx，差在**誰壞了**：
**502**＝我後面那台回了垃圾或連不上。
**504**＝我後面那台還活著，只是太慢，我等到超時。
**503**＝**我自己**現在不服務（維護／過載／熔斷）。查問題時這三個直接指向不同的地方。

  順便：301 / 302 / 307 / 308 的坑（點開看）
  歷史包袱：早期瀏覽器遇到 `301`／`302` 會把 `POST` 悄悄改成 `GET` 再送一次（body 就沒了）。`307`／`308` 是後來補的，明確規定**方法和 body 都要原封不動**。所以：需要保留方法 → 用 `307`（暫時）／`308`（永久）。另外 `301`／`308` 會被瀏覽器**長期記住**，改錯了很難收回，搬家沒搬乾淨前先用暫時的那組。

✍️ 換你試幾題
  
心裡先答，再往下想理由（沒有標準答案警察，但每題都有明顯較好的選擇）：

1. 刪除一筆待辦事項，成功了，沒東西要回給前端。
2. 使用者上傳頭像，你丟進佇列慢慢轉檔，馬上要回應。
3. 註冊時 email 已經有人用了。
4. 手機 App 帶著三天前過期的 token 來打 API。
5. 你呼叫金流商的 API，等了 30 秒沒回應，你的 API 該回什麼給 App？

  看看我的答案
  1. `204`（做完了、沒 body）。2. `202` ＋ 一個查進度的位置（別回 200 假裝做完了）。3. `409`（跟現況衝突）——若你把「email 重複」當成欄位驗證失敗，回 `400/422` 也說得通，重點是全站一致。4. `401`（是「你是誰」的問題，不是權限問題）。5. `504`（上游超時），不是 `500`——分清楚，你才會去查金流商而不是查自己。

✔ 過關標準
  
面對任何一個回應，你能**三秒內說出該回哪個碼、而且講得出理由**；並且在 4xx／5xx 之間不會擺錯邊。

你不需要背得出 418 是什麼（真的有這個碼，是愚人節玩笑的茶壺）。你需要的是：挑一組常用碼、寫進團隊規範、全站用一樣的規則。

帶走這一句
  
先問「是誰的錯」，再問「等一下會不會好」——
剩下的就只是查表。

📚 想深讀（權威來源）
- [RFC 9110 §15 · HTTP Semantics](https://www.rfc-editor.org/rfc/rfc9110.html#name-status-codes)：狀態碼的正式定義來源，取代舊的 RFC 7231。每個碼「該在什麼情況用、要附哪些標頭」都在這。
- [MDN · HTTP response status codes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status)：查單一碼最快的地方，有瀏覽器實際行為與範例。
- [RFC 9457 · Problem Details for HTTP APIs](https://www.rfc-editor.org/rfc/rfc9457.html)：錯誤 body 的標準格式，別再自己發明錯誤結構。
- [IANA · HTTP Status Code Registry](https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml)：所有已註冊狀態碼的權威清單（`429` 出自 RFC 6585，不在 9110 裡）。

＊「三個問題」的挑碼流程是本文為了好記自行整理的教學框架，非引自單一來源；每個碼的語意定義則依 RFC 9110 / IANA。

筆記整理　**Chance Lu**　·　學習筆記　·　例子皆為通用示意
