api-design について
api-design は、利用者向け API 契約とプロトコルの意味を設計するスキルです。互換性、エラー、検証、進化を扱い、インターフェースを定義・変更するチームを支援します。
主な機能
- 01契約を先に定めるインターフェース設計
- 02後方互換性を保つ API の進化
- 03一貫したエラーレスポンスの意味付け
- 04境界での入力検証の指針
- 05リクエストの冪等性に関する指針
活用シーン
- 01新しい API エンドポイントの設計
- 02利用者を壊さない公開インターフェースの変更
- 03独立してリリースされるチーム間の契約定義
- 04データベーススキーマに基づく API 形状の策定
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ citypaul/ .dotfiles --skill api-designインストール後、Claude Code で/api-designを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
LICENSEresources/api-evolution.mdapi-security.mdauth-security.mdhttp-fundamentals.mdproblem-details.mdsource-notes.md詳細
API とインターフェースの設計
コンシューマーとの互換性とプロトコルのセマンティクスを扱う場合は、このスキルを使用してください。プロセス内モジュールの責務、呼び出し元が担う負担全体、情報隠蔽、深さについては、codebase-design を読み込んでください。内部モジュールが公開契約やチーム間契約も公開する場合は、両方を使用してください。重要なゲートウェイ、フレームワーク、SDK、プロバイダー、またはプロトコル実装の選択が未解決の場合に限り、evaluate-existing-solutions を使用してください。API のセマンティクスと互換性は、このスキルで扱います。
TypeScript の型パターン(ブランド型、判別可能なユニオン、スキーマファースト)については、typescript-strict スキルを参照してください。不変性のパターンについては、functional スキルを参照してください。API の動作のテストについては、testing スキルを参照してください。OAuth 2.0 または OpenID Connect については、認証を通常の API キーの選択として扱わず、secure-oauth-oidc スキルを読み込んでください。ブラウザー向け BFF のエントリーポイント(公開/保護アクセスの分類、セッション Cookie、CSRF/Origin/Fetch Metadata ポリシー、保護された SSE/WebSocket の登録、エンドポイント保護の適用)については、bff-entry-points スキルを読み込んでください。エラー形式と REST のセマンティクスは、このスキルで扱います。BFF パターン自体(採用、粒度、集約、上流の ID)については、bff-design を読み込んでください。フロントエンドと一体でデプロイされる単一コンシューマー向け BFF では、このスキルが外部コンシューマー向け契約に求めるバージョニングの規律を、正当に緩和できます。
補足資料は resources/ ディレクトリにあります。必要に応じて読み込んでください。
| リソース | 読み込むタイミング |
|---|---|
problem-details.md | RFC 9457 のエラーレスポンスを実装する場合 — メンバーのセマンティクス、単一エラーと検証エラーの JSON 例、拡張メンバー、§5 のセキュリティガイダンス |
api-evolution.md | バージョニング戦略と非推奨化のパターン |
api-security.md | API 境界のセキュリティ保護 |
auth-security.md | JWT BCP のセキュリティと、専用の OAuth/OIDC スキルへの振り分け |
http-fundamentals.md | HTTP プロトコルの基礎 — キャッシュディレクティブ、コンテンツネゴシエーション、ブラウザーセキュリティ、ステータスコード、ヘッダー設計 |
source-notes.md | 出典、変更不可の監査ベースライン、ライセンスの適用範囲、ローカルでの逸脱の確認 |
使用する場面
- 新しい API エンドポイントを設計する
- チーム間、または独立してリリースされるコンシューマー間の契約を定義する
- 既存の公開インターフェースを変更する
- API の形状に影響するデータベーススキーマを確立する
基本原則
ハイラムの法則
ある API のユーザーが十分な数に達すると、契約で何を約束しているかにかかわらず、システムの観測可能な挙動はすべて、誰かが依存するようになります。
文書化されていない癖、エラーメッセージの文面、タイミング、順序など、公開される挙動はすべて、ユーザーが依存した時点で事実上の契約になります。
- 公開する内容を意図的に決めてください。 観測可能な挙動はすべて、将来の約束になり得ます。
- 実装の詳細を漏らさないでください。 ユーザーが観測できるものには、必ず依存するようになります。
- 設計時に非推奨化を計画し、発生した時点で記録してください。 ユーザーが依存するようになったものを削除するには、常に想定以上のコストがかかります。公開済みの名前が別の名前に置き換わる場合は、コンシューマーの目に触れる場所に記録してください。フィールドまたはその型への非推奨コメント、引き続きその名前を含むレスポンスの
Deprecation/Sunsetヘッダー、置き換え先を示す README または API ドキュメントの記載が必要です。置き換えを記録しなければ、それは文書化されていない変更です。ヘッダーの構文と非推奨化チェックリストについては、resources/api-evolution.mdを参照してください。 - テストだけでは不十分です。 契約テストが完璧でも、ハイラムの法則により、「安全な」変更が、文書化されていない挙動に依存する実際のユーザーを破損させることがあります。
1 バージョンのルール
同じ依存関係や API の複数のバージョンからコンシューマーに選択させることは避けてください。異なるコンシューマーが同じものの異なるバージョンを必要とすると、ダイヤモンド依存関係の問題が発生します。常に 1 つのバージョンだけが存在する世界を想定して設計し、フォークするのではなく拡張してください。
契約を先に定義する
実装する前にインターフェースを定義します。契約が仕様であり、実装はそれに従います。
type TaskAPI = {
readonly createTask: (input: CreateTaskInput) => Promise<Task>;
readonly listTasks: (params: ListTasksParams) => Promise<PaginatedResult<Task>>;
readonly getTask: (id: TaskId) => Promise<Task>;
readonly updateTask: (id: TaskId, input: UpdateTaskInput) => Promise<Task>;
readonly deleteTask: (id: TaskId) => Promise<void>;
};
これはTDDと同じ考え方です。契約(実現したいこと)を定義し、それに対するテストを書いてから実装します。
変更より追加を優先する
既存の利用者を壊さずにインターフェースを拡張します。
このルールは、依頼の文言より優先されます。 すでに公開済みの契約に含まれるもの(レスポンスフィールド、ヘッダー、ステータス、ルートなど)の名前変更、型変更、選択肢の制限、削除を求める依頼は、新しい形を求めるものであり、障害を求めるものではありません。追加する形で対応します。新しい名前を古い名前と並べて追加し、古い名前も引き続き同じ値を保持して動作するようにし、古い名前が置き換えられることを記録します。また、置き換えではなく追加したことと、古い名前を廃止するには何が必要かを返信で伝えます。破壊的な変更を実施してから、その旨を注記してはいけません。また、どちらの意味なのかを尋ねるために作業を止めてはいけません。追加による変更なら、依頼内容を実現しながら互換性を保ち、依頼者が破壊的変更を望んでいた場合にも元に戻せます。
次の方法でインターフェースを拡張します。
type CreateTaskInput = {
readonly title: string;
readonly description?: string;
readonly priority?: 'low' | 'medium' | 'high'; // Added later, optional
readonly labels?: ReadonlyArray<string>; // Added later, optional
};
後方互換性を損なう変更:
- フィールドの削除
- フィールドの型変更
- 任意フィールドを必須にする
- 列挙値の変更
後方互換性を保つ変更:
- 新しい任意フィールドの追加
- 新しい列挙値の追加(利用者が未知の値を処理できる場合)
- 新しいエンドポイントの追加
一貫したエラーの意味づけ
エラー処理の方針を1つ選び、すべての箇所で使用します。エンドポイントによって例外をスローしたり、null を返したり、{ error } を返したりするなど、パターンを混在させないでください。
エラー形式の選択
同一のバージョン付きAPI契約では、プロトコル固有のエンドポイントで別の形式が必要な場合を除き、文書化されたエラー形式を1つ使用します。どの互換性のある形式を選ぶかより、一貫性が重要です。
外部の利用者がいる公開APIでは、RFC 9457(Problem Details)を使用します。これは業界標準であり、機械可読で、サードパーティの開発者が期待する形式です。Content-Typeには application/problem+json を使用します。
単一のフロントエンドを持つ内部APIでは、よりシンプルで一貫した形式も有効です。最低限必要なエラーレスポンスには、機械可読なエラーコード、任意の人間が読めるメッセージ、正しいHTTPステータスコードを含めます。これはRFC 9457より簡潔でありながら、一貫性と対応可能性を保てます。
// Simpler shape — sufficient for internal APIs
type ApiError = {
readonly error: string; // Machine-readable code (UPPER_SNAKE_CASE)
readonly message?: string; // Human-readable description
readonly fieldErrors?: Record<string, string>; // For validation errors
};
シンプルな形式から始める場合は、後からRFC 9457へ発展できるように設計します(例:error を title に、message を detail に対応させる)。後戻りできない設計にしないでください。
RFC 9457(HTTP API向けProblem Details)
公開APIで機械可読なAPIエラーを表す標準形式です。Content-Typeには application/problem+json を使用します。標準メンバーは、type(エラーの種類を識別するURI)、title(安定した人間が読める要約)、status(実際のHTTPステータスと一致する必要があります)、detail(発生ごとの説明)、instance(任意の発生URI)です。拡張メンバーも使用できます。クライアントは、認識できない拡張メンバーを無視する必要があります。
エラーは対応可能であるべきです。利用者が、何が起きたのか、なぜ起きたのか、どう対処すればよいのかを把握できるようにします。エラーレスポンスはデバッグ用のツールではありません。スタックトレース、内部パス、実装の詳細を決して公開しないでください。
相関ID(トレースID、またはその不透明な参照)を拡張メンバーまたは instance に含めることで、ユーザーから報告されたエラーをトレースおよび正規のログイベントと関連付けられるようにします。observability スキルを参照してください。トレースIDから内部情報が漏れることはありません。これは詳細情報の漏洩ではなく、検索キーです。
メンバーの完全な意味、単一エラーと検証エラーのJSON例、拡張メンバーのルール、Problem Detailsを使用しない場合、RFC 9457 §5のセキュリティガイダンスについては、resources/problem-details.md を参照してください。
HTTP ステータスコードの対応表
| ステータス | 意味 | 使用する場面 |
|---|---|---|
| 400 | 不正なリクエスト | クライアントが不正な形式のデータを送信した場合 |
| 401 | 認証されていない | 認証されていない場合 |
| 403 | アクセス禁止 | 認証済みだが、認可されていない場合 |
| 404 | 見つからない | リソースが存在しない場合 |
| 409 | 競合 | 重複、バージョンの不一致 |
| 422 | 処理できないコンテンツ | バリデーションに失敗した場合(意味的に無効) |
| 429 | リクエストが多すぎる | レート制限を超過した場合。妥当な再試行時刻がわかっている場合は Retry-After を含めます |
| 500 | 内部サーバーエラー | サーバーエラー(内部の詳細は決して公開しません) |
API 境界でのバリデーション
信頼できない表現とエンドポイントスキーマの入力は、システムに入る時点で
検証し、導出した型を内部コードに渡します。これはドメインのスマート
コンストラクターや遷移チェックに代わるものではありません。不変条件の
強制は、その値を所有するモデルが引き続き担います。typescript-strict と
domain-driven-design を参照してください。
表現自体を解析できない場合(たとえば、不正な JSON)は 400 を返します。 解析に成功した後、値がエンドポイントスキーマまたはビジネスバリデーションに 失敗した場合は 422 を返します。異なる独自のマッピングを採用するのは、 すべての例、エラー変換器、コンシューマーで一貫して使用する場合に限り、 そのマッピングを選定して文書化してください。
app.post('/api/tasks', async (req, res) => {
const result = CreateTaskSchema.safeParse(req.body);
if (!result.success) {
return res.status(422).json({
type: 'https://api.example.com/problems/validation-error',
title: 'Validation Error',
status: 422,
detail: 'Invalid task data',
errors: result.error.flatten(),
});
}
const task = await taskService.create(result.data);
return res.status(201).json(task);
});
サードパーティ API のレスポンスは信頼できないデータです。使用前に必ず その構造と内容を検証してください。
冪等性
ネットワーク障害は発生します。クライアントは再試行します。冪等性がないと、 再試行によって請求、注文、レコードが重複して作成されます。
HTTP メソッドの冪等性
| メソッド | 安全性 | 冪等性 | 備考 |
|---|---|---|---|
| GET | はい | はい | 要求されたセマンティクスは読み取り専用です。付随するログ記録やメトリクスの記録は許可されます |
| PUT | いいえ | はい | 同じリクエストは同じ意図された効果をもたらします。レスポンスのステータスや本文は異なる場合があります |
| DELETE | いいえ | はい | 2 回削除しても結果は同じです |
| POST | いいえ | いいえ | 明示的な冪等性処理が必要です |
| PATCH | いいえ | 保証されない | 実装によります |
POST の冪等性キー
冪等でない操作(特に金銭、注文、状態変更を伴う操作)には、クライアントが 指定する冪等性キーを使用します。
app.post('/api/payments', async (req, res) => {
const rawIdempotencyKey = req.headers['idempotency-key'];
const idempotencyKey =
typeof rawIdempotencyKey === 'string' &&
/^[\x21-\x7e]{1,128}$/.test(rawIdempotencyKey)
? rawIdempotencyKey
: undefined;
if (!idempotencyKey) {
return res.status(400).json({
type: 'https://api.example.com/problems/invalid-idempotency-key',
title: 'Invalid Idempotency Key',
status: 400,
detail: 'POST /api/payments requires one visible-ASCII Idempotency-Key of at most 128 characters',
});
}
const result = CreatePaymentSchema.safeParse(req.body);
if (!result.success) {
return res.status(422).json(toValidationProblem(result.error));
}
const scope = req.user.id;
const fingerprint = stableHash(result.data);
const claim = await idempotencyStore.claim({
scope,
key: idempotencyKey,
fingerprint,
});
if (claim.status === 'parameters-mismatch') {
return res.status(409).json(toProblem('IDEMPOTENCY_PARAMETERS_MISMATCH'));
}
if (claim.status === 'completed') {
return res.status(claim.response.status).json(claim.response.body);
}
if (claim.status === 'in-progress') {
res.setHeader('Retry-After', '1');
return res.status(409).json(toProblem('IDEMPOTENCY_REQUEST_IN_PROGRESS'));
}
// createOnce durably owns operationId + fingerprint beyond response-cache
// expiry. For an external provider it also persists/reuses the provider's
// payment/intent ID, so recovery never depends on a time-limited request key.
const payment = await paymentService.createOnce({
operationId: claim.operationId,
input: result.data,
});
await idempotencyStore.complete(claim.id, { status: 201, body: payment });
return res.status(201).json(payment);
});
設計原則:
- キーは API キーまたは認証済みユーザーのスコープに限定します
- レスポンスキャッシュとキーの保持期間は、文書化されたクライアントの 最大再試行・照合期間と影響のリスクに基づいて選定します。24 時間が 万能なルールではありません
- キャッシュされたレスポンスの削除後も、永続的な操作 ID(および フィンガープリントまたはトゥームストーン)を保持するか、重複しないことを 確認できるまで、有効期限切れ/不明という明示的な結果を返します。 リスクのある再利用キーを、新しい操作として黙って扱ってはいけません
- 同じキーでの再試行時にパラメーターが異なる場合は、エラーを返します
- キーはアトミックに取得します。読み取りと作成を分けた手順は競合状態を 起こしやすくなります
- 1 つのトランザクションまたは永続的な下流操作 ID を使って、ビジネス上の 効果とキーの取得を連携させます
- 外部決済では、プロバイダーの決済 ID/インテント ID を永続化して照合し、 リクエスト冪等性キーの再試行期間をプロバイダーが文書化した保持期間内に 制限します
- 処理中のキー取得を明示的に表現し、取得後にワーカーが失敗した場合の 再試行/復旧方法を定義します
- 「少なくとも 1 回」の配信を前提に設計します。すべてのリクエストが 複数回送信される可能性があると想定してください
DELETE を冪等にする
DELETE は冪等な効果を持つ必要があります。繰り返しても、ほかのものを削除したり、
2 回目の副作用を発生させたりしてはいけません。繰り返し時のレスポンスは 204
のままでも、クライアントにとって不在が意味を持つ場合は 404 でもかまいません。
契約を 1 つ選び、文書化してください。
app.delete('/api/tasks/:id', async (req, res) => {
const deleted = await taskService.delete(req.params.id);
// This API deliberately treats already-absent as success.
return res.status(204).send();
});
レート制限
API がクォータポリシーを公開する場合、クライアントにとって有用なレスポンスでは、 適用されるポリシーと残りの状態を一貫して伝えてください。そのようなポリシーが ないエンドポイントに、クォータヘッダーを独自に設けてはいけません。
標準ヘッダー(IETF ドラフト — RFC には未到達)
IETF の標準化作業は draft-ietf-httpapi-ratelimit-headers(バージョン 11、2026 年 5 月時点 — 現在もワーキンググループのドラフトです)です。現行のドラフトでは、次の 2 つの構造化フィールドが定義されています。
RateLimit-Policy: "hour";q=1000;w=3600 # Policy: quota of 1000 per 3600s window
RateLimit: "hour";r=742;t=60 # Current state: 742 remaining, resets in 60s
以前のドラフトでは、RateLimit-Limit、RateLimit-Remaining、RateLimit-Reset の 3 つのヘッダーが使われていました。また、現在も多くの公開済み API で、この 3 つのヘッダーか、標準化されていない X-RateLimit-* 系のヘッダーが使われています。API を利用する際は、これらのいずれかが使われていると想定してください。新しい API では、現行のドラフトで定められたフィールドを優先してください。どの名前を選ぶ場合でも、必ずドキュメントに記載してください。ヘッダー名は契約の一部であり、ドラフトが RFC になる前に変更される可能性もあります。
サーバーが適切な再試行時刻を提示できる場合は、429 レスポンスに Retry-After を含めてください。これは安定した標準 HTTP ヘッダーですが、HTTP では、429 レスポンスにこれを含めないことも認められています。
HTTP/1.1 429 Too Many Requests
Retry-After: 30
RateLimit-Policy: "hour";q=1000;w=3600
RateLimit: "hour";r=0;t=30
Content-Type: application/problem+json
{
"type": "https://api.example.com/problems/rate-limit-exceeded",
"title": "Rate Limit Exceeded",
"status": 429,
"detail": "You have exceeded 1000 requests per hour. Retry after 30 seconds."
}
設計上の考慮事項
- 操作ごとに異なる制限を設ける — 読み取りは書き込みよりもコストが低くなります。
- 制限をドキュメントで伝える — 429 を受けるまで利用者に制限を探らせないでください。
- ジッターを伴う指数バックオフを推奨する — 単純な再試行ループは、アクセスの集中を引き起こします。
HTTP キャッシュ
レスポンスには有効期間を明示的に設定してください。ヒューリスティックな鮮度判定に頼らないでください。
実用的なルール:
ExpiresよりもCache-Control: max-age=Nを優先します — 短い有効期間(例:max-age=5)でも、複数のクライアント間で再利用できます。- 本文を再転送せずに効率的に再検証できるよう、ETag を設定します。
- リクエストヘッダーによってレスポンスが変わる場合は、そのリソースのすべてのレスポンス(デフォルトのレスポンスも含む)に
Varyを設定します。 - 機密データを含むレスポンスには
no-storeを使用します —no-cacheは「キャッシュしない」という意味ではありません(「保存するが、使用前に再検証する」という意味です)。
Cache-Control ディレクティブの一覧に加え、コンテンツネゴシエーション、ヘッダーの設計、プロトコルバージョンの独立性については、resources/http-fundamentals.md を参照してください。
REST の規約
リソースの命名
| パターン | 規約 | 例 |
|---|---|---|
| エンドポイント | 複数形の名詞を使い、動詞は使わない | GET /api/tasks, POST /api/tasks |
| クエリパラメーター | camelCase | ?sortBy=createdAt&pageSize=20 |
| レスポンスフィールド | camelCase | { createdAt, updatedAt, taskId } |
| 真偽値フィールド | is/has/can の接頭辞を付ける | isComplete, hasAttachments |
| 列挙値 | UPPER_SNAKE | "IN_PROGRESS", "COMPLETED" |
| ヘッダー | X- 接頭辞を付けない(RFC 6648/BCP 178) | Example-Request-Id |
リソースの設計
GET /api/tasks → List tasks (with query params for filtering)
POST /api/tasks → Create a task
GET /api/tasks/:id → Get a single task
PATCH /api/tasks/:id → Update a task (partial)
DELETE /api/tasks/:id → Delete a task
GET /api/tasks/:id/comments → List comments for a task (sub-resource)
POST /api/tasks/:id/comments → Add a comment to a task
部分更新には PATCH を使用します(指定されたフィールドのみが変更されます)。PUT は、クライアントがオブジェクト全体を送信する場合にのみ使用してください。
ページネーション
結果が上限のある安全なレスポンスサイズを超える可能性があるリストエンドポイントには、ページネーションを適用してください。その上限が契約の一部であれば、少数の固定された列挙値は完全な一覧を返してもかまいません。
// Request
// GET /api/tasks?page=1&pageSize=20&sortBy=createdAt&sortOrder=desc
// Response shape
type PaginatedResult<T> = {
readonly data: ReadonlyArray<T>;
readonly pagination: {
readonly page: number;
readonly pageSize: number;
readonly totalItems: number;
readonly totalPages: number;
};
};
フィルタリング
フィルターにはクエリパラメーターを使用します。
GET /api/tasks?status=in_progress&assignee=user123&createdAfter=2025-01-01
入出力の分離
呼び出し元が指定する内容と、システムが返す内容を分けます。
// Input: what the caller provides
type CreateTaskInput = {
readonly title: string;
readonly description?: string;
};
// Output: includes server-generated fields
type Task = {
readonly id: TaskId;
readonly title: string;
readonly description: string | null;
readonly createdAt: Date;
readonly updatedAt: Date;
readonly createdBy: UserId;
};
よくあるもっともらしい理由
| もっともらしい理由 | 実際のところ |
|---|---|
| 「API は後でドキュメント化します」 | 型がそのままドキュメントになります。まず型を定義してください。 |
| 「増え続けるこのリストに上限は必要ありません」 | 結果が 1 回の応答に収まらなくなる前に、ページネーションまたは文書化された別の安全な上限を定義してください。契約上の上限が明確なリストに、形だけのページネーションは必要ありません。 |
| 「PATCH は複雑なので、PUT を使いましょう」 | PUT では毎回オブジェクト全体を渡す必要があります。クライアントが実際に求めているのは PATCH です。 |
| 「必要になったら API のバージョンを付けます」 | バージョン管理のない破壊的変更は、利用側に影響します。最初から拡張を前提に設計してください。 |
| 「名前の変更を依頼されたので、破壊的変更も承認されています」 | よりわかりやすい名前を求める依頼は、名前についての依頼であり、互換性を壊す依頼ではありません。新しい名前を追加し、古い名前も引き続き使えるようにし、旧名として記録し、その判断を返信で説明してください。破壊的変更をリリースしてはいけません。また、どちらの意味だったのかを尋ねて作業を止めてはいけません。 |
| 「ドキュメント化されていない動作を使う人はいません」 | ハイラムの法則:観測可能なものには、必ず依存する人がいます。 |
| 「内部 API に契約は必要ありません」 | 内部の利用者も利用者です。契約は密結合を防ぎ、並行作業を可能にします。 |
| 「再試行はクライアント側の問題です」 | 冪等性がなければ、再試行によって重複が発生します。少なくとも 1 回の配信を前提に設計してください。 |
| 「この高コストの公開操作が攻撃されてから、悪用対策を追加します」 | 認証なしで利用できる操作、高コストの操作、または悪用されやすい操作は、最初から保護してください。クライアントに見えるクォータが実際に契約の一部である場合に限り、クォータヘッダーを公開してください。 |
| 「エラーメッセージはデバッグ用にすぎません」 | エラーは API の開発者体験の一部です。診断的なものではなく、次の行動につながるものにしてください。 |
危険信号
- 条件によって異なる形式を返すエンドポイント
- エンドポイント間で一貫性のないエラー形式
- スタックトレースや内部パスを公開するエラーレスポンス
- 既存フィールドに対する破壊的変更(型の変更、削除)
- ページネーションやその他の明示的な上限がない、上限のないリストエンドポイント
- REST URL に含まれる動詞(
/api/createTask、/api/getUsers) - 検証せずに使用されるサードパーティ API のレスポンス
- エンドポイントに型付きの入出力スキーマがない
- 重複した試行で状態変更の効果が繰り返される可能性があり、冪等性の戦略が定義されていない、再試行可能な POST 操作
- 文書化されたクォータポリシーに対し、クライアントが正しく動作するために十分な状態をレスポンスで公開していない
- 指数バックオフを使わない再試行ロジック
- ブラウザーに配信されるレスポンスに、エントリーポイントに適用されるセキュリティヘッダーがない
X-接頭辞付きのカスタムヘッダー(RFC 6648 で非推奨)
検証
API の設計後に、次の項目を確認します。
- すべてのエンドポイントに、型付きの出力契約と、入力がある場合は型付きの入力契約があります
- エラーレスポンスは、一貫した単一の形式に従っています(公開 API では RFC 9457、内部 API ではより簡潔で一貫した形式)
- エラーレスポンスから実装の詳細(スタックトレース、内部パスなど)が漏れることはありません
- 信頼境界では信頼できないデータの表現やスキーマを検証し、ドメイン不変条件はその所有者が引き続き適用します
- 増加し続けるリストを返すエンドポイントには、ページネーションまたは文書化された別の安全な上限があります
- 新しいフィールドは追加のみで任意指定(後方互換)であり、この変更によって公開済みの名前、型、ステータス、ルートが削除または別の用途に転用されていません
- 置き換えられた名前はすべて、引き続き以前の値で提供され、コンシューマーが確認できる場所に置き換え済みとして記録されています
- 命名規則はすべてのエンドポイントで一貫しています
- 実装前に契約を定義しています(コントラクトファースト)
- 再試行可能な POST 操作では、重複した試行による処理の重複をどう防ぐかを定義しています
- クォータポリシーを文書化した API では、クライアントが確認できる上限の状態を一貫して公開しています
- Content-Type は、RFC 9457 のレスポンスでは
application/problem+json、簡潔な形式ではapplication/jsonです - ブラウザー向けのエントリーポイントでは、提供するコンテンツとデプロイポリシーに適したセキュリティヘッダーを適用しています
- キャッシュ戦略を定義しています(明示的な
Cache-Control、再検証用の ETag、必要に応じたVary)
帰属表示
このスキルは、Addy Osmani の MIT ライセンス対象である api-and-interface-design スキルを応用したものです。ローカルの履歴にはこの応用が記録されていますが、元の上流インポートのリビジョンは記録されていません。変更されない監査用ベースライン(インポートリビジョンの主張ではありません)、引き継いだアイデア、ローカルでの相違点については resources/source-notes.md を、適用される上流の告知については LICENSE を参照してください。