mcpField
A
api-error-design

api-error-design

github.com/samber開発78.5k 回インストール3 日前に更新
リポジトリ

api-error-design について

API チーム向けに公開 API のエラー応答を設計するスキルです。分類体系、RFC 9457 の形式、メッセージ、再試行シグナル、ドキュメントを扱います。

主な機能

  1. 01既存のエラー応答の監査
  2. 02エラーコード分類の選定
  3. 03RFC 9457 形式の設計
  4. 04実行可能なエラーメッセージの作成
  5. 05再試行シグナルの定義
  6. 06エラー記録とリクエスト追跡

活用シーン

  1. 01稼働中 API のエラー応答の監査
  2. 02新しい REST API のエラー形式設計
  3. 03機械可読な再試行処理の改善

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/samber/developer-platform-skills --skill api-error-design

インストール後、Claude Code で/api-error-designを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

evals/
evals.json
references/
error-documentation-examples.md
message-rewrite-examples.md
rfc9457-payload-examples.md
taxonomy-catalog-examples.md

詳細

API エラー設計

API エラーサーフェスの設計者として、リクエストが失敗したときに公開 API が返す内容(コード、エンベロープ、メッセージ、再試行シグナル、ドキュメント)を設計します。インテグレーターがサポートチケットを起票せず、レスポンスだけで問題を解決できるようにします。

RFC 9457 が掲げる目的が、ここでの指針です。共通のエラー形式を定義することで、「独自の形式を定義したり、さらに悪いことに、既存の HTTP ステータスコードの意味を再定義したりする必要がなくなる」ことを目指します。

確認事項

メモリ(推奨): メモリをファイルに保存する場合は、developer-platform-context.md の利用を検討してください。別のメモリシステムを使用している場合は、そちらを利用してください。このファイルは参考資料であり、必須ではありません。タスクごとに情報を別々のセクションに分けてください。完了したタスクは削除してください。タスクには日付を付け、一般的なプロジェクトのコンテキストには日付を付けないでください。インタビューへの回答は、2 つのタスク間で異なる場合があります。

設計を始める前に、以下を確認してください。回答によって後続の手順が変わります。まとめて質問してください。これは戦術的な設計タスクであり、戦略インタビューではありません。

  1. パラダイム:REST のみ、gRPC のみ、それとも両方ですか?(分類方式の選択に影響します)
  2. 新規開発ですか、それとも既存 API の改修ですか?既存 API の改修の場合は、異なるエンドポイントから実際の本番エラー応答を 5~10 件提示してもらいます。
  3. 既存のクライアントが分岐処理に使っているコード、フィールド、メッセージ文字列はどれですか?(これらは契約です。「安定性に関する契約」を参照してください)
  4. エラーを利用するのは誰ですか?自社アプリの開発者、サードパーティのインテグレーター、機械/エージェントの呼び出し元、それとも複数ですか?(次のセクションを参照してください)
  5. ドメインには、1 つのエラーコードでは情報が明らかに不足する、複数層にわたる失敗原因(決済の拒否、不正行為、コンプライアンスなど)がありますか?
  6. 移行の上限:新しいエラーサーフェスはいつまでにリリースする必要がありますか?一度限りのクリーンアップですか、それとも複数のサービスで長年にわたって共有する分類体系ですか?また、クライアント側でどの程度の移行コストを許容できますか?(分類方式の優先順位を変えます。「手順 2」を参照してください)

利用者の種類

設計を左右するのは、利用者の種類です。

  • 自社アプリの開発者 - 内部ドキュメントを読んだり、チームメイトに尋ねたりできます。内部向けのコードや簡潔なメッセージでも、大きな負担にはなりません。対応に最もコストがかからない利用者です。
  • サードパーティのインテグレーター - セルフサービスで問題を解決できることが最も重要です。次の情報が必要です。
    • 公開されたエラーカタログ
    • 各コードに対応する documentation_url
    • フィールド/値/制約の詳細
    • サポートへのエスカレーション用リクエスト ID
  • 機械およびエージェントの利用者 - 分岐処理には、安定した構造化フィールドだけを使います。retryable ブール値と型付きの詳細フィールドが特に重要です。文章のメッセージは二次的なものであり、唯一のシグナルにしてはいけません。
  • クライアントの背後にいるエンドユーザー - メッセージがそのまま転送されることがあります。Stripe は、カードエラーの message テキストについて、エンドユーザーに表示しても安全であることを明示しています。コードごとに安全性を判断し、カタログに記載してください。

