仕組み

エラー契約とは、失敗時のステータスコード、レスポンス形式、エラー識別子などの取り決めです。

ステータスが適切なら、クライアントは「入力を直す」「データを読み直す」「時間を置く」などを区別できます。

400・422・409の違い

ステータス 意味の中心 APIでの例
400 Bad Request クライアント側の要求に問題がある JSON構文が壊れている
422 Unprocessable Content 内容形式と構文は理解できるが、指示を処理できない 終了日が開始日より前
409 Conflict 対象リソースの現在の状態と競合している 発送済みの注文をキャンセルしようとした
500 Internal Server Error サーバー側で予期しない問題が起きた 未処理の例外で処理が中断した

この区別はHTTP仕様に基づきます。ただし、400は構文エラーだけに限定されたコードではありません。 バリデーションエラーを400に統一するAPIもあり、422との使い分けには設計上の選択があります。HTTP仕様:ステータスコード

409と412も区別する

「データが古い」という競合でも、HTTPの If-Match 条件が満たされなかった場合は、通常 412 Precondition Failed を使います。

PUT /articles/123
If-Match: "revision-7"

その記事がすでに別の人によって更新されていれば、上書きを拒否できます。

本文も機械的に扱える形式にする

エラーメッセージの文字列だけを見て分岐すると、文言変更や翻訳で処理が壊れます。

RFC 9457のProblem Detailsを使うなら、たとえば次の形式にできます。

HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json
{
  "type": "https://api.example.com/problems/invalid-date-range",
  "title": "日付の範囲が不正です",
  "status": 422,
  "detail": "終了日は開始日以降にしてください。",
  "instance": "/requests/req-123",
  "errors": [
    {
      "field": "endDate",
      "code": "BEFORE_START_DATE"
    }
  ]
}

type などは標準の項目です。この例の errors は、フィールド単位の問題を表す独自拡張です。RFC 9457:Problem Details

クライアントは、安定した type やエラーコードで分岐し、説明文は表示に使います。

すべて500にすると何が困るか

入力ミスまで障害として監視され、利用者にも修正方法が伝わりません。また、クライアントが自動再試行すると、直らない要求を繰り返す可能性があります。

逆に、500だから処理が一切実行されていないとも限りません。書き込みの再試行には、1番の冪等性が必要です。

設計上の要点
エラーは、失敗の報告と、次の行動を決めるためのAPIの一部です。