APIエラーレスポンス設計|仕様で決める5つのルール

目次
はじめに
APIは、正常系だけを見れば簡単に見えます。しかし実運用では、入力不足、認証切れ、権限不足、競合、外部サービス停止、タイムアウトなどが必ず起きます。このとき、APIごとにステータスコードやJSON形式が違うと、クライアント側は個別対応を重ねることになります。問い合わせ時にも、利用者が見たメッセージとサーバーログを結び付けられません。
IETFのHTTP Semantics(RFC 9110)は、ステータスコードをレスポンスの意味として定義しています。また、Problem Details for HTTP APIs(RFC 9457)は、HTTPステータスだけでは足りない機械可読なエラー情報を共通形式で表す方法を定めています。エラー設計は、実装者が例外処理を書く段階ではなく、APIの利用者と提供者が契約を決める段階で行うべきです。
この記事では、APIエラーレスポンスを仕様化するための5つのルールを解説します 。API仕様書の書き方やOpenAPIの変更レビューと合わせて使える実務的な整理です。
エラーレスポンス設計で決める5つのルール
1. HTTPステータスコードと業務エラーを分ける
最初に、HTTPステータスコードが示す範囲を統一します。RFC 9110では、4xxはクライアント側に原因がある状態、5xxは一見妥当なリクエストをサーバーが処理できなかった状態として整理されています。ここを曖昧にして、すべてを200、400、500の三種類だけで返すと、認証のやり直し、入力修正、再試行などの判断ができません。
代表的な使い分けは次の通りです。
- 400:JSONの構文不正など、リクエスト全体を解釈できない
- 401:有効な認証情報がない
- 403:認証済みだが操作権限がない
- 404:対象リソースが存在しない、または存在を開示しない
- 409:現在のリソース状態と競合している
- 422:形式は解釈できるが、項目値が業務ルールを満たさない
- 429:要求回数の上限を超えた
- 500:想定外の内部エラーが発生した
- 503:一時的にサービスを提供できない
一方、「請求書が確定済みで編集できない」「在庫が不足している」といった業務上の理由は、HTTPステータスとは別のエラーコードで表します。たとえば409に「INVOICE_ALREADY_FINALIZED」を組み合わせれば、共通クライアントは競合として扱い、画面は業務に合った案内を表示できます。
2. レスポンスの共通項目を固定する
エラーJSONはAPIごとに独自形式を作らず、全体で共通化します。RFC 9457のProblem Detailsでは、問題の種類を示すtype、人向けの短いtitle、HTTPのstatus、個別事象のdetail、発生単位を識別するinstanceが定義されています。JSONではContent-Typeにapplication/problem+jsonを使えます。
実務では、次の項目を基本セットにすると扱いやすくなります。
~~~json
{
"type": "https://example.com/problems/validation-error",
"title": "入力内容を確認してください",
"status": 422,
"detail": "2件の入力エラーがあります",
"instance": "/requests/req_01J...",
"code": "VALIDATION_ERROR",
"traceId": "01J...",
"errors": [
{ "field": "email", "code": "INVALID_FORMAT" }
]
}
~~~
人向けのdetailだけをプログラムで判定してはいけません。文言は改善や多言語化で変わるためです。クライアントの分岐には安定したcodeを使い、画面表示にはtitleやdetailを使います。入力エラーでは、対象フィールドをfieldやJSON Pointerで返せるようにします。
3. 公開する情報とログだけに残す情報を分ける
エラーを詳しく返せば調査しやすくなるとは限りません。スタックトレース、SQL、内部ホスト名、ライブラリ名、個人情報、アクセストークンを返すと、攻撃者にも有用な情報になります。OWASP REST Security Cheat Sheetも、クライアントには一般化したメッセージを返し、技術的な内部情報を露出しないよう推奨しています。
外部レスポンスには、利用者が次に取る行動へ必要な情報だけを含めます。詳細な例外、呼び出し元、外部サービスの応答、処理時間はサーバーログへ記録します。その二つをtraceIdやrequestIdで結び付けると、利用者は秘密情報を見ずに問い合わせでき、運用担当者は該当ログを短時間で探せます。
また、401と403、あるいは404の使い方はセキュリティ方針と合わせます。対象の存在自体を知られてはいけない場合、権限がない利用者へ404を返す設計もあります。これは実装者ごとに判断せず、API全体の方針として仕様へ記載します。
4. 再試行できる条件をレスポンスで示す
クライアントが自動再試行してよいエラーと、人の修正が必要なエラーを分けます。入力不正の422を何度送っても成功しません。一方、429や一時的な503は、一定時間後に成功する可能性がありま す。
再試行可能な場合は、対象のステータスコード、最大回数、待機時間、指数バックオフの有無を決めます。必要に応じてRetry-Afterヘッダーを使います。ただし、POSTを無条件に再送すると二重登録や二重決済が起きるため、冪等性キーや処理状態照会のAPIもセットで設計しなければなりません。
仕様書には、各エラーについて「利用者が修正」「認証を更新」「時間を置いて再試行」「運用担当へ連絡」のどれを求めるかを書きます。エラーコード一覧が、そのまま復旧手順の入口になる状態が理想です。
5. エラー一覧を受け入れ条件とテストへつなぐ
エラー一覧は参考資料ではなく、テスト可能な仕様にします。エンドポイントごとに、発生条件、HTTPステータス、エラーコード、表示メッセージ、再試行可否、ログ項目を表にします。そして正常系と同じように、異常系を受け入れ条件へ落とします。
たとえば「他利用者が先に更新した場合、409とRESOURCE_VERSION_CONFLICTを返し、元データを上書きしない」「メール形式が不正な場合、422と対象フィールドを返す」のように、入力・状態・期待結果を明示します。AIエージェントの受入テストと同様に、入力・期待結果・証跡の対応を、APIエラーにも整理できます。
変更時には後方互換性も確認します。既存コードの意味を変える、項目を削除する、同じ条件で別ステータスへ変える、といった修正はクライアントを壊す可能性があります。項目追加は比較的安全でも、厳密なJSON検証を行う利用者がいる場合は影響します。OpenAPIのレスポンス定義と実際のテスト結果を一致させましょう。
仕様化に使えるエラー定義表
最低限、次の列を持つ表を作ると、設計・実装・テスト・運用で同じ情報を使えます。
| 項目 | 記載内容 |
|---|---|
| 発生条件 | どの入力・権限・状態で起きるか |
| HTTPステータス | HTTPとしての結果 |
| エラーコード | クライアントが判定する安定した識別子 |
| 利用者向け文言 | 次の行動が分かる説明 |
| 再試行 | 可否、待機時間、上限 |
| ログ | traceId、監査対象、秘匿する詳細 |
| テスト | 再現条件と期待レスポンス |
この表を業務フローや画面仕様と別々に管理すると、変更時にずれます。Kakusillで要件と仕様を整理すると、例外条件を関連する画面・業務ルール・APIへ結び付けやすくなります。
まとめ:エラーを実装の例外ではなくAPIの契約にする
APIエラー設計で重要なのは、HTTPの意味、業務エラーの識別、公開情報の範囲、復旧方法、テスト条件を一つの契約として揃えることです。
- HTTPステータスと業務エラーコードを分ける
- Problem Detailsを参考に共通項目を固定する
- 外部へ返す情報と内部ログを分離する
- 再試行できる条件と冪等性を決める
- エラー一覧を受け入れ条件とテストへつなぐ
正常系だけのAPI仕様は、実運用の半分しか表していません。例外時に利用者、クライアント、運用担当が迷わないところまで定義して、初めて実装へ渡せる仕様になります。



