2年以上前のコードベースを開けば、そこに傷跡を見つけるでしょう。/getUser、/user_list、/Users/fetchAll、3つの異なるページネーションスキーム、そして同じレスポンス内にcustomerIDフィールドがorder_idの隣に存在しているといった具合です。どれも何かを壊すわけではありません。しかし、それらすべてが全員の作業を遅らせます。
命名は、あなたがこれまで行うAPI設計の決定の中で最も安価であり、また、変更する際には最も高価なものです。一度クライアントが/getOrdersに依存してしまえば、あなたはそれを何年もサポートし続けなければなりません。このガイドでは、REST APIがあなたに課すあらゆる命名の決定に対して具体的なルールを提示し、それぞれに推奨例と非推奨例を挙げます。これは、より広範な開発者向けREST APIガイドラインと同じ考え方に従いますが、チームが最も議論する部分、つまり「物事をどう呼ぶか」に焦点を当てています。
これらのルールをコードレビューのコメントではなくツールで強制したい場合は、Apidogを使用すると、誰もコードを書く前に共有スキーマに対してすべてのエンドポイントを視覚的に定義できます。詳細については、最後で説明します。
コレクションには複数形の名詞を使用する
URLは操作ではなくリソースを名付けます。コレクションは物の集合体であるため、複数形の名詞として名前を付けます。
推奨:
GET /v1/products
GET /v1/products/89
GET /v1/orders
非推奨:
GET /v1/getProducts
GET /v1/product
GET /v1/productList
複数形は両方のレベルで機能します。/productsは「製品のコレクション」と読め、/products/89は「コレクション内の製品89」と読めます。単数形での命名は、1つのアイテムに対して/product/89、多数のアイテムに対して/productといった不自然なURLを強制し、これは間違った読み方をさせます。Microsoft REST APIガイドラインがまさにこの理由で複数形の名詞を採用し、ほとんどの公開API(Stripe、GitHub、Shopify)も同様の道をたどっています。
例外として、シングルトンリソースがあります。ユーザーがカートを1つだけ持っている場合、/users/42/cartは問題ありません。基数が1のものに複数形を使用しないでください。
パスから動詞を除外する
HTTPメソッドが動詞です。パスに別の動詞を入れると、情報が重複し、リソースモデルが壊れます。
推奨:
GET /v1/orders/42 (読み取り)
DELETE /v1/orders/42 (削除)
PATCH /v1/orders/42 (更新)
非推奨:
GET /v1/fetchOrder/42
POST /v1/deleteOrder/42
POST /v1/updateOrderStatus
動詞ベースのパスは、表面積も増加させます。4つのメソッドを持つ1つのリソースが、個別にドキュメント化、テスト、キャッシュする必要のある4つのエンドポイントになってしまいます。キャッシュの無効化も悪化します。CDNはGET /v1/orders/42をキャッシュし、DELETE /v1/orders/42で無効化できます。なぜなら、どちらも同じURLを指しているからです。/fetchOrder/42を/deleteOrder/42に関連付けることはできません。
URLパスにはケバブケースを使用する
複数の単語で構成されるパスセグメントには区切り文字が必要であり、ハイフンが適切なものです。
推奨:
/v1/gift-cards
/v1/shipping-addresses
非推奨:
/v1/giftCards
/v1/gift_cards
/v1/GiftCards
理由は3つあります。Googleはインデックス作成のためにハイフンを単語区切り文字として扱うため、ケバブケースを使用すると公開APIドキュメントのランクが向上します。メールやドキュメントでURLに下線が引かれると、アンダースコアは消えてしまいます。そして、URLでのキャメルケースは、大文字小文字の区別に関するバグを引き起こします。ほとんどのサーバーでは/giftCardsと/giftcardsは異なるURLであり、誰かが間違った方を入力してしまうでしょう。Zalando RESTful APIガイドラインでは、ケバブケースをMUSTルールとしており、彼らはこのプレイブックを何百もの内部サービスに適用しています。
JSONの命名規則を一つ選び、文書化する
リクエストとレスポンスボディ内のフィールド名については、正直なところ、キャメルケースとスネークケースのどちらも機能します。機能しないのは、それらを混在させることです。
推奨(どちらか一方を常に):
{ "orderId": 42, "createdAt": "2026-08-30T09:15:00Z", "totalAmount": 4999 }
{ "order_id": 42, "created_at": "2026-08-30T09:15:00Z", "total_amount": 4999 }
非推奨:
{ "orderId": 42, "created_at": "2026-08-30T09:15:00Z", "TotalAmount": 4999 }
キャメルケースはJavaScriptおよびJavaクライアントにきれいにマッピングされます。スネークケースは読みやすく、Ruby、Python、およびほとんどのSQLカラム名と一致します。Stripeはそれを至る所で使用しています。APIを最も多く利用するユーザーに基づいて選択し、その選択をスタイルガイドに記載して、プルリクエストごとに議論するのではなく、一度だけ議論するようにします。異なるチームが異なるエンドポイントを出荷するため、実際のAPIでは混在した命名規則が最も一般的な不整合です。これはガバナンスの失敗であり、好みの問題ではありません。
ネストは2レベルまでにする
ネストは所有関係を表します。/users/42/ordersは「ユーザー42に属する注文」を意味します。これは便利です。しかし、2レベルを超えると有用ではなくなります。
推奨:
GET /v1/users/42/orders
GET /v1/orders/1337/refunds
非推奨:
GET /v1/users/42/orders/1337/refunds/7/status
深いネストは、リーフリソースがそれ自体でグローバルに一意なIDを持っている場合でも、クライアントがすべての祖先IDを運んでリーフリソースに到達することを強制します。もし払い戻しがID 7を持っているなら、/refunds/7または/orders/1337/refunds/7として公開し、そこで止めましょう。良い臭いテストは、URLに3つ以上のIDが含まれている場合、それを平坦化することです。注文が存在すれば、そのパスにユーザーを含める必要はありません。/orders/1337だけで独立しています。
フィルタリング、ソート、ページネーションはクエリパラメータに入れる
パスはリソースを識別します。クエリパラメータは、それらをどのように表示するかを変更します。フィルターをパスにエンコードすることは決してしないでください。
推奨:
GET /v1/orders?status=active&sort=-created_at&limit=50&cursor=eyJpZCI6NDJ9
GET /v1/products?category=electronics&min_price=1000
非推奨:
GET /v1/orders/active
GET /v1/orders/sorted-by-date-desc
GET /v1/getOrdersByStatusAndDate
sort=-created_atパターン(降順を表すマイナス記号)はJSON:API仕様に由来し、2つ目のorder=descパラメータを不要にします。/orders/activeのようなフィルターパスは、フィルターを組み合わせる必要がない限り無害に見えますが、組み合わせごとに新しいエンドポイントを作成することになります。ページネーションパラメータ名も同じ規律が必要です。limit/cursorまたはpage/per_pageのいずれかを一度選択し、すべてのコレクションで再利用してください。APIページネーションガイドでは、カーソル対オフセットのトレードオフについて深く掘り下げていますが、ここでの命名規則は単に統一することです。
パスにバージョンを記述する
主流のオプションは2つあります。パスセグメント (/v1/products) またはヘッダー (Accept: application/vnd.myapi.v1+json) です。ヘッダーによるバージョン管理は、URLがバージョン間で同じリソースを指し続けるため、より「純粋な」RESTであり、Google API設計ガイドは両方のアプローチが広く使われていることを指摘しています。しかし、パスによるバージョン管理は運用面で優れています。すべてのログ行で可視化され、ブラウザからテスト可能で、Varyの複雑な処理なしにキャッシュ可能であり、クライアントが忘れることが不可能です。バージョンヘッダーの欠落によって引き起こされる「curlでは動作するが本番では失敗する」という問題をデバッグしたことのあるすべての開発者は、代替手段のコストを知っています。メジャーバージョンのみの/v1/を使用し、/v1.2/は使用しないでください。マイナーな変更は追加的で破壊的でないものであるべきです。コンテンツネゴシエーションを含む完全な意思決定ツリーについては、APIバージョン管理戦略の比較を参照してください。
リソースIDを不透明に扱い、連番の整数を不用意に公開しない
/orders/41、/orders/42、/orders/43:連番の整数IDは、誰が見ても処理している注文数を正確に伝えてしまい、攻撃者がID空間を探索して認証の隙間を探る列挙攻撃を招きます。この種のバグである、不適切なオブジェクトレベル認証は、OWASP API Security Top 10で第1位に位置しています。
推奨:
GET /v1/orders/ord_9f8e2a71b3
GET /v1/users/550e8400-e29b-41d4-a716-446655440000
非推奨(列挙が問題となる場合):
GET /v1/orders/42
GET /v1/invoices/10883
Stripeのord_9f8e2a71b3のような接頭辞付きのランダムIDは、最強のパターンです。推測不可能で、ログで自己記述的であり、安全に公開できます。どちらの場合でも認証チェックは必須です。不透明なIDは、チェックの欠落による影響範囲を軽減しますが、それ自体がチェックの代わりになるわけではありません。内部的には整数の主キーを保持できます。このルールは、URLで公開する内容に関するものです。
非CRUDアクションをコントローラーリソースとしてモデル化する
遅かれ早かれ、クリーンなCRUDマッピングを持たないアクションが必要になります。注文のキャンセル、支払いの再試行、メールの再送信などです。ステータスフィールドに対するPATCHを介してそれらをトンネルしたり、最上位レベルに動詞を置いたりしないでください。
推奨:
POST /v1/orders/42/cancel
POST /v1/payments/pay_88a1/retry
非推奨:
PATCH /v1/orders/42 { "status": "cancelled" }
POST /v1/cancelOrder { "orderId": 42 }
これはコントローラーパターンであり、動詞を使用しないというルールに対する唯一の例外として認められています。動詞はパスの最後に配置され、それが作用するリソースの下にスコープされます。PATCHアプローチはRESTfulに見えますが、フィールドの更新内に状態マシンを隠しています。注文をキャンセルすると、払い戻しがトリガーされ、在庫が解放され、通知が送信されます。それをフィールド書き込みであると見せかけると、サーバーは意図を検出するためにペイロードを比較することを強制されます。/cancelエンドポイントは意図を明示し、アクションに独自の権限と監査証跡を与え、キャンセル理由のようなアクション固有の入力のための余地を残します。
ヘッダーとクエリパラメータの命名規則を統一する
2つの小さな領域でも、同じ規律が求められます。カスタムヘッダーには、HTTPの慣習に合わせてハイフン区切りのパスカルケースを使用します。例:Idempotency-Key、Request-Id。古いX-プレフィックスは省略してください。RFC 6648によって2012年に非推奨になりました。ヘッダー名はワイヤー上では大文字小文字を区別しませんが、ドキュメントやSDKでは常に一貫した記述を保つべきです。
クエリパラメータはJSONボディの命名規則と一致させるべきです。ボディがスネークケースを使用している場合、?min_price=1000&created_after=2026-01-01と記述し、?minPrice=1000としないようにします。レスポンスでcreated_atと読み、クエリでcreatedAfterと入力しなければならない開発者は、最初の試行で間違え、その後のすべての人も同様に間違えるでしょう。
ルールセット全体を一目で確認
| # | ルール | 推奨 | 非推奨 |
|---|---|---|---|
| 1 | コレクションには複数形の名詞を使用する | /products, /products/89 |
/getProducts, /productList |
| 2 | パスに動詞を含めない | DELETE /orders/42 |
POST /deleteOrder/42 |
| 3 | パスセグメントにはケバブケースを使用する | /gift-cards |
/giftCards, /gift_cards |
| 4 | JSONの命名規則は一つに統一し、文書化する | order_id をどこでも使用 |
orderId と order_id を混在させる |
| 5 | ネストは最大2レベルまで | /orders/1337/refunds |
/users/42/orders/1337/refunds/7 |
| 6 | フィルターとページネーションはクエリパラメータに入れる | ?status=active&sort=-created_at |
/orders/active |
| 7 | パスにメジャーバージョンを含める | /v1/products |
/v1.2/products, バージョンヘッダー |
| 8 | リソースIDは不透明にする | /orders/ord_9f8e2a71b3 |
/orders/42 (公開、列挙可能) |
| 9 | アクションにはコントローラーパターンを使用する | POST /orders/42/cancel |
PATCHで {"status":"cancelled"} |
| 10 | ヘッダーとパラメータの命名規則を統一する | Idempotency-Key, ?min_price= |
X-IDEMPOTENCY_KEY, ?minPrice= を混在させる |
大規模なコンベンションの適用
Wikiのスタイルガイドは何も変えません。APIの一貫性を保つチームは、1つの習慣を共有しています。それは、コードが存在する前に設計を行い、コンベンションを適用することです。これこそが、実践におけるAPIガバナンスの核心です。
ここでApidogがワークフローにおいてその価値を発揮します。エンドポイントはスキーマファーストのビジュアルデザイナーで定義されるため、パス、キャメルケース、パラメータ名は、コントローラーコードに埋め込まれた文字列ではなく、明示的な設計成果物となります。共有コンポーネントとは、Pagination、Error、Moneyスキーマが一度定義され、すべてのエンドポイントで再利用されることを意味します。誰も新しいサービスでper_pageをpageSizeとして再発明することはありません。そして、設計はレビュー機能が組み込まれたチームワークスペースに存在するため、リーダーは、3つのクライアントが統合する後ではなく、名前変更にワンクリックしかかからない設計時に/getUserOrdersを発見できます。その仕様はドキュメント、モックサーバー、テストを駆動するため、承認された名前が全員が出荷する名前となります。Apidogをダウンロードして、次の新しいエンドポイントで無料で試してみてください。古いAPIを改造するのは困難ですが、新しいAPIで一貫性を保つのはそうではありません。
よくある質問
RESTのURLは複数形と単数形のどちらにすべきですか?
複数のインスタンスを持つリソース(/products、/orders、/users)には複数形を使用します。複数形は、コレクション(/orders)と個々のメンバー(/orders/42)の両方で自然に機能します。/users/42/cartのような真のシングルトンには単数形を予約してください。リソースモデリングのより深い理由を知りたい場合は、REST APIとは何かに関するガイドで、その基本原理から説明しています。
JSONフィールド名にはcamelCaseとsnake_caseのどちらが優れていますか?
どちらもメリットで勝るわけではありません。camelCaseはJavaScriptを多用するユーザーに適しており、snake_caseはより読みやすく、Python、Ruby、Stripeの公開APIと一致します。重要なルールは、どちらか一方を選択し、スタイルガイドに記述し、スキーマレビューで強制することです。エンドポイント間で命名規則が混在する方が、どちらか一方を選択するよりも問題が大きくなります。
APIバージョンはURLに入れるべきですか、それともヘッダーに入れるべきですか?
強力なハイパーメディア要件がない限り、パス(/v1/orders)を使用してください。パスバージョンは、クライアントが何も努力しなくても、ログ、キャッシュ、ブラウザテストに表示されます。ヘッダーによるバージョン管理はURLをバージョン間で安定させますが、クライアントがヘッダーを忘れるとサイレントに失敗します。メジャーバージョンのみを使用し、マイナーな変更は追加的で破壊的でない更新としてリリースしてください。
REST APIパスで動詞が許容されることはありますか?
はい、1箇所だけあります。非CRUDアクション用のコントローラーエンドポイントです。例えば、POST /orders/42/cancelやPOST /payments/pay_88a1/retryなどです。動詞はパスの最後に、そのリソースのスコープ内で配置され、メソッドは常にPOSTです。それ以外の場所では、HTTPメソッドが動詞を伝え、パスは名詞のみを保持します。
