HTTPステータスコードの使い分け
200/400/401/403/404 など代表的なHTTPステータスコードの意味と、どの場面で返すべきかの判断基準
TL;DR
- 200:正常に処理が完了した
- 400:リクエストの内容が壊れているか不正(クライアント側のミス)
- 401:認証されていない(ログインが必要)
- 403:認証済みだが、そのリソースへのアクセス権がない
- 404:指定したリソースが存在しない
401と403、400と422は特に混同しやすいので違いを明確に把握しておきたい。
目的
HTTPステータスコードは、サーバーがリクエストを受け取った後に「どういう結果になったか」をクライアントに伝えるための数値コードである。
適切なステータスコードを返すことで、
- クライアント側が次のアクションを判断できる
- エラーの原因がどこにあるか(クライアント / サーバー)を素早く特定できる
- APIの品質・可読性が上がる
誤ったステータスコード(たとえばエラーなのに200を返すなど)を使うと、クライアント側のエラーハンドリングが壊れる原因になるため、正確に使い分けることが重要である。
ステータスコードの分類
HTTPステータスコードは100〜599の3桁の数値で、先頭の数字によって大まかなカテゴリが決まる。
| 範囲 | 分類 | 概要 |
|---|---|---|
| 1xx | 情報 | 処理の途中(Webアプリでほぼ意識しない) |
| 2xx | 成功 | リクエストが正常に処理された |
| 3xx | リダイレクト | 別のURLへの転送が必要 |
| 4xx | クライアントエラー | リクエスト側に問題がある |
| 5xx | サーバーエラー | サーバー側に問題がある |
200 OK
意味
リクエストが正常に処理された。最も基本的な成功レスポンス。
いつ返すか
- GET:データ取得が成功したとき
- PUT / PATCH:更新が成功したとき
- DELETE:削除が成功し、レスポンスボディに結果を含むとき
GET /users/1
→ 200 OK
{
"id": 1,
"name": "田中 太郎",
"email": "tanaka@example.com"
}注意点
POST でリソースを新規作成した場合は 200 ではなく 201 Created を返すのが正確である。200 は「既存の処理が成功した」場面に使う。
また、エラーが発生しても200を返しレスポンスボディの中にエラーコードを入れる実装を見かけることがある。これは避けるべき設計で、クライアントのエラーハンドリングを複雑にする。
400 Bad Request
意味
リクエストの形式や内容がサーバーの期待と合わない。クライアント側のミスが原因。
いつ返すか
- 必須パラメータが欠けている
- パラメータの型が間違っている(数値のはずが文字列など)
- JSONの構造が不正(パースできない)
- バリデーションエラー(メールアドレスの形式が違う、パスワードが短すぎるなど)
POST /users
Content-Type: application/json
{
"name": "",
"email": "not-an-email"
}
→ 400 Bad Request
{
"errors": [
{ "field": "name", "message": "名前は必須です" },
{ "field": "email", "message": "メールアドレスの形式が正しくありません" }
]
}401・403・404との違い
400は「リクエストの中身自体がおかしい」場合に返す。認証や権限の話ではなく、入力内容の問題である。認証が必要な場合は401、権限がない場合は403を返す。
補足:422 Unprocessable Entity との使い分け
400と混同されやすいのが 422 Unprocessable Entity である。
| コード | 使う場面 |
|---|---|
| 400 | リクエストの形式自体がおかしい(JSONが壊れているなど) |
| 422 | 形式は正しいが、内容のバリデーションが通らない |
ただし、実際のAPIでは400をバリデーションエラーに使うケースも多く、プロジェクト内で統一されていれば大きな問題はない。
401 Unauthorized
意味
リクエストを処理するために認証が必要だが、認証情報が提供されていないか、無効である。名前は「Unauthorized(未認可)」だが、実際の意味は **「未認証(Unauthenticated)」**である点に注意。
いつ返すか
- リクエストにトークン(Authorization ヘッダーなど)が含まれていない
- トークンが期限切れ(JWT の有効期限切れなど)
- トークンが改ざんされており検証に失敗した
- ログインAPIでIDまたはパスワードが一致しない
GET /me
→ (Authorization ヘッダーなし)
→ 401 Unauthorized
{
"message": "認証が必要です"
}GET /me
Authorization: Bearer expired_token
→ 401 Unauthorized
{
"message": "トークンの有効期限が切れています"
}403との違い
| コード | 状況 | ユーザーの状態 |
|---|---|---|
| 401 | 誰なのかわからない | 未ログイン / トークン無効 |
| 403 | 誰かはわかるが許可されていない | ログイン済みだがアクセス権がない |
「ログインしていないからアクセスできない」→ 401
「ログインしているが、このリソースには触れない」→ 403
403 Forbidden
意味
リクエストは認識できたが、サーバーがそのリクエストを拒否している。認証済みであっても、そのリソースへのアクセス権が与えられていない。
いつ返すか
- 一般ユーザーが管理者専用エンドポイントにアクセスした
- 他のユーザーのデータを取得・更新しようとした
- IPアドレス制限などで弾かれた
- 特定のロール(権限)を持っていないユーザーが制限されたAPIを呼んだ
GET /admin/users
Authorization: Bearer valid_user_token
→ 403 Forbidden
{
"message": "このリソースへのアクセス権がありません"
}DELETE /posts/100
Authorization: Bearer valid_user_token
→ (投稿100は別のユーザーの記事)
→ 403 Forbidden
{
"message": "他のユーザーのリソースを削除する権限がありません"
}セキュリティ上の注意
「そのリソースが存在すること自体を隠したい」場合は、403ではなく404を返す設計にすることもある。たとえば、あるリソースの存在を知られたくない場合に403を返すと「存在はしているがアクセスできない」ことが露見してしまう。
404 Not Found
意味
リクエストされたリソースが存在しない。
いつ返すか
- 指定したIDのユーザーや記事がDBに存在しない
- 存在しないURLパスへのリクエスト
- 削除済みのリソースへのアクセス
GET /users/9999
→ (ID 9999 のユーザーが存在しない)
→ 404 Not Found
{
"message": "ユーザーが見つかりません"
}403との使い分け(セキュリティ観点)
前述の通り、リソースの存在を隠したい場合に404を返すことがある。
| ケース | 返すべきコード |
|---|---|
| 存在しないリソース | 404 |
| 存在するが権限がない(存在を公開していい) | 403 |
| 存在するが権限がない(存在を隠したい) | 404 |
どちらを選ぶかはセキュリティ要件とAPI仕様の設計方針による。
よく使われるその他のコード
| コード | 名前 | 概要 |
|---|---|---|
| 201 | Created | リソースの新規作成が成功した(POSTの成功時) |
| 204 | No Content | 成功したがレスポンスボディがない(DELETEの成功時など) |
| 422 | Unprocessable Entity | 形式は正しいがバリデーションエラー |
| 429 | Too Many Requests | レートリミットを超えた |
| 500 | Internal Server Error | サーバー側で予期しないエラーが発生した |
| 503 | Service Unavailable | サーバーが一時的に利用不可(メンテナンスなど) |
まとめ:判断フローチャート
リクエストを受け取った
↓
リクエストの形式・内容は正しいか?
No → 400 Bad Request
↓ Yes
認証情報はあるか?(トークンは有効か?)
No → 401 Unauthorized
↓ Yes
そのリソースへのアクセス権はあるか?
No → 403 Forbidden
↓ Yes
リソースは存在するか?
No → 404 Not Found
↓ Yes
処理を実行
→ 200 OK / 201 Created / 204 No Content