閉じる

Web API開発で知るべきHTTPステータスコード(200/404/500)の意味

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
HTTP/1.1 200 OK
Content-Type: application/json

{
  "id": 123,
  "name": "Widget",
  "price": 980
}

201 Created(作成)

意味

新しいリソースが作成されました。

Locationヘッダーで新規リソースのURLを通知するのが定石です。

典型的な場面

POSTで新規ユーザーや注文を作成したとき。

レスポンス例

HTTP
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
HTTP/1.1 204 No Content

400 Bad Request(リクエスト不正)

意味

リクエストが不正でサーバーは処理できません。

バリデーションエラーなど。

典型的な場面

必須フィールド欠落、不正なJSON、範囲外の値。

レスポンス例

HTTP
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
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
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
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
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
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
HTTP/1.1 502 Bad Gateway
Content-Type: application/json

{ "error": "bad_gateway" }

503 Service Unavailable

一時的にサービスが利用できません。

Retry-Afterヘッダーが指定される場合があります。

メンテナンス時にも使います。

HTTP
HTTP/1.1 503 Service Unavailable
Retry-After: 120
Content-Type: application/json

{ "error": "service_unavailable" }

504 Gateway Timeout

上流からの応答がタイムアウトしました。

重い処理やネットワーク遅延が原因です。

HTTP
HTTP/1.1 504 Gateway Timeout
Content-Type: application/json

{ "error": "gateway_timeout" }

Python(requests)での確認方法

response.status_codeを取得する

まずはresponse.status_codeでステータスコードを読み出します。

学習用にhttpbinを使います。

Python
# 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()を使います。

Python
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でも対処は異なります。

「修正すべきか」「待てばよいか」を即時判断できるようにしましょう。

Python
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、ステータス、所要時間が役立ちます。

最低限のログを残しておくと原因特定が早くなります。

Python
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)

テストで期待するコードを検証

期待するステータスを断言するテストを用意します。

回帰を早期に検知できます。

Python
# 簡易的な検証例。実プロジェクトでは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_coderaise_for_status()を押さえ、ログとテストで期待コードを確認すると堅牢になります。

コードの意味を正しく使い分けることが、読みやすく保守性の高いAPI開発につながります。

この記事を書いた人
エーテリア編集部
エーテリア編集部

人気のPythonを初めて学ぶ方向けに、文法の基本から小さな自動化まで、実際に手を動かして理解できる記事を書いています。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!