存在する中で最も要求の厳しい利用者に合わせて設計してください。サードパーティのインテグレーターとエージェントに十分なサーフェスを用意すれば、自社開発者の要件も追加コストなしで満たせます。逆は成り立ちません。

ワークフロー

  1. 既存のエラーサーフェスを監査します(新規開発の場合は、失敗条件を列挙します)。
  2. 分類方式を選びます。フラットなカタログ、コード/サブコード、ドメイン/理由のいずれかです。
  3. エンベロープを採用します。まずステータスコードを決め、次に RFC 9457 と拡張メンバーを使用します。
  4. メッセージを書き直します。人が読めて、実行可能な対応につながり、一貫性のあるものにします。
  5. 再試行可能性のシグナルを設計します。
  6. エンドポイントごとにすべてのエラーを文書化し、リクエスト ID の追跡を組み込みます。

各手順のセクションは、以下に順番に記載されています。

1. エラーの発生範囲を監査する

  • エンドポイントごとの実際のエラーレスポンスを収集する(既存システムの改修の場合)。新規開発の場合は、エンドポイントごとのすべての失敗条件を列挙します。バリデーション、認証、競合、レート制限、サーバー障害のケースを含めます。
  • 各レスポンスを、このファイルの末尾にある「失敗モードのチェックリスト」に照らして評価し、見つかった問題をすべて記録します。
  • リリース済みのコード、フィールド、メッセージ文字列をすべて洗い出します。クライアントが観測した値には、すでに依存している可能性があります(ヒュラムの法則)。そのため、再設計では旧来の識別子を新しいものに対応付け、黙って破棄してはなりません。
  • 監査結果は、エンドポイントごとに1行の表にまとめ、次の列を設けます。
    • endpoint
    • status returned
    • envelope shape
    • code present?
    • field-level detail?
    • retry signal?
    • documented?
    • findings

2. タクソノミーを選ぶ

タクソノミーは、呼び出し元が分岐処理に使う機械可読な識別体系です。形は3種類あり、次の順に評価します。

  • 効率:code/subcode catalog > flat code catalog > domain/reason (Google AIP-193)

  • 工数:domain/reason > code/subcode catalog > flat code catalog

  • 価値:domain/reason > code/subcode catalog > flat code catalog

  • デフォルトの選択肢:コード/サブコードのカタログ。 カテゴリごとにトップレベルの code を1つ設け、それぞれを1つのHTTPステータスに固定し、具体的な理由を subcodes で示します。フィールドレベルのバリデーションやクライアントの分岐処理に十分な詳細度を保ちつつ、HTTPとの結び付きを維持できます。RESTのみの公開APIでは、工数あたりの価値が最も高い選択肢です。カタログ作成にかかるのは約1日で、その大半はレビューです。担当は1チームで済み、サービス間の合意形成も不要です。

  • 対象範囲が小さく、各カテゴリに実質的な理由が1つしかない場合は、フラットなカタログに切り替えます。 ステータスに固定したコードの一覧だけで完結するため、サブコードを設けても中身のない枠組みになります。工数はほぼゼロで、列挙に1〜2時間ほどです。

  • APIファミリーがRESTとgRPCにまたがる場合、または複数のサービスで共通のタクソノミーを出力する必要がある場合は、domain/reason(Google AIP-193の ErrorInfo)に引き上げます。 domain と reason により、HTTPステータスに依存せずエラーを識別でき、動的なコンテキストには metadata マップを使えます。これは最も工数のかかる選択肢で、価値も最も高くなります。出力するすべてのサービスを対象に、domainとreasonについて四半期規模の合意形成が必要です。そのため効率性の評価では毎回不利になりますが、前述の2つの条件が、そもそもこの方式を選ぶ理由になります。

  • gRPCのみの対象範囲では、HTTPステータスに固定する選択肢は格下げではなく、候補から除外します。 フラットなカタログとコード/サブコードのカタログは、各コードを1つのHTTPステータスに固定して識別する仕組みですが、gRPCのみの対象範囲にはHTTPステータスがありません。質問1への回答が「gRPCのみ」であれば、これらは候補から外れ、domain/reasonだけが残ります。最下位に置かないのは、「ステータスの対応付けは後で行う」という先送りが、誰かがHTTPゲートウェイを追加した時点で、いつのまにか作業範囲に再登場するためです。RESTの対象範囲がファミリーに加わったときに、再検討します。

  • この順位付けはデフォルトであり、絶対的なルールではありません。質問6や、ユーザーについて把握しているその他の情報に基づき、順位を付け直してください。

    • 近々リリース予定がある場合は、フラットなカタログを優先します。
    • 複数のサービスで長年共有するタクソノミーには、domain/reasonを選びます。
    • gRPCやGoogle Cloudの慣習に精通したチームであれば、domain/reasonをほぼ追加工数なしで導入できます。これにより工数の評価が変わり、最有力候補も入れ替わります。

    Stripeのように決済を扱うドメインでは、どの選択肢を選んでも、Stripeの階層型の形式(type + code + decline_code)が必要になることがあります。

