HTTP ステータスコードを設計判断で迷わず選ぶための整理
HTTP ステータスコードは何百種類もあるように感じますが、実務でよく使うのは20個程度です。問題は「200 でいいのか 201 か」「401 か 403 か」のような選択で、ここで一貫した判断基準を持っているかが API 設計の品質に出ます。本記事では迷いやすい組み合わせを中心に整理します。
クラス分けの本来の意味
ステータスコードは先頭1桁で5クラスに分かれます。仕様(RFC 9110)の定義文を正確に把握するのが、迷いを減らす第一歩です。
| クラス | 名前 | 意味 |
|---|---|---|
| 1xx | Informational | 中間応答(暫定的) |
| 2xx | Successful | リクエストが正常に受理された |
| 3xx | Redirection | クライアント側で追加アクションが必要 |
| 4xx | Client Error | クライアントの誤りによりサーバーが処理を拒否した |
| 5xx | Server Error | サーバー側の障害で処理に失敗した |
「クライアントの誤り」と「サーバー側の障害」の境界が曖昧になりがちで、ここを4xx か5xx かで適切に切り分けることが、運用時のアラート設計(例:5xx のみページャーを鳴らす)に直結します。
2xx:成功系の使い分け
| コード | 用途 |
|---|---|
| 200 OK | 一般的な成功 |
| 201 Created | 新しいリソースの作成に成功(POST の典型) |
| 202 Accepted | 受理したが処理は非同期で続く(バックグラウンドジョブ) |
| 204 No Content | 成功したがレスポンスボディなし(DELETE の典型) |
201 vs 200
リソースを新規作成した場合は 201 + Location ヘッダで作成された URL を返すのが REST の作法です。200 で済ませる API も多いですが、CRUD を厳密に表現するなら 201 を使う価値があります。
202 の使いどころ
「メール送信を受け付けた、実際の送信は別ジョブで」のような非同期処理は 202 が正解です。レスポンスボディに進捗確認 URL を返す、というパターンが定番です。
4xx:クライアントエラーの紛らわしい3つ
401 Unauthorized vs 403 Forbidden
名前と挙動が逆に感じる代表格です。
- 401 Unauthorized:認証情報が無い/不正。「あなたが誰か分からない」
- 403 Forbidden:認証は成功したが権限が無い。「あなたが誰かは分かったが、これは見せられない」
判定の流れ:
- リクエストに有効な認証情報があるか? → 無ければ 401
- 認証は通った。このリソースへのアクセス権はあるか? → 無ければ 403
API ゲートウェイのログを見るとき、401 がスパイクしていればトークン失効、403 がスパイクしていれば権限設定ミス、という切り分けができます。
404 Not Found vs 410 Gone
- 404 Not Found:見つからない(理由は問わない)
- 410 Gone:以前は存在したが恒久的に削除された
通常は 404 で十分ですが、Google などのクローラーは 410 を見つけるとインデックスから即座に削除します。一方 404 は「一時的に消えてるだけかも」と判断されてしばらくクロールされ続けます。
サイトのコンテンツを意図的に削除する場合は 410 を返したほうが SEO 的に綺麗です。
400 Bad Request vs 422 Unprocessable Entity
- 400 Bad Request:リクエスト自体が壊れている(JSON パースエラー、必須ヘッダ欠落など)
- 422 Unprocessable Entity:リクエスト形式は正しいが、内容のバリデーションに失敗
バリデーションエラーを 400 で返す API も多く、実装的にはどちらでも問題なく動きます。ただし「JSON 構文エラー」と「メールアドレス形式不正」を同じ 400 で返すと、クライアント側でリトライ判定がしづらくなります。422 を使い分けると:
- 400 → リトライしても無駄
- 422 → 入力を直してリトライすべき
という意味付けができ、エラーハンドリングが綺麗になります。
429 Too Many Requests
レート制限を返すコード。Retry-After ヘッダで「何秒後にリトライ可能か」を示すのが作法です。
HTTP/1.1 429 Too Many Requests
Retry-After: 60
{"error": "rate limited", "retry_after_seconds": 60} クライアント側はレスポンスヘッダの Retry-After を読んで指数バックオフを実装すると、サーバー側に優しい振る舞いになります。
5xx:サーバーエラーの使い分け
| コード | 用途 |
|---|---|
| 500 Internal Server Error | 想定外のサーバーエラー全般 |
| 502 Bad Gateway | 上流(バックエンド/プロキシ先)からの応答が異常 |
| 503 Service Unavailable | サーバーが一時的に利用不可(メンテ/過負荷) |
| 504 Gateway Timeout | 上流のタイムアウト |
500 を使うべきタイミング
「500 を投げないことが正義」と思うとつい例外を全部キャッチして 200 + {success: false} で返したくなりますが、バグや想定外エラーは 500 を素直に返すほうが運用上正しいです。
- ログに 500 が出ることでアラートが鳴る
- 200 で
success: falseを隠すとバグが可視化されない
「ユーザー体験を壊さないように」と 200 で返す場合でも、サーバー側のログには 500 相当のエラー記録を残すべきです。
503 と Retry-After
メンテナンス中など「いつ復旧するか分かる」場合は 503 + Retry-After を返すと、賢いクライアントは適切に待機できます。
HTTP/1.1 503 Service Unavailable
Retry-After: Sat, 26 Apr 2026 12:00:00 GMT ステータスコードと冪等性
REST API の設計では、HTTP メソッドの冪等性とステータスコードを合わせて考えます:
- GET / PUT / DELETE は冪等:同じリクエストを何度送っても結果が同じ
- POST は非冪等:送るたびに新しいリソースが作られる
冪等な操作は 200(成功)または 204(削除成功)で、同じリクエストの2回目以降も同じ結果を返すのが理想です。
非冪等な POST で「すでに作られている」場合、409 Conflict を返してクライアントに重複を伝えるパターンが使われます。
まとめ:迷ったときの判定フロー
処理は成功したか?
├── Yes
│ ├── 新規リソース作成? → 201
│ ├── 非同期処理開始? → 202
│ ├── レスポンスボディ無し? → 204
│ └── それ以外 → 200
└── No
├── サーバー側の問題?
│ ├── 一時的(メンテ・過負荷)? → 503
│ ├── 上流の問題? → 502 / 504
│ └── 想定外バグ → 500
└── クライアント側の問題?
├── 認証情報なし/不正 → 401
├── 権限なし → 403
├── リソース存在しない → 404 or 410
├── レート制限 → 429
├── リクエスト構文NG → 400
└── バリデーションNG → 422 API 実装時にこのフローを横に置いておくと、レビューでも判断が揃います。
特定のステータスコードの正確な意味や、レアな番号(418 I’m a teapot, 451 Unavailable For Legal Reasons など)を確認したいときは、本サイトの HTTP ステータスコード一覧で番号から逆引きできます。