テクノロジー

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

株式会社Atsumell|8分で読めます
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の意味、業務エラーの識別、公開情報の範囲、復旧方法、テスト条件を一つの契約として揃えることです。

  1. HTTPステータスと業務エラーコードを分ける
  2. Problem Detailsを参考に共通項目を固定する
  3. 外部へ返す情報と内部ログを分離する
  4. 再試行できる条件と冪等性を決める
  5. エラー一覧を受け入れ条件とテストへつなぐ

正常系だけのAPI仕様は、実運用の半分しか表していません。例外時に利用者、クライアント、運用担当が迷わないところまで定義して、初めて実装へ渡せる仕様になります。

#API設計#HTTP#エラーハンドリング#要件定義#システム開発