Web APIを正しく扱うには、HTTPステータスコードの意味を理解することが近道です。
とくに200/404/500の違いは設計とデバッグの要です。
本記事ではPythonのrequests
を使いながら、初心者でも実務で迷わないように、コードと具体例で丁寧に解説します。
HTTPステータスコードの基本(200/404/500)
ステータスコードとは
HTTPステータスコードはサーバーがリクエストに対して返す処理結果を表す3桁の数値です。
返される値は200
のような数字で、意味を補う短いテキスト(Reason-Phrase)を伴います。
例えば200 OK
は成功、404 Not Found
は対象が見つからない、500 Internal Server Error
はサーバー側のエラーを示します。
数字と理由句の関係
数字が意味の主役で、理由句は補助的な説明です。
クライアント側では数字に基づいて処理を分岐すべきで、理由句の文言に依存しない方が安全です。
クライアントとサーバーの約束
APIドキュメントは「どの操作で、どのステータスを返すか」という約束事です。
コードと本文(JSONなど)は整合するように設計します。
例えば、エラー時に200
で「error:true」を返すといった設計は避けます。
1xx/2xx/3xx/4xx/5xxのクラス分け
HTTPの3桁コードは先頭の数字でクラスが決まります。
全体像を押さえると判断が速くなります。
クラス | 意味 | 典型例 | 開発時の捉え方 |
---|---|---|---|
1xx | 情報 | 100 Continue | ストリーミングや大きなアップロード時の制御でまれに登場 |
2xx | 成功 | 200 OK / 201 Created / 204 No Content | 処理成功。本文の有無や作成済みリソースの場所に注目 |
3xx | リダイレクト | 301 Moved Permanently / 302 Found / 307/308 | 別のURLへ誘導。APIでは慎重に扱う |
4xx | クライアント誤り | 400 Bad Request / 401 / 403 / 404 / 429 | 入力や認証・権限、回数制限の問題 |
5xx | サーバー誤り | 500 / 502 / 503 / 504 | サーバーや上流の障害。リトライや問い合わせを検討 |
クラス別のざっくり使い分け
- 2xxなら基本は成功です。ただし
204
は本文がない点に注意します。 - 4xxはリクエスト修正で解決できる問題です。再試行より先に原因修正を考えます。
- 5xxはサーバー側の問題が中心です。指数バックオフなどのリトライ戦略を検討します。
Web APIでの意味と注意点
Web APIでは、コードの選択がクライアントの挙動に直結します。
よくある誤用と避け方
- 誤用: エラーなのに
200
でJSONにエラーフラグだけを入れる。
正解: 4xx/5xxを適切に使い、本文にエラー詳細を返します。 - 作成成功で
200
を返す。
正解: 新規作成は201 Created
を返しLocation
ヘッダーでURLを示します。 - 削除成功で本文にメッセージを返す。
正解:204 No Content
で空レスポンスが自然です。
セキュリティ上の配慮
存在の有無を隠すために、アクセス不可な資源へ404
を返すケースがあります。
用途によって403
との使い分けをドキュメントに明記します。
代表的なコードの意味と例
200 OK(成功)
意味
リクエストが成功したことを示します。
本文に結果が含まれるのが一般的です。
典型的な場面
GETでリソース取得に成功したとき、検索の結果一覧など。
レスポンス例
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": 123,
"name": "Widget",
"price": 980
}
201 Created(作成)
意味
新しいリソースが作成されました。
Locationヘッダーで新規リソースのURLを通知するのが定石です。
典型的な場面
POSTで新規ユーザーや注文を作成したとき。
レスポンス例
HTTP/1.1 201 Created
Location: https://api.example.com/orders/98765
Content-Type: application/json
{
"id": 98765,
"status": "created"
}
204 No Content(成功だが本文なし)
意味
処理は成功しましたが、返す本文はありません。
典型的な場面
DELETEで削除成功。
PUTやPATCHで更新成功時に本文不要と判断した場合。
レスポンス例
HTTP/1.1 204 No Content
400 Bad Request(リクエスト不正)
意味
リクエストが不正でサーバーは処理できません。
バリデーションエラーなど。
典型的な場面
必須フィールド欠落、不正なJSON、範囲外の値。
レスポンス例
HTTP/1.1 400 Bad Request
Content-Type: application/json
{
"error": "validation_error",
"message": "price must be >= 0",
"fields": {
"price": "must be non-negative"
}
}
401 Unauthorized(認証が必要)
意味
認証が必要、または認証情報が無効です。
WWW-Authenticate
ヘッダーが付く場合があります。
典型的な場面
トークンの未送信や期限切れ。
レスポンス例
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="example", error="invalid_token"
Content-Type: application/json
{
"error": "invalid_token",
"message": "Access token is expired"
}
403 Forbidden(権限不足)
意味
認証済みだが権限が不足しています。
典型的な場面
一般ユーザーが管理者専用エンドポイントへアクセス。
レスポンス例
HTTP/1.1 403 Forbidden
Content-Type: application/json
{
"error": "forbidden",
"message": "You do not have permission to access this resource"
}
404 Not Found(見つからない)
意味
対象のリソースが見つかりません。
存在非公開のため意図的に404を返す場合もあります。
典型的な場面
存在しないIDの参照、削除済みリソースのアクセス。
レスポンス例
HTTP/1.1 404 Not Found
Content-Type: application/json
{
"error": "not_found",
"message": "Order 99999 was not found"
}
429 Too Many Requests(制限超過)
意味
レート制限に達しました。
Retry-Afterやレート制限関連ヘッダーを確認します。
典型的な場面
短時間に大量のリクエストを送ったとき。
レスポンス例
HTTP/1.1 429 Too Many Requests
Retry-After: 30
RateLimit-Limit: 100
RateLimit-Remaining: 0
RateLimit-Reset: 1726300000
Content-Type: application/json
{
"error": "rate_limited",
"message": "Too many requests. Try again later."
}
500 Internal Server Error(サーバー側エラー)
意味
サーバー内部の予期しないエラーです。
クライアントには非がない場合が多いです。
典型的な場面
未処理の例外、依存サービスの障害によりエラーハンドリングされなかった場合。
レスポンス例
HTTP/1.1 500 Internal Server Error
Content-Type: application/json
{
"error": "internal_error",
"message": "Unexpected server error"
}
502/503/504(ゲートウェイ/一時的障害/タイムアウト)
502 Bad Gateway
上流のサーバーから無効な応答を受け取りました。
APIゲートウェイやプロキシ経由で起きます。
HTTP/1.1 502 Bad Gateway
Content-Type: application/json
{ "error": "bad_gateway" }
503 Service Unavailable
一時的にサービスが利用できません。
Retry-Afterヘッダーが指定される場合があります。
メンテナンス時にも使います。
HTTP/1.1 503 Service Unavailable
Retry-After: 120
Content-Type: application/json
{ "error": "service_unavailable" }
504 Gateway Timeout
上流からの応答がタイムアウトしました。
重い処理やネットワーク遅延が原因です。
HTTP/1.1 504 Gateway Timeout
Content-Type: application/json
{ "error": "gateway_timeout" }
Python(requests)での確認方法
response.status_codeを取得する
まずはresponse.status_code
でステータスコードを読み出します。
学習用にhttpbinを使います。
# httpbinを使って代表的なステータスコードを取得する例
# 実行前に: pip install requests
import requests
def fetch_and_show(url: str) -> None:
# URLへGETリクエストを送る
resp = requests.get(url)
# ステータスコードと理由句を表示
print(f"URL={url}")
print(f"status_code={resp.status_code}, ok={resp.ok}, reason={resp.reason}")
# レスポンス本文の先頭だけを確認(長文対策)
snippet = resp.text[:80].replace("\n", "\\n")
print(f"body(80chars)={snippet}")
print("-" * 40)
# 200, 404, 500 を順に確認
urls = [
"https://httpbin.org/status/200",
"https://httpbin.org/status/404",
"https://httpbin.org/status/500",
]
for u in urls:
fetch_and_show(u)
URL=https://httpbin.org/status/200
status_code=200, ok=True, reason=OK
body(80chars)=
----------------------------------------
URL=https://httpbin.org/status/404
status_code=404, ok=False, reason=NOT FOUND
body(80chars)=
----------------------------------------
URL=https://httpbin.org/status/500
status_code=500, ok=False, reason=INTERNAL SERVER ERROR
body(80chars)=
----------------------------------------
成功判定とraise_for_statusの活用
Response.ok
は「200〜399ならTrue」という簡易判定です。
確実に失敗を例外として扱うならraise_for_status()
を使います。
import requests
from requests import HTTPError
def get_json_or_raise(url: str):
try:
resp = requests.get(url)
# ステータスコードが4xx/5xxならHTTPError例外を投げる
resp.raise_for_status()
# JSONとしてパース(本文がない204などは別途対処が必要)
if resp.status_code == 204:
return None
return resp.json() if "application/json" in resp.headers.get("Content-Type", "") else resp.text
except HTTPError as e:
# 具体的なステータスで分岐
status = e.response.status_code if e.response is not None else None
if status == 401:
print("認証エラー。トークンやAPIキーを確認してください。")
elif status == 403:
print("権限不足。ロールやスコープを確認してください。")
elif status == 404:
print("対象が見つかりません。IDやURLを確認してください。")
elif status == 429:
retry_after = e.response.headers.get("Retry-After", "unknown")
print(f"レート制限。{retry_after}秒後に再試行してください。")
elif status and 500 <= status < 600:
print("サーバー側エラー。しばらく待ってから再試行を検討してください。")
else:
print(f"HTTPエラーが発生しました: {e}")
return None
print(get_json_or_raise("https://httpbin.org/status/401"))
print(get_json_or_raise("https://httpbin.org/status/404"))
print(get_json_or_raise("https://httpbin.org/status/429"))
print(get_json_or_raise("https://httpbin.org/status/500"))
認証エラー。トークンやAPIキーを確認してください。
None
対象が見つかりません。IDやURLを確認してください。
None
レート制限。unknown秒後に再試行してください。
None
サーバー側エラー。しばらく待ってから再試行を検討してください。
None
エラーコード別の基本対応方針
同じ4xxでも対処は異なります。
「修正すべきか」「待てばよいか」を即時判断できるようにしましょう。
import requests
def handle_status(status: int, headers: dict) -> str:
# 実践向け: ステータスに応じた人間向けメッセージを返す
if 200 <= status < 300:
return "成功。結果を処理します。"
if status == 304:
return "変更なし。キャッシュ利用を検討します。"
if status == 400:
return "入力不正。リクエストパラメータを見直してください。"
if status == 401:
return "未認証。APIキーやトークンを設定してください。"
if status == 403:
return "権限不足。アカウント権限やスコープを確認してください。"
if status == 404:
return "見つからない。ID/URLが正しいか確認してください。"
if status == 409:
return "競合。リソースの状態を再取得してからやり直してください。"
if status == 412:
return "前提条件失敗。ETagやIf-Matchの一致を確認してください。"
if status == 429:
ra = headers.get("Retry-After", "不明")
return f"レート制限。{ra}秒後に再試行してください。"
if 500 <= status < 600:
return "サーバー側エラー。指数バックオフでリトライを検討します。"
return f"想定外のステータス({status})。ログに残して調査してください。"
def demo(url: str):
resp = requests.get(url)
print(url, "->", resp.status_code, handle_status(resp.status_code, resp.headers))
demo("https://httpbin.org/status/200")
demo("https://httpbin.org/status/429")
demo("https://httpbin.org/status/503")
https://httpbin.org/status/200 -> 200 成功。結果を処理します。
https://httpbin.org/status/429 -> 429 レート制限。不明秒後に再試行してください。
https://httpbin.org/status/503 -> 503 サーバー側エラー。指数バックオフでリトライを検討します。
初心者向けベストプラクティス
APIドキュメントで許容コードを確認
実装前に各エンドポイントが返しうるステータスコードを一覧し、クライアント側の分岐を決めます。
作成(201)、更新(200/204)、削除(204)、取得(200/304)といった期待値をテストにも反映します。
エラー本文のフォーマット(例: {"error":"...", "message":"..."}
)も合わせて定義します。
リダイレクト(3xx)の扱い
requests
は通常、自動でリダイレクトに追従しますが、APIでは要注意です。
307/308はHTTPメソッドを維持し、301/302/303はGETに変わる可能性があります。
必要に応じてallow_redirects=False
で明示的に制御し、Location
ヘッダーを確認します。
ログにHTTPステータスコードを残す
障害解析ではメソッド、URL、ステータス、所要時間が役立ちます。
最低限のログを残しておくと原因特定が早くなります。
import requests
import time
def logged_get(url: str):
start = time.time()
resp = requests.get(url)
elapsed_ms = int((time.time() - start) * 1000)
# 実運用ではloggingモジュールを使用
print(f"GET {url} -> {resp.status_code} ({elapsed_ms}ms)")
return resp
logged_get("https://httpbin.org/status/200")
logged_get("https://httpbin.org/status/404")
GET https://httpbin.org/status/200 -> 200 (13425ms)
GET https://httpbin.org/status/404 -> 404 (1195ms)
テストで期待するコードを検証
期待するステータスを断言するテストを用意します。
回帰を早期に検知できます。
# 簡易的な検証例。実プロジェクトではpytest/unittestを使用します。
import requests
def assert_status(url: str, expected: int):
resp = requests.get(url)
assert resp.status_code == expected, f"期待:{expected}, 実際:{resp.status_code}"
# 例: httpbinの既知のステータス
assert_status("https://httpbin.org/status/200", 200)
assert_status("https://httpbin.org/status/404", 404)
print("期待ステータスの検証に成功しました。")
期待ステータスの検証に成功しました。
まとめ
HTTPステータスコードは、Web APIの成否と次の行動を伝える共通言語です。
200/201/204の成功系では本文やLocationの扱いに注意し、400/401/403/404/429のクライアント誤りは原因を直すことが最優先、500/502/503/504のサーバー誤りは時間をおいての再試行や問い合わせを検討します。
Pythonのrequests
ではresponse.status_code
とraise_for_status()
を押さえ、ログとテストで期待コードを確認すると堅牢になります。
コードの意味を正しく使い分けることが、読みやすく保守性の高いAPI開発につながります。