どの形式を選ぶ場合でも、クライアントが目にする可能性のあるすべてのコードを事前に列挙した、単一の参照ドキュメントとしてカタログを公開してください。呼び出し箇所で場当たり的に作られ、カタログに追加されないエラーコードは、細部の問題ではなく設計上の欠陥です。

3つの形式すべての詳しい抜粋、フィールドレベルおよびフィールド間のバリデーションの詳細、StripeとGoogleの事例については、references/taxonomy-catalog-examples.mdを参照してください。

3. エンベロープを採用する

  1. まず適切な HTTP ステータスを選びます。問題詳細はステータスを説明するボディ形式であり、ステータスの代わりではありません。ステータスだけを読むプロキシ、キャッシュ、モニターにも、正しいシグナルが届くようにします。
  2. RFC 9457 の問題詳細を使用します(RFC 7807 に代わる規格です。規格としては 9457 を明記してください)。type、title、status、detail、instance、メディアタイプ application/problem+json を使用します。
  3. 拡張メンバーを通じて分類体系を付与します。code、フィールド単位の詳細を示す errors: [...]、retryable、documentation_url、request_id を使用します。標準フィールドは汎用的で人が読める内容にし、拡張メンバーに機械可読な識別情報を持たせます。標準形式が拡張に対応している場合に、独自の非標準エンベロープを別途作成してはいけません。
  4. どこでも同じエンベロープを使用します。すべてのエンドポイント、すべてのステータスで統一してください。形式の混在は、兄弟レビュー用スキルが指摘する、レビューで最もよく見られる問題です。GraphQL も提供するサーフェスでは、ここで設計する REST エンベロープに相当するものはありません。GraphQL は常に 200 を返し、型付き結果オブジェクトの errors 配列にエラーを含めます。この形式は samber/developer-platform-skills@public-graphql-api-design が対象としています。
  5. よくあるステータスの混同を明確に解消します。
    • 400(形式が不正)と 422(構文的には有効だが、意味的に不正)
    • 401(認証されていない)と 403(認証済みだが、認可されていない)
  6. 部分エラーを返すレスポンスは拒否します。成功と項目ごとの失敗をボディ内に混在させた単一の 200 は、ステータスコードを回避し、クライアントに個別対応を強いることになります(AIP-193 の警告)。許容されるのは一括操作または長時間実行される操作に限られます。その場合でも、項目ごとの失敗には常に同じエラー形式を再利用し、その場限りの形式は使いません。

適切なペイロードと不適切なペイロードの完全な例については、references/rfc9457-payload-examples.md を参照してください。

4. メッセージを書き直す

優れたメッセージは、人が読めて、対処につながり、API 全体で形式が一貫しています(Speakeasy が示す 3 つの特性)。この 3 つの特性はそれぞれ独立しています。読みやすくても対処につながらないメッセージや、対処につながっても読みやすくないメッセージもあります。それぞれを個別に確認してください。

  • 同じエラーオブジェクト内に、フィールド名、受け取った値、違反した制約を示します。典型的な失敗例は、Zoho Creator の実際の {"code": 2945, "description": "LESS_THAN_MIN_OCCURANCE"} です。ルール名だけで、フィールドも、期待値も、自分で解決する方法も示されていません。
  • 平易で、責めることなく、修正方法がわかる表現を使います(Nielsen Norman Group のガイドライン)。「日付は MM/DD/YYYY 形式で入力してください」とし、「無効な日付を入力しました」とはしません。NN/g はエンドユーザー向けの文面を小学 7~8 年生程度の読解レベルにすることを推奨しています。開発者向けの文面では、より正確な情報を含めてもかまいませんが、形式は同じにします。読者はストレスを感じながらデバッグしています。
  • 誰が対応するかに応じて重大度を分けます(Ted Spence の考え方)。
    • 呼び出し元が処理を止めてリクエストを変更すべき場合は 4xx
    • 後で再試行すべき場合、またはプロバイダーがバグを修正すべき場合に限り 5xx
  • すべてのメッセージに、それだけで完結する文章を無理に書かないでください。コード外で解決するほうが簡単なエラーもあります(ルールの根拠を説明するのに長い段落が必要な場合など)。段落を message に詰め込むのではなく、documentation_url でそのコードのカタログ項目にリンクしてください。
  • ローカライズ:message/detail では Accept-Language を尊重し、Content-Language を返し、code は常に翻訳せずに保ちます。Google の方式では、message を開発者向けの安定した内容に保ち、エンドユーザー向けのテキストにはローカライズ済みメッセージの詳細を別途追加します。クライアントがログに記録し、照合に使うフィールドは決して翻訳しないでください。

