|
| 1 | +--- |
| 2 | +title: "RFC 7807 から RFC 9457 で Web API のエラー表現で変わったこと" |
| 3 | +date: 2026/06/11 00:00:00 |
| 4 | +postid: a |
| 5 | +tag: |
| 6 | + - Web |
| 7 | + - rfc |
| 8 | + - WebAPI |
| 9 | + - エラーハンドリング |
| 10 | +category: |
| 11 | + - Infrastructure |
| 12 | +thumbnail: /images/2026/20260611a/thumbnail.jpg |
| 13 | +author: 真野隼記 |
| 14 | +lede: "HTTP API のエラーレスポンスを標準化する仕様、RFC 7807 Problem Details for HTTP APIs は 2016 年 3 月に公開され、以降 Web API のエラー表現のデファクトとして広く参照されてきました。その RFC 7807 ですが、約 7 年後の 2023 年 7 月に RFC 9457 に置き換えられていたのはご存知でしょうか。" |
| 15 | +--- |
| 16 | + |
| 17 | +<img src="/images/2026/20260611a/top.jpg" alt="" width="600" height="355"> |
| 18 | + |
| 19 | +# はじめに |
| 20 | + |
| 21 | +TIG(Technology Innovation Group)の真野です。 |
| 22 | + |
| 23 | +HTTP API のエラーレスポンスを標準化する仕様、RFC 7807 "Problem Details for HTTP APIs" は 2016 年 3 月に公開され、以降 Web API のエラー表現のデファクトとして広く参照されてきました。その RFC 7807 ですが、約 7 年後の 2023 年 7 月に RFC 9457 に置き換えられていたのはご存知でしょうか。 |
| 24 | + |
| 25 | +RFC 9457 は後方互換で、差分は思ったより少ないのです。それでも「何が変わって、なぜ変わったのか」を押さえておくと、これから Web API のエラー設計をする際に判断しやすくなるでしょう。本記事では両 RFC の概要と差分、そして今後の設計指針を整理します。 |
| 26 | + |
| 27 | +# RFC 7807: Problem Details の標準化 (2016年) |
| 28 | + |
| 29 | +RFC 7807 は 2016年3月に公開された Proposed Standard です。 |
| 30 | + |
| 31 | +API ごとにエラーの表現方法がバラバラで、クライアント側が毎回パース方法を変える必要があるという悩みを解決するために作られました。RFC 7807 は以下の 5 つのメンバと、`application/problem+json` というメディアタイプを定義しています。 |
| 32 | + |
| 33 | +| メンバ | 役割 | |
| 34 | +|---|---| |
| 35 | +| `type` | 問題の種類を識別する URI | |
| 36 | +| `title` | 人間向けの短い要約 | |
| 37 | +| `status` | HTTP ステータスコード | |
| 38 | +| `detail` | 問題の詳細説明 | |
| 39 | +| `instance` | 特定の発生インスタンスを示す URI | |
| 40 | + |
| 41 | +サンプルとして、2 歳になる我が子に「早くお風呂に入ろう」とリクエストしたところ、「今はおもちゃで遊びたい」と 400 エラーで返された状況を problem details で表現してみます。なお、RFC 原文のサンプルは「残高不足」を扱っています。 |
| 42 | + |
| 43 | +```http |
| 44 | +HTTP/1.1 400 Bad Request |
| 45 | +Content-Type: application/problem+json |
| 46 | +Content-Language: ja |
| 47 | +
|
| 48 | +{ |
| 49 | + "type": "https://example.com/probs/busy-with-toys", |
| 50 | + "title": "今はお風呂に入れません。", |
| 51 | + "detail": "今はおもちゃで遊びたい気分なので、もう少し待ってください。", |
| 52 | + "instance": "/children/myson/bath-requests/20260420-0730", |
| 53 | + "status": 400 |
| 54 | +} |
| 55 | +``` |
| 56 | + |
| 57 | +この「共通フォーマットで機械可読なエラーを返す」というアイデアは普及し、多くのフレームワーク・ライブラリが対応しました。 |
| 58 | + |
| 59 | +- [RFC 7807 原文](https://www.rfc-editor.org/rfc/rfc7807.html) |
| 60 | +- [日本語訳](https://tex2e.github.io/rfc-translater/html/rfc7807.html) |
| 61 | + |
| 62 | +# RFC 9457: 7807 を置き換える改訂版 (2023年) |
| 63 | + |
| 64 | +RFC 9457 は 2023年7月に公開された Proposed Standard です。RFC 7807 を obsolete(廃止)する位置づけになっています。 |
| 65 | + |
| 66 | +Abstract にはこう書かれています。 |
| 67 | + |
| 68 | +> This document defines a "problem detail" to carry machine-readable details of errors in HTTP response content to avoid the need to define new error response formats for HTTP APIs. This document obsoletes RFC 7807. |
| 69 | +> (Google翻訳) |
| 70 | +この文書は、HTTP API 用の新しいエラー応答フォーマットを定義する必要性を回避するために、HTTP レスポンスコンテンツにエラーの機械可読な詳細情報を含めるための「問題の詳細」を定義します。この文書は RFC 7807 を廃止します。 |
| 71 | + |
| 72 | +「新しいフォーマットを定義するものではない」と明記されており、つまりフォーマットは 7807 と同じです。 **完全な後方互換性が保たれている** ため、7807 に準拠して実装したレスポンスはそのまま 9457 準拠として有効です。 |
| 73 | + |
| 74 | +- [RFC 9457 原文](https://www.rfc-editor.org/rfc/rfc9457.html) |
| 75 | +- [日本語訳](https://tex2e.github.io/rfc-translater/html/rfc9457.html) |
| 76 | + |
| 77 | +# 変更点 |
| 78 | + |
| 79 | +RFC 9457 の [Appendix D "Changes from RFC 7807"](https://www.rfc-editor.org/rfc/rfc9457.html#name-changes-from-rfc-7807) が挙げる変更点は3点です。 |
| 80 | + |
| 81 | +1. Section 4.2 で、共通の問題型 URI を登録する IANA レジストリを新設した |
| 82 | +2. Section 3 で、複数の問題をどう扱うべきかを明確化した |
| 83 | +3. Section 3.1.1 で、デリファレンスできない `type` URI の使用に関するガイダンスを追加した |
| 84 | + |
| 85 | +加えて Section 5 の Security Considerations もやや強化されています。新しいフィールドの追加や、既存フィールドの削除は一切ありません。 |
| 86 | + |
| 87 | +# 改善点①: `type` URI のガイダンス明確化 |
| 88 | + |
| 89 | +## 7807 時代の課題 |
| 90 | + |
| 91 | +RFC 7807 は「`type` URI はデリファレンス可能である必要はない」と書いていました。しかし実装者からすると、 |
| 92 | + |
| 93 | +- 相対 URI を使ってよいのか? |
| 94 | +- 存在しない URI を書いてよいのか? |
| 95 | +- 人間向けのドキュメントはどこに置けばいいのか? |
| 96 | + |
| 97 | +といった具体的な判断材料が不足しており、迷いやすい状態でした。結果として、各社で運用がバラけたわけです。 |
| 98 | + |
| 99 | +## 9457 での改善 |
| 100 | + |
| 101 | +RFC 9457 の [Section 3.1.1](https://www.rfc-editor.org/rfc/rfc9457.html#name-type) は以下の点を明確化しました。 |
| 102 | + |
| 103 | +- `type` はあくまで識別子であり、HTTP で取得できなくてもよい |
| 104 | +- 可能ならその URI に人間向けの説明(HTML)を置くことが望ましい |
| 105 | +- 型固有の拡張メンバの仕様は、別途ドキュメント化すべき |
| 106 | + |
| 107 | +これで、**社内向け API で相対 URI や URN を `type` に使う設計**が、公式にお墨付きを得た形です。 |
| 108 | + |
| 109 | +## 具体例 |
| 110 | + |
| 111 | +子どもが夕食前に「ゼリーを食べたい」とリクエストしたものの、「夕食前のおやつはダメ」ルールで却下された、というケースを例に見てみます。 |
| 112 | + |
| 113 | +**❌ Before (7807 時代)**: 絶対 URI でないと不安で、存在しない URL をわざわざ書く |
| 114 | + |
| 115 | +```json |
| 116 | +{ |
| 117 | + "type": "https://api.example.com/problems/snack-before-dinner", |
| 118 | + "title": "夕食前のおやつは食べられません", |
| 119 | + "detail": "夕食まであと 30 分です。ゼリーは夕食の後に食べましょう。", |
| 120 | + "status": 422 |
| 121 | +} |
| 122 | +``` |
| 123 | + |
| 124 | +この URL を開いても 404 なのですが、それでも「絶対 URI にしておけば安全かな」と書きがちでした。 |
| 125 | + |
| 126 | +**✅ After (9457)**: 相対 URI や URN を堂々と使える |
| 127 | + |
| 128 | +```json |
| 129 | +{ |
| 130 | + "type": "/problems/snack-before-dinner", |
| 131 | + "title": "夕食前のおやつは食べられません", |
| 132 | + "detail": "夕食まであと 30 分です。ゼリーは夕食の後に食べましょう。", |
| 133 | + "status": 422 |
| 134 | +} |
| 135 | +``` |
| 136 | + |
| 137 | +URN でも OK です。 |
| 138 | + |
| 139 | +```json |
| 140 | +{ |
| 141 | + "type": "urn:example:problems:snack-before-dinner", |
| 142 | + "title": "夕食前のおやつは食べられません", |
| 143 | + "detail": "夕食まであと 30 分です。ゼリーは夕食の後に食べましょう。", |
| 144 | + "status": 422 |
| 145 | +} |
| 146 | +``` |
| 147 | + |
| 148 | +## 備考 |
| 149 | + |
| 150 | +「どんなドキュメントを、どこに、どの形式で置くか」まで踏み込んだ指針は RFC には書かれていません。そこは各組織の設計判断に委ねられています。 |
| 151 | + |
| 152 | +# 改善点②: 複数エラーの扱い |
| 153 | + |
| 154 | +## 7807 時代の課題 |
| 155 | + |
| 156 | +フォームバリデーションのように「複数の問題を同時に返したい」場面に対する指針が、RFC 7807 にはありませんでした。結果、各フレームワーク・各社が独自拡張を持つことになりました。 |
| 157 | + |
| 158 | +- Spring の `invalid-params` |
| 159 | +- JSON API 系の `errors` |
| 160 | +- 独自の `violations` / `issues` など |
| 161 | + |
| 162 | +命名も構造もバラバラで、相互運用性が弱かったわけです。 |
| 163 | + |
| 164 | +## 9457 での改善 |
| 165 | + |
| 166 | +RFC 9457 の [Section 3](https://www.rfc-editor.org/rfc/rfc9457.html#name-the-problem-details-json-ob) が、まず次の方針を示しました。 |
| 167 | + |
| 168 | +> When a problem in an HTTP response has multiple problems, it is RECOMMENDED that a single problem be chosen to represent the response, preferring the most relevant or urgent problem. |
| 169 | +> (Google翻訳) |
| 170 | +HTTPレスポンスに複数の問題が含まれている場合、レスポンスを代表する問題として、最も関連性が高い、または緊急性の高い問題を優先して選択することが推奨されます |
| 171 | + |
| 172 | +レスポンス全体としては単一の problem details を選ぶのが推奨、という方針です。そのうえで、関連する複数の問題を伝えたい場合は拡張メンバに含めればよいとし、Section 3.2 で `errors` 配列を拡張メンバの**例**として具体的に示しています。 |
| 173 | + |
| 174 | +朝の幼稚園準備で、検温忘れ・水筒が空・連絡帳未記入、名札がない靴下の利用という 4つのミスが一度に検出された、というケースを例にしてみます。 |
| 175 | + |
| 176 | +```http ✅️具体例 |
| 177 | +HTTP/1.1 400 Bad Request |
| 178 | +Content-Type: application/problem+json |
| 179 | +Content-Language: ja |
| 180 | +
|
| 181 | +{ |
| 182 | + "type": "/problems/kindergarten-preparation-failed", |
| 183 | + "title": "幼稚園の持ち物に不備があります", |
| 184 | + "errors": [ |
| 185 | + { "detail": "朝の検温がまだ終わっていません", "pointer": "#/healthCheck/temperature" }, |
| 186 | + { "detail": "水筒が空のままです", "pointer": "#/items/waterBottle" }, |
| 187 | + { "detail": "連絡帳の家庭欄が未記入です", "pointer": "#/items/communicationNotebook" }, |
| 188 | + { "detail": "名札がついていない靴下を履いています", "pointer": "#/clothing/socks" } |
| 189 | + ] |
| 190 | +} |
| 191 | +``` |
| 192 | + |
| 193 | +## 備考 |
| 194 | + |
| 195 | +ただし、`errors` 配列は規範化されているわけではなく、あくまで **例示**です。位置指定の手段(JSON Pointer か JSONPath か独自キー名か)も自由で、依然として実装者の裁量は広く残っています。 |
| 196 | + |
| 197 | +# 改善点③: 共通型レジストリの新設 |
| 198 | + |
| 199 | +## 7807 時代の課題 |
| 200 | + |
| 201 | +「Rate Limit 超過」「Quota 超過」のような、どの API でも共通で使えそうなエラー型でさえ、各社が独自の URI を定義していました。再利用と収束のための仕組みが仕様側になかったわけです。 |
| 202 | + |
| 203 | +## 9457 での改善 |
| 204 | + |
| 205 | +RFC 9457 の [Section 4.2](https://www.rfc-editor.org/rfc/rfc9457.html#name-registered-problem-types) で IANA に [HTTP Problem Types レジストリ](https://www.iana.org/assignments/http-problem-types/http-problem-types.xhtml) が新設されました。登録テンプレートには以下の項目を含めます。 |
| 206 | + |
| 207 | +- Type URI |
| 208 | +- Title |
| 209 | +- Recommended HTTP status code |
| 210 | +- Reference |
| 211 | + |
| 212 | +## 登録方法 |
| 213 | + |
| 214 | +登録ポリシーは **Specification Required** で、[RFC 8126](https://www.rfc-editor.org/rfc/rfc8126.html) Section 4.6 に基づきます。簡単に言うと以下です。 |
| 215 | + |
| 216 | +- 登録したい問題型の仕様書(公開された安定したドキュメント)を用意する |
| 217 | +- 上記テンプレートの項目を埋めて IANA に申請する |
| 218 | +- **Designated Expert** がレビューして承認・却下を判断する |
| 219 | +- 審査では「定義の明確性」「RFC 9457 の要件適合性」「コミュニティからのフィードバック」が考慮される |
| 220 | + |
| 221 | +**ベンダー固有・アプリケーション固有・デプロイ固有の値は登録不可**とされており、広く再利用できる共通型だけが対象です。 |
| 222 | + |
| 223 | +プレフィックスとして `https://iana.org/assignments/http-problem-types#` を使うことも可能ですが、RFC 自身も「この URI は解決可能でない場合がある」と書いており、あくまで識別子として使う位置づけです。 |
| 224 | + |
| 225 | +## 例示 |
| 226 | + |
| 227 | +子どもが 1 分間に 20 回「おかし食べたい」と連投してくる状況を想定します。API としては HTTP 共通の 429 Too Many Requests を返したいところですが、同じ 429 エラーでも実装ごとに `type` URI の付け方はバラバラでした。 |
| 228 | + |
| 229 | +**❌ Before (7807 時代)**: 共通のエラーでも実装ごとに独自 URI を定義していた |
| 230 | + |
| 231 | +```json |
| 232 | +{ |
| 233 | + "type": "https://a-family.example/problems/too-many-snack-requests", |
| 234 | + "title": "Too Many Requests", |
| 235 | + "status": 429 |
| 236 | +} |
| 237 | +``` |
| 238 | + |
| 239 | +```json |
| 240 | +{ |
| 241 | + "type": "https://b-family.example/problems/snack-request-cooldown", |
| 242 | + "title": "Too Many Requests", |
| 243 | + "status": 429 |
| 244 | +} |
| 245 | +``` |
| 246 | + |
| 247 | +```json |
| 248 | +{ |
| 249 | + "type": "urn:c-family:problems:snack-quota-exceeded", |
| 250 | + "title": "Too Many Requests", |
| 251 | + "status": 429 |
| 252 | +} |
| 253 | +``` |
| 254 | + |
| 255 | +**✅ After (9457)**: 共通型を IANA レジストリで共有できる仕組みが整った |
| 256 | + |
| 257 | +現状は `about:blank` のみ登録済みで、型に特別な意味を持たせない場合の標準値として使います。 |
| 258 | + |
| 259 | +```json |
| 260 | +{ |
| 261 | + "type": "about:blank", |
| 262 | + "title": "Too Many Requests", |
| 263 | + "status": 429, |
| 264 | + "detail": "おやつの要求が多すぎます。30 分後にまた声をかけてください。" |
| 265 | +} |
| 266 | +``` |
| 267 | + |
| 268 | +将来的に Rate Limit 超過のような汎用エラー型が IANA に登録されれば、複数の API 間で `type` を共通化できるようになるというのがレジストリ新設の狙いになります。 |
| 269 | + |
| 270 | +## 備考 |
| 271 | + |
| 272 | +[2026年4月時点のレジストリ](https://www.iana.org/assignments/http-problem-types/http-problem-types.xhtml)に登録されているのは 3 件です。`about:blank` に加えて、`https://iana.org/assignments/http-problem-types#date` と `#ohttp-key` がありますが、後者 2 つはいずれも [RFC 9458 Oblivious HTTP](https://www.rfc-editor.org/rfc/rfc9458.html) 由来で登録されたものです。汎用的な業務エラー型(Rate Limit 超過、バリデーションエラー等)が登録されているわけではありません。 |
| 273 | + |
| 274 | +# 改善点④: Security Considerations の補強 |
| 275 | + |
| 276 | +## 7807 時代の課題 |
| 277 | + |
| 278 | +RFC 7807 にもセキュリティ上の注意書きはありましたが、具体性に欠けていました。結果、`detail` フィールドにスタックトレースや内部パスがそのまま載る実装が散見される状態でした。 |
| 279 | + |
| 280 | +## 9457 での改善 |
| 281 | + |
| 282 | +RFC 9457 の [Section 5](https://www.rfc-editor.org/rfc/rfc9457.html#name-security-considerations) はより踏み込んだ記述になっています。 |
| 283 | + |
| 284 | +- スタックダンプ・内部実装詳細の露出を避ける |
| 285 | +- `instance` に不透明でない(デバッグ性のある)URL を置くと、情報漏洩経路になりうる |
| 286 | +- クライアントは信頼できないソースから来た problem details を、安易にそのまま表示しない(XSS 観点) |
| 287 | + |
| 288 | +## 具体例 |
| 289 | + |
| 290 | +冒頭のお風呂サンプルを引きずって、「子どもがお風呂に持ち込むおもちゃが電池式だったので業務ルールで弾いた」ケースをイメージしてみます。 |
| 291 | + |
| 292 | +**❌ Before (悪い例)**: `detail` にスタックトレースをそのまま載せる |
| 293 | + |
| 294 | +```json |
| 295 | +{ |
| 296 | + "type": "/errors/bath-preparation-failed", |
| 297 | + "title": "Bath preparation failed", |
| 298 | + "detail": "com.example.bathtime.exception.ToyNotWaterproofException: Battery-powered toy cannot be brought into the bathroom\n at com.example.bathtime.validator.ToyWaterSafetyValidator.validate(ToyWaterSafetyValidator.java:58)\n at com.example.bathtime.service.BathPreparationService.validateItems(BathPreparationService.java:34)\n at com.example.bathtime.service.BathPreparationService.prepare(BathPreparationService.java:21)\n at com.example.bathtime.controller.BathController.startBath(BathController.java:19)", |
| 299 | + "status": 422 |
| 300 | +} |
| 301 | +``` |
| 302 | + |
| 303 | +パッケージ構成・例外クラス名・内部バリデータ構造まで透けて見えるため、攻撃者の下調べに使われかねません。 |
| 304 | + |
| 305 | +**✅ After (良い例)**: 利用者向けは業務的な説明にとどめ、相関 ID で運用側から追跡可能にする |
| 306 | + |
| 307 | +```json |
| 308 | +{ |
| 309 | + "type": "/errors/toy-not-waterproof", |
| 310 | + "title": "このおもちゃはお風呂に持ち込めません", |
| 311 | + "detail": "電池で動くおもちゃはお風呂場に持ち込めません。別のおもちゃを選んでください。", |
| 312 | + "instance": "/errors/trace/a1b2c3d4e5f6", |
| 313 | + "status": 422 |
| 314 | +} |
| 315 | +``` |
| 316 | + |
| 317 | +`instance` には不透明な相関 ID(トレース ID 等)だけを入れ、サーバ側のログと突き合わせればデバッグできる運用にしておくのが定石です。 |
| 318 | + |
| 319 | +## 備考 |
| 320 | + |
| 321 | +「どこまで出してよいか」の線引きは、公開 API(B2C)か社内 API かで大きく変わります。最終的には設計者が文脈を踏まえて判断するしかなく、RFC としては「気をつけろ」以上のことは書けないものと思います。 |
| 322 | + |
| 323 | +# フューチャーの Web API 設計ガイドラインでの扱い |
| 324 | + |
| 325 | +私がフューチャーの [Web API 設計ガイドライン](https://future-architect.github.io/arch-guidelines/documents/forWebAPI/web_api_guidelines.html) の執筆に関わった関係で、ガイドライン側との対応関係にも触れておきます。 |
| 326 | + |
| 327 | +ガイドラインではエラーレスポンスを RFC 9457 準拠で推奨しており、主な整合点は以下の通りです。 |
| 328 | + |
| 329 | +- `type` は相対パス URI を推奨(9457 Section 3.1.1 の「デリファレンス不可でよい」ガイダンスに相当) |
| 330 | +- 複数エラーは `errors` 配列で表現し、件数が 1 件でも配列形式で統一 |
| 331 | +- `errors[].detail` を必須、`pointer` を任意とする |
| 332 | +- エラー時の Content-Type は `application/problem+json` を推奨 |
| 333 | +- B2C 公開 API では詳細情報を控え、内部 API では詳細を許容(9457 Section 5 Security Considerations と整合) |
| 334 | + |
| 335 | +興味ある方は、ぜひ [ガイドライン本体](https://future-architect.github.io/arch-guidelines/documents/forWebAPI/web_api_guidelines.html) も読んでみてください。 |
| 336 | + |
| 337 | +# まとめ |
| 338 | + |
| 339 | +RFC 7807 から RFC 9457 で変わったことは、フォーマットとしてはゼロに等しく、本質的には「**実務で迷いやすかった部分のガイダンスが整理された**」という位置づけのアップデートです。 |
| 340 | + |
| 341 | +新規の Web API 設計では、まず RFC 9457 を読み、`application/problem+json` を返すところから始めれば良いと思いました。差分が少ないこと自体が、この仕様がもう十分に枯れていることを示しているようで、個人的には読んでいて心地よい RFC の更新でした。 |
| 342 | + |
0 commit comments