適切な例と不適切な例の対比については、references/message-rewrite-examples.md を参照してください。

5. 再試行可能性を示すシグナルを設計する

エラーレスポンスが示す内容を決めます。sibling samber/developer-platform-skills@api-idempotency-retry がクライアント側の仕組み(バックオフアルゴリズム、ジッター、冪等性キー)を担当します。アルゴリズムではなく、フィールドを設計してください。

  • ステータスコードによる基本的な分類:
    • 再試行可能:408、429(常に Retry-After を付ける)、502、503、504、および場合によっては500
    • 再試行不可:400、401、403、404、409、422(リクエスト自体に誤りがあり、変更せずに再送しても同じ結果になるため)
  • 最低限のシグナル:429と503のレスポンスに Retry-After ヘッダーを付けます。本文の形式を把握していない汎用クライアントやプロキシでも解析できる、実際のHTTPヘッダーです。
  • より詳細なシグナル:ヘッダーだけでは呼び出し側の選択肢を十分に示せない場合は、本文に retryable、retry_after、および任意のガイダンスフィールドを含む明示的な再試行オブジェクトを設けます。
  • ヘッダーがある場合でも retryable: true|false を含めます。これは、汎用エラーハンドラーがステータスコードのルックアップテーブルを参照せずに分岐できる唯一のフィールドであり、すべてのクライアントライブラリがヘッダーを解析済みの本文と同じように簡単に取り出せるわけではありません。
  • 500は本当に曖昧なステータスとして扱います。一時的な問題なのか、実際のバグなのか判断できません。呼び出し側に推測させず、500では再試行オブジェクトを明示的に返してください。再試行シグナルのない単なる500は、解消すべき設計上の不足です。

ペイロードの例はreferences/rfc9457-payload-examples.mdにあります。

6. すべてのエラーを文書化し、すべてのリクエストを追跡する

  • 各エンドポイントが返すステータスをOpenAPIのresponsesブロックに列挙し、ステータスごとに異なるエラーコードがある場合は、そのコードごとに名前付きの例を用意します。汎用的なスキーマ参照を1つだけ記載してはいけません。これにより、呼び出し側が本番環境でエラーに遭遇する前に、その内容を調べられるよう、エラーカタログと結び付けられます。
  • 手順2のカタログを公開ページ1つにまとめ、各コードに安定したアンカーを付けて、documentation_urlから参照できるようにします。サポートチケットを受けてからではなく、コードをリリースする前に文書化してください(Stripeのdoc_urlが意図する設計です)。
  • すべてのレスポンスでリクエストIDヘッダー(例:X-Request-ID)を返し、エラー本文のrequest_idにも同じIDを含めます。ヘッダーがなくても、エラー本文をコピーしてチケットに貼り付けたときにIDが残る必要があります。これは、「エラーが発生しました。表示された内容はこちらです」というチケットを「リクエストIDはこちらです」というチケットに変えるうえで、最も効果の高い追加項目です。
  • ツールが対応している場合は、公開カタログとエンドポイントごとの例を仕様から再生成し、ドキュメントとレスポンスが気付かないうちに食い違わないようにします。

OpenAPIの形式とリクエストIDの設定については、references/error-documentation-examples.mdを参照してください。

安定性に関する契約

エラーコードはAPI契約の一部です。ほかの破壊的変更と同様に扱ってください。

  • Hyrumの法則はエラーにも全面的に当てはまります。ユーザーが十分に増えれば、メッセージの文言、順序、文書化されていないコードも含め、観測できるあらゆる挙動に依存する人が現れます。何を契約とするか、意図を明確にしてください。

  • カタログで明示します。

    • code(またはdomain+reason)は安定しており、分岐条件として安全です。
    • message/detailの文言は契約ではなく、予告なく変更される場合があります(GoogleはStatus.messageについてこれを明言しています)。
  • 変更は追加のみで進めます。

    • 新しいコードや新しい任意フィールドは自由に追加できます。
    • リリース済みのコードを別の用途に転用、改名、削除してはいけません。

    Googleの契約がモデルになります。(domain, reason)の組み合わせは「時間が経っても一貫している必要があり」、メタデータキーは拡張のみ可能です。

  • 再設計によってコードを廃止する必要がある場合は、APIの非推奨化の仕組み(sibling samber/developer-platform-skills@api-versioning-policy の担当範囲)を通してください。エラーコードの廃止はバージョンの境界で行い、無言で変更してはいけません。

  • カタログの管理責任者を決めます。新しいコードをリリースする前に必ず確認する担当者またはレビュー手順を設けてください。具体的な適用方法は組織によって異なりますが、守るべき不変条件は「カタログに記載されていないコードを本番環境に出さないこと」です。

障害パターン

アンチパターンのチェックリスト。いずれも、そのまま監査で指摘されます。

  • フィールド、値、制約のいずれも明示されていない汎用的なメッセージ。
  • 500 のレスポンス本文にスタックトレース、データベースのエラーテキスト、内部パス、または設定情報が漏れている。これは DX だけの問題ではなく、セキュリティ上の指摘事項です。
  • エンベロープの形式がエンドポイント間、またはバリデーションエラーとサーバーエラーの間で異なる。
  • レスポンスにコードが含まれているのに、公開カタログには掲載されていない。
  • ステータスコードが誤っている。特に、本文にエラーオブジェクトがあるのに 200 を返している。
  • エラーレスポンスにリクエスト ID がない。
  • エンドポイントのドキュメントに記載された例が、実際に返されるすべてのエラーを網羅していない。
  • リトライの手掛かりがない、単独の 500。
  • 複数フィールドにまたがるバリデーションエラーを、単一フィールド用の形式に無理に当てはめている。
  • code 値を翻訳または別の用途に転用している。

測定

  • エンベロープの一貫性:エンドポイントとステータスの 100% で同じ形式を使用します。呼び出し元にとって「一貫している」かどうかは二択であるため、形式が異なるエンドポイントが 1 つでもあれば監査不合格です。
  • カタログの網羅性:レスポンスで確認できるコードのうち、公開カタログまたは各エンドポイントのドキュメント例に記載されていないものをゼロにします。
  • サポートの指標:リクエスト ID を添えて届くエラー関連のサポートチケットの割合と、エラーの意味を尋ねるチケットの件数を追跡します。方向性としては、根拠のある主張に従います(リクエスト ID のエコーによって、曖昧な問い合わせを追跡可能な問い合わせにできます)。業界標準のしきい値はないため、プラットフォーム独自の初月の実績を基準値として設定し、それを改善してください。

エンベロープの一貫性とカタログの網羅性の両方が合格するまで設計を繰り返してください。これらは必須条件です。サポートの指標はその後に注視する傾向であり、合格基準にはしないでください。

呼び出し例

  • 「公開 REST API のエラー分類を設計してください。現在、すべての失敗で説明文だけの 500 が返されます。」
  • 「RFC 9457 を採用します。既存のエラーコードを、現在のインテグレーターに影響を与えずに problem details に対応付けてください。」
  • 「/payments エンドポイントからのエラーレスポンスを監査し、インテグレーターからの問い合わせを減らせるようにメッセージを書き直してください。」

参考資料

同じコレクションの関連項目:

  • samber/developer-platform-skills@public-api-design-review - API 全体の一貫性をレビューします。エンベロープが 1 つに統一されているかを確認するもので、このスキルではエンベロープの内容を設計します。
  • samber/developer-platform-skills@api-idempotency-retry - ここで設計するシグナルを利用する、クライアント側のリトライの仕組み(バックオフ、ジッター、冪等性キー)を扱います。
  • samber/developer-platform-skills@api-rate-limit-policy - このエラー分類が扱う 429 の制限を扱います。どの制限に達したのか、Retry-After の値、スロットリングが 200 の中に含まれて返される可能性の有無が対象です。このスキルでは、その回答を載せるエンベロープを扱います。
  • samber/developer-platform-skills@api-reference-quality - エンドポイントごとのエラー説明が掲載されるリファレンスドキュメントを監査します。
  • samber/developer-platform-skills@api-versioning-policy - エラーコードを廃止するための非推奨化の仕組みを扱います。
  • samber/developer-platform-skills@public-grpc-api-design - このエラー分類が対象とする範囲に gRPC が含まれる場合、gRPC 固有のエラーの仕組み(google.rpc.Status、ゲートウェイでの HTTP マッピングに関する落とし穴)を扱います。

コメント