「APIエンドポイント」と「URL」は同じものを指すようでいて、仕様書の読み方を誤るとURIの設計や呼び出し側の実装がずれる用語です。URLはリソースの場所を示す文字列、エンドポイントはベースURLとパスにHTTPメソッドを組み合わせた「1つの操作の入口」と捉えると、両者の違いが設計の判断に直結します。ここではその違いをコードで分解したうえで、URIをどう決めるかの設計指針であるリソース指向アーキテクチャ(ROA)の定義と4つの特性、リソースの切り出し方、URI命名、HTTPメソッドと冪等性、標準メソッドで表せない操作の扱い、エラー応答の標準まで、設計判断として使える形で整理します。
まとめ:エンドポイントとURLの違いとリソース指向で設計する要点
- URLはURIの部分集合で、エンドポイントは「ベースURL+パス+HTTPメソッド」で決まる操作の入口。同じURLでもGETとDELETEは別のエンドポイントとして扱います。
- ROAはRESTの実装ガイドライン。RESTはFieldingの2000年の博士論文が定義したアーキテクチャスタイル、ROAはRichardson と Ruby の『RESTful Web Services』(2007)第4章が示した設計指針で、RFCのような標準仕様ではありません。
- ROAの4つの概念はリソース/URI(名前)/表現/リンク、4つの特性はアドレス可能性・ステートレス性・接続性・統一インターフェース。
- 設計は名詞(リソース)を先に決め、動詞はHTTPメソッドに任せる。URIに動詞を入れた時点でROAから外れます。
- GETとHEADは安全かつ冪等、PUTとDELETEは冪等、POSTもPATCHも冪等ではありません(RFC 9110 / RFC 5789)。POSTの再送対策は
Idempotency-Keyが事実上の標準です。 - 標準メソッドで表せない操作はAIP-136のカスタムメソッド(コロン構文)に隔離し、エラー応答はRFC 9457のProblem Detailsに寄せます。
- すべてのAPIをROAにする必要はありません。手続き的な操作が主のAPIは、無理にリソース化するより別方式が適します。
APIエンドポイントとURL・URIの違い:ベースURLとパスの分解と用語の整理
リソース指向の設計に入る前に、用語の重なりをほどいておきます。API仕様書やSDKのドキュメントでは「URL」「URI」「エンドポイント」「ベースURL」が混在して使われますが、それぞれが指す範囲は異なります。
URIとURLの関係:RFC 3986が定めるURLはURIの部分集合という定義
URIの汎用構文を定めるRFC 3986は、URL(Uniform Resource Locator)を「リソースを識別するのに加え、ネットワーク上の場所などの主なアクセス手段を記述してリソースの所在を示すURIの部分集合」と定義しています。つまりURIは識別子の総称、URLはその中で「どこにあるか」まで示すものという包含関係です。
Web APIの文脈では、https://api.example.com/v1/orders/1001は識別子(URI)であると同時に所在(URL)でもあるため、両者を厳密に使い分ける場面は多くありません。ROAで「URI」と呼ぶのは、リソースに名前を付けるという識別の側面を強調するためです。
エンドポイントの正体:ベースURL+パス+HTTPメソッドで1つの操作を指す
エンドポイントはURLと違い、RFCで定義された用語ではありません。実務での意味は、OpenAPIの仕様が最もよく表しています。OpenAPI Specification 3.2.1のPaths Objectは「個々のエンドポイントとその操作への相対パス」を保持し、そのパスはServer ObjectのURLに連結されて完全なURLになる、と定めています。
| 用語 | 指す範囲 |
|---|---|
| ベースURL | スキーム+ホスト(+共通パス) |
| パス | リソースを指す相対部分 |
| URL | ベースURL+パス+クエリ |
| エンドポイント | HTTPメソッド+パス |
注文APIに当てはめると、それぞれ次の部分に当たります。
ベースURL https://api.example.com/v1
パス /orders/{order_id}
URL https://api.example.com/v1/orders/1001?fields=status
エンドポイント GET /orders/{order_id}
ここで押さえるべきは、1つのURLに対してメソッドの数だけエンドポイントがある点です。/v1/orders/1001というURLは1つでも、GET(取得)・PATCH(更新)・DELETE(削除)は別々のエンドポイントとして仕様書に並びます。リソース指向の設計では、URLを名詞(リソース)の数だけ用意し、操作の違いはメソッドに任せるため、「URLは少なく、エンドポイントはその数倍」という形になります。
Pythonで確かめるエンドポイントの分解:同じURLでもメソッドが違えば別操作
次のコードは、実際のURLをベースURL・パス・クエリに分け、仕様書のパステンプレートとメソッドの組からエンドポイントを引きます。Python 3の標準ライブラリだけで動きます。
from urllib.parse import urlsplit
# 1つのURLに対して、HTTPメソッドの数だけエンドポイント(操作)がある
ENDPOINTS = {
("GET", "/v1/orders/{order_id}"): "注文の取得",
("PATCH", "/v1/orders/{order_id}"): "注文の部分更新",
("DELETE", "/v1/orders/{order_id}"): "注文の削除",
}
def to_template(path):
# 実URLのパスを、仕様書に書くパステンプレートへ戻す
parts = path.strip("/").split("/")
if len(parts) == 3 and parts[:2] == ["v1", "orders"]:
return "/v1/orders/{order_id}"
return path
url = "https://api.example.com/v1/orders/1001?fields=status"
u = urlsplit(url)
base_url = f"{u.scheme}://{u.netloc}"
print("ベースURL:", base_url)
print("パス :", u.path)
print("クエリ :", u.query)
for method in ("GET", "PATCH", "DELETE"):
key = (method, to_template(u.path))
print(f"{method:6} {key[1]} -> {ENDPOINTS[key]}")
実行結果は次のとおりです。URLは1本のまま、3つのエンドポイントに振り分けられていることが分かります。
ベースURL: https://api.example.com
パス : /v1/orders/1001
クエリ : fields=status
GET /v1/orders/{order_id} -> 注文の取得
PATCH /v1/orders/{order_id} -> 注文の部分更新
DELETE /v1/orders/{order_id} -> 注文の削除
クエリ文字列(fields=status)はエンドポイントを変えません。同じエンドポイントに対する入力の違いとして扱います。
ドキュメントで「エンドポイント」が指す範囲が揺れる3つのパターン
エンドポイントは標準化された用語ではないため、ドキュメントによって指す範囲が揺れます。読み違えやすいのは次の3パターンです。
1つ目はホスト(サービス)単位。GoogleのAPI設計指針AIP-122は、1つのサービスがリージョン別・mTLS用・プライベートアクセス用など複数のエンドポイントを持てるとし、ここでのエンドポイントはlibrary.googleapis.comのような接続先ホストを指します。同じAIP-122は、リソースにアクセスする完全なURIにはプロトコル・APIバージョン・対象のサービスエンドポイントが含まれると説明しています。2つ目はパス単位で、OpenAPIのPaths Objectのように/orders/{order_id}をエンドポイントと呼ぶ使い方です。3つ目はメソッド+パス単位で、API一覧表でGET /ordersとPOST /ordersを別行にする書き方がこれに当たります。
仕様書を書く側は、どの粒度でエンドポイントと呼ぶかを冒頭で決めておくと読み手の混乱を防げます。迷ったら、レビューや権限設定の単位として扱いやすい3つ目(メソッド+パス)に寄せるのが実務的です。
リソース指向アーキテクチャ(ROA)の定義とRESTとの役割分担・成熟度
RESTとROAの役割分担:Fieldingの6制約と2007年の設計指針
RESTはRoy Fieldingが2000年の博士論文第5章で定義したアーキテクチャスタイルで、クライアントサーバ・ステートレス・キャッシュ・統一インターフェース・階層化システム・コードオンデマンド(任意)という6つの制約からなります。これは制約の集合であり、「注文APIのURLをどう決めるか」までは指定しません。この抽象度の高さが、現場で「RESTfulのつもりだが人によって設計が違う」という状態を生みました。6つの制約それぞれの意味とSOAP・GraphQLとの違いはRESTとは?REST APIの仕組みと6原則・SOAP/GraphQLとの違いを実装目線で解説で掘り下げています。
Leonard Richardson と Sam Ruby は『RESTful Web Services』(2007)の第4章でリソース指向アーキテクチャを提示し、RESTの制約を守るための具体的な設計手順として整理しています。RESTが原則、ROAがその適用手順という関係です。ただしROAはRFCやW3C勧告のような標準仕様ではありません。準拠を主張できる適合性テストは存在せず、「ROA準拠」という表現に規格上の裏付けはない点は押さえておいてください。
ROAの4つの概念と4つの特性:接続性が実務で最も抜けやすい理由
ROAはAPIを次の4つの概念で捉えます。
| 概念 | 意味 | 具体例 |
|---|---|---|
| リソース | 名前を付けて公開する価値のあるもの | ある1件の注文、注文の一覧 |
| URI(名前) | リソースを一意に識別するアドレス | 注文1001を指すURI |
| 表現 | リソースの状態を返す形式 | JSON、XML、CSV |
| リンク | 次に辿れるリソースへの参照 | 注文から顧客リソースへのURI |
そのうえで、設計が満たすべき性質として4つの特性を挙げます。アドレス可能性は、公開したい情報のすべてがURIを持つこと。フィルタ後の一覧にもURIが振られていれば、その状態をそのままリンクとして共有できます。ステートレス性は、各リクエストが単体で処理に必要な情報を持つこと。サーバがクライアントの前回操作を覚えている前提のAPIは、この時点で外れます。接続性は、表現の中に次のリソースへのリンクを含めること(HATEOASに相当)。統一インターフェースは、操作をHTTP標準メソッドの意味どおりに使うことです。
実務で最も抜けやすいのは接続性です。IDだけを返して「URLはクライアント側で組み立ててください」という設計は広く使われていますが、ROAの定義上は接続性を満たしません。外す判断自体はありえます。ただし意図して外していることは設計として明示しておくべき箇所です。
リチャードソン成熟度モデルで測る現在地とレベル2で止める実務の判断
自分のAPIがどこまでリソース指向かは、リチャードソン成熟度モデル(RMM)で位置づけられます。Leonard Richardsonが2008年のQCon講演で示し、Martin Fowlerが2010年3月18日の記事Richardson Maturity Modelで広めたものです。
| レベル | 状態 | 典型的な症状 |
|---|---|---|
| 0 | HTTPを通信路として使うだけ | 単一URLに全操作をPOST |
| 1 | リソースの導入 | URIは分かれたがPOST一辺倒 |
| 2 | HTTPメソッドとステータスコードの正用 | 多くの実用APIがここ |
| 3 | ハイパーメディア制御(HATEOAS) | 応答に次操作のリンクを含む |
エンドポイントとURLの関係で見ると、レベル0は「URLが1本でエンドポイントも1つ(中身はPOSTの本文で分岐)」、レベル1は「URLは分かれたがエンドポイントはPOSTだけ」、レベル2で初めて「1つのURLに複数メソッドのエンドポイントが並ぶ」形になります。前の章で見た分解は、レベル2以上のAPIを前提にしています。
Fowler自身が記事で断っているとおり、RMMは「RESTのレベル」を定義したものではありません。彼はレベル3をFieldingの言うRESTの前提条件と位置づけつつ、モデル自体は概念を理解するための道具だとしています。「レベル3でなければRESTではない」という断定は原典を超えた読み方で、HATEOASの実装は必須ではありません。実務ではレベル2で止め、接続性が効く箇所(ページネーションの次ページリンクなど)にだけリンクを入れる選択が現実的です。レベル3を採用するか見送るかの線引きはHATEOASの仕組みと採用条件で整理しています。
リソースの切り出しとURI命名:エンドポイント一覧を作る設計の手順
リソース抽出の起点:DBスキーマをそのまま写し取るという典型的な失敗
リソース指向の設計は、機能一覧ではなく名詞の抽出から始めます。「注文を確定する」「注文をキャンセルする」という機能から入ると動詞ベースのURIになりますが、「注文」という名詞から入れば、確定もキャンセルも注文リソースの状態遷移として表現できます。名詞を先に並べ、それぞれに標準メソッドを掛け合わせると、エンドポイント一覧が機械的に出来上がる、という順序です。
陥りやすいのは、DBのテーブル構成をそのままエンドポイントに写す設計です。GoogleのAPI設計指針AIP-121は「リソース指向APIは必ずしもデータベースではない」と明記し、内部スキーマの露出を戒めています。中間テーブルがそのまま/v1/user_role_mappings として公開されるようなAPIは、内部構造を変えた瞬間に破壊的変更になります。公開するのはクライアントが名前で呼びたいものだけです。AIP全体の構成とapi-linterでの準拠チェックはGoogle API設計ガイドとは?AIPの規約とapi-linterでの準拠チェック手順にまとめています。
コレクションと個別リソースのURI命名規則:複数形・動詞なし・3階層
URIはコレクションと個別リソースが交互に現れる階層で構成します。
GET /v1/orders 注文コレクション
GET /v1/orders/1001 個別の注文
GET /v1/orders/1001/items 注文に属する明細コレクション
POST /v1/orders 注文の作成
命名の判断基準は3点です。コレクション名は複数形の名詞にする(AIP-122もコレクション識別子は名詞の複数形にするよう求めています)。URIに動詞を入れない(/getOrder や /orders/1001/cancel はROAから外れます)。階層を深くしすぎない。3階層を超えたら、そのリソースが本当に親に従属しているかを疑い、独立したトップレベルのコレクションに切り出すほうが扱いやすくなります。この3点をレビューでどう確認するかはREST API設計のベストプラクティス|URI・冪等性・エラー応答のレビュー基準でチェック項目として整理しています。
標準メソッドの使い分けと冪等性:RFC 9110とRFC 5789による分類
6メソッドの安全性・冪等性の一覧とPATCHが冪等でないという定義
統一インターフェースの実体は、HTTPメソッドの意味論をRFCどおりに使うことです。分類の出典はRFC 9110(HTTP Semantics、2022年6月発行。RFC 7231を廃止)で、PATCHのみRFC 5789が定義元になります。
| メソッド | 安全 | 冪等 | 出典 | ROAでの役割 |
|---|---|---|---|---|
| GET | Yes | Yes | RFC 9110 | リソースの取得 |
| HEAD | Yes | Yes | RFC 9110 | メタ情報のみ取得 |
| POST | No | No | RFC 9110 | コレクションへの作成 |
| PUT | No | Yes | RFC 9110 | 全置換 |
| DELETE | No | Yes | RFC 9110 | 削除 |
| PATCH | No | No | RFC 5789 | 部分更新 |
誤解が多いのがPATCHです。PATCHはRFC 9110の定義対象に含まれていません。定義元はRFC 5789(2010年3月)で、同RFCは「PATCHは安全でも冪等でもない」と明記しています。同時に、差分の表現の仕方によっては冪等なPATCHリクエストを発行しうるとも述べており(絶対値を設定する差分などがこれに当たります)、さらに別途、更新の衝突を防ぐためETagとIf-Matchによる条件付きリクエストを推奨しています。この2つは別の話です。条件付きリクエストは更新の取りこぼしを防ぐ仕組みであって、再送を冪等にする仕組みではありません(成功後の再送は412で弾かれます)。「PATCHは冪等」と書かれた解説を鵜呑みにせず、差分の中身で判断してください。
更新にPUTとPATCHのどちらを使うかは、GoogleのAIP-134が判断材料になります。AIP-134はUpdateにPATCH(部分更新)を推奨し、PUTによる全置換を強く非推奨としています。全置換は、クライアントが知らない新規フィールドを送らなかっただけで意図せず消してしまうためです。
ステータスコード7種の使い分けと400・422の線引きによるリトライ判断
統一インターフェースはメソッドとステータスコードの両輪です。リソース指向のAPIで頻出するのは次の組み合わせです。
| コード | 意味 | 返す場面 |
|---|---|---|
| 200 | OK | GET成功、本文を返す更新 |
| 201 | Created | POSTで作成しLocationに新URI |
| 204 | No Content | DELETE成功、本文なしの更新 |
| 400 | Bad Request | 構文・型が不正 |
| 404 | Not Found | リソースが存在しない |
| 409 | Conflict | 一意制約違反、状態遷移の矛盾 |
| 422 | Unprocessable Content | 構文は正しいが業務ルール違反 |
迷いやすいのが400と422の線引きです。JSONとして壊れている・型が違うなら400、JSONとしては正しいが「発送済みの注文はキャンセルできない」のような業務ルールに反するなら422、と決めておくとクライアント側のリトライ判断が機械的になります。どちらも再送しても結果は変わらないため、クライアントは再試行せずに入力を直すべきエラーです。なお、存在しないパスへのリクエストは404、パスは存在するがメソッドが許可されていない場合は405(Method Not Allowed)と、エンドポイント単位での不一致もステータスコードで区別できます。
POST再送の二重登録対策:失効ドラフトのIdempotency-Keyとその実装
POSTは冪等でないため、ネットワークタイムアウト後にクライアントが再送すると注文が二重に作られます。この対策として広く使われているのがIdempotency-Keyヘッダです。クライアントがリクエストごとにUUID等の一意なキーを付け、サーバは初回の結果を保存して、同じキーの再送には同じ応答を返します。
これはRFCではありません。IETF httpapi WGでドラフトdraft-ietf-httpapi-idempotency-key-headerとして議論されましたが、2026年10月時点のDatatrackerでは最新改訂の-07(2025年10月15日)のまま状態がExpiredとなり、RFC化されていません。拠り所になるのはStripeの冪等リクエスト仕様のようなデファクト仕様です。Stripeは最大255文字のキーを受け取り、初回のステータスコードとレスポンスボディを保存して再送に同じ結果を返し、24時間を過ぎたキーは自動削除の対象とし、同一キーでパラメータが異なる場合はエラーにします。自前実装でも、この「保存期間」と「パラメータ不一致の扱い」を決めずに始めると運用で破綻します。キーの発行・保存・再生の実装手順は冪等キーとは?Idempotency-Keyの発行・保存・再生の実装と二重課金の止め方で解説しました。
標準メソッドで表せない操作の逃がし方:カスタムメソッドと非同期LRO
カスタムメソッドのコロン構文による動詞の隔離とcancelをPATCHで済ます判断
「メールを送信する」「動画をエンコードする」のように、名詞に還元しづらい操作は必ず出てきます。ここでURIに/orders/1001/cancelと動詞を生やすと、リソース階層に動詞が混ざり、以後どこまでが名詞でどこからが動詞か分からなくなります。
AIP-136はこの逃げ道として、リソースURIの末尾にコロンで動詞を付けるカスタムメソッドを定めています。副作用があるならPOST、状態の参照だけならGETを使い、新しいHTTPメソッドは定義しません。
POST /v1/publishers/123/books/456:archive
POST /v1/orders/1001:cancel
コロンで区切ることで、パスの階層構造(名詞)と操作(動詞)が構文上はっきり分かれます。エンドポイントの観点では、/v1/orders/1001というURLの「GET・PATCH・DELETE」に、:cancel付きURLの「POST」が1つ足される形です。一方でAIP-136は、カスタムメソッドは標準メソッドで容易に表現できない機能に限って使うべきとし、可能なら標準メソッドを優先するよう求めています(SHOULD)。カスタムメソッドが増え続けるAPIは、リソースの切り出しが間違っているサインです。「注文をキャンセルする」は、実はstatusフィールドへのPATCHで足りることが少なくありません。
非同期処理のオペレーションのリソース化(LRO)と202 Acceptedのポーリング
数分かかる処理を同期で待たせると、タイムアウトとリトライで処理が多重に走ります。ROAらしい解き方は処理そのものをリソースにすることです。AIP-151はこれをロングランニングオペレーションとして定式化しています。作成リクエストは即座にオペレーションを返し、クライアントはそのURIをポーリングして完了を判定します。オペレーションが持つのはname、完了フラグのdone、成功時のresponse、失敗時のerrorの4項目です。
POST /v1/videos/42:transcode
-> 202 Accepted
{ "name": "operations/abc123", "done": false }
GET /v1/operations/abc123
-> 200 OK
{ "name": "operations/abc123", "done": true, "response": { ... } }
なお、AIP-151が定めるのはオペレーションリソースの構造であり、HTTPステータスコードまでは規定していません。上の例の202 Acceptedは、受理と完了を区別するHTTP実装として一般的な返し方です。いずれにせよポーリング先が独立したURIになるため、クライアントは接続を維持し続ける必要がなく、進捗確認のリクエストは冪等なGETになります。
コレクション取得の設計:カーソル方式のページネーションとフィルタ式
カーソル方式を既定とするページネーション設計とOFFSETが遅くなる理由
一覧取得の設計で最初に決めるのがページネーション方式です。offsetとlimitで切る方式は実装が簡単ですが、2つの問題を抱えます。1つは性能で、PostgreSQLの公式ドキュメント(LIMIT and OFFSET)は「OFFSET句でスキップされる行もサーバ内部で計算する必要があるため、大きなOFFSETは非効率になりうる」と明記しています。10万件目から取得するには、10万行を読み飛ばす作業が毎回発生する計算です。
もう1つがドリフトです。ページをめくっている間に先頭側へ行が挿入されると、以降の行の位置が1つずつずれ、すでに見た行が次のページにもう一度現れます。これはOFFSETが「値」ではなく「行数」で位置を決める方式である以上、避けられません。加えてPostgreSQLの同ドキュメントは、ORDER BYで一意な順序を強制しない限りLIMIT/OFFSETの結果自体が不定になるとも警告しています。
カーソル(キーセット)方式は、行数でスキップせず「最後に見た値」を条件に渡します。WHERE id < :last_seen_id ORDER BY id DESC LIMIT 20 のように書けば、インデックスで直接その位置に到達でき、件数が増えても速度が落ちず、ドリフトも起きません。
実装で落とし穴になるのは、ソートキーが一意でない場合です。created_at順のように同値が並びうる列を単独のカーソルにすると、同一時刻の行を取りこぼしたり重複させたりします。一意な列を組み合わせたタイブレークが必要です。
-- created_at 順(同値がありうる)でのカーソル
SELECT * FROM orders
WHERE (created_at, id) < (:last_created_at, :last_id)
ORDER BY created_at DESC, id DESC
LIMIT 20;
API表現としては、AIP-158のpage_size / page_token / next_page_tokenが参考になります。設計上の要点は、ページトークンをクライアントがパースできない不透明な値にすることと、空のnext_page_tokenだけを終端の合図にすることです。トークンの中身がオフセット値だと分かる形式にすると、クライアントがそれを自前で組み立てはじめ、内部方式を変えられなくなります。ページ送りはクエリ文字列で表すため、エンドポイントは増えません。オフセットとカーソルの選び分けを実装例つきで比べた解説はページネーション設計とは?オフセットとカーソルの選び分けと実装にあります。
フィルタ式の設計:OR条件と否定が要る時点で単一filterへ集約
絞り込み条件をフィールドごとのクエリパラメータ(?status=paid&min_price=1000)で増やしていくと、条件が増えるたびに仕様が膨らみ、OR条件や否定を表現できなくなります。AIP-160は単一のfilter文字列に式を渡す方式を採り、AND/OR/NOT・比較演算子・フィールド走査をひとつの文法で扱います(不正な式はINVALID_ARGUMENTで拒否)。
小規模なAPIならフィールドごとのパラメータで十分です。分かれ目はOR条件や否定の要望が出てきたかどうか。出てきた時点でパラメータ方式は破綻に向かうため、そこがフィルタ式へ切り替える境目です。どちらの方式でも、絞り込みのために/orders/paidのような別エンドポイントを生やさないことが、エンドポイント数を抑える基本です。
エラー応答とリトライの設計:RFC 9457の形式と429・ジッタ付きバックオフ
Problem Details(RFC 9457)によるエラー形式の統一
エラー応答の形式を独自に決めると、クライアントごとにパース処理が分かれます。RFC 9457(2023年7月発行、RFC 7807を廃止)がHTTP APIのエラー応答形式を標準化しており、メディアタイプapplication/problem+jsonにtype(問題種別を識別するURI)、title(人間可読の要約)、status(HTTPステータスと同じ値)、detail(この発生に固有の説明)、instanceを載せます。クライアントはtitleの文字列ではなくtypeのURIで分岐する、というのが仕様の求める使い方です。
HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json
{
"type": "https://api.example.com/problems/order-already-shipped",
"title": "発送済みの注文はキャンセルできません",
"status": 422,
"detail": "注文1001は発送済みのためキャンセルできません。",
"instance": "/v1/orders/1001"
}
instanceにエラーが起きたリソースのURIを入れておくと、どのエンドポイントで何が起きたかをログと突き合わせやすくなります。詳しくはRFC 9457とは?Problem Details(application/problem+json)でHTTP APIエラー応答を標準化【RFC 7807との違い】で解説しています。
429とRetry-Afterの返し方とFull Jitterによる再試行の分散
レート制限に返す429はRFC 9110ではなくRFC 6585で定義されたステータスコードで、Retry-After(RFC 9110 §10.2.3、HTTP日付または秒数)を添えて再試行時期を伝えられます。クライアント側の再試行は指数バックオフにジッタを加えるのが定石です。AWSの公式解説「Exponential Backoff And Jitter」(2015年3月4日)はsleep = random(0, min(cap, base * 2 ** attempt))というFull Jitterを検証し、総リクエスト数(クライアントの作業量)が最も少なくなる一方、完了までの時間はDecorrelated Jitterよりやや長くなると報告しています。両者の優劣は用途によりますが、ジッタなしの指数バックオフが劣ることは明確です。障害復旧の瞬間に全クライアントの再試行が同じ時刻に揃い、復旧したサーバを再び潰す波を作ります。
バージョニングとスキーマの管理:AIP-180の破壊的変更とOpenAPI
破壊的変更の線引きとバージョンの上げどき:v1のまま追加で吸収する基準
URIにバージョンを含める方式(/v1/orders)は、キャッシュやログでバージョンが可視になるため運用しやすく、実務での採用が多い方式です。この方式ではバージョンがベースURLの一部になり、v2を出すとすべてのエンドポイントのURLが変わります。ただし肝心なのは方式選びよりも、何を破壊的変更と見なすかを先に決めることです。
ここはGoogleのAIP-180(Backwards compatibility)が明確な基準を出しています。同一メジャーバージョン内で禁止(must not)されるのは、既存フィールド・メソッド・列挙値の削除やリネーム、既存フィールドの型変更(ワイヤ互換であっても不可)、リソース名の変更、既定値の変更です。フィールドの追加は許されますが、既存のリクエストに必須フィールドを追加することは禁止され、追加する任意フィールドは「そのフィールドが無かった頃と同じ挙動」を既定値として持たなければなりません。
この線引きを最初に共有しておけば、多くの変更はv1のまま追加で吸収できます。バージョンを上げるほど並行運用するコードとテストが増えるため、上げずに済ませる基準を持つことがそのまま運用コストの削減になります。リソース定義からURI・スキーマ・エラー応答・版管理までを工程順に並べた手順はAPI設計とは?リソース定義からURI・スキーマ・エラー・版管理までの実装手順で整理しました。
OpenAPIによるエンドポイント定義の機械可読化:serversとpathsの分担
リソース定義とメソッドの対応は、OpenAPIで機械可読な形にしておくとクライアント生成・モックが自動化できます。2026年10月時点の最新はOpenAPI 3.2系で、3.2.0(2025年9月19日公開)に続き、パッチ版の3.2.1が2026年9月10日に公開されています(OpenAPI Specification v3.2.1)。「最新は3.1系」という記述はすでに古くなっています。
エンドポイントとURLの違いは、OpenAPIの書き方にそのまま現れます。serversにベースURLを1回だけ書き、pathsにパスを、その下にメソッドごとの操作を書きます。
openapi: 3.2.0
info:
title: Orders API
version: 1.0.0
servers:
- url: https://api.example.com/v1
paths:
/orders/{order_id}:
parameters:
- name: order_id
in: path
required: true
schema:
type: string
get:
summary: 注文の取得
responses:
"200":
description: OK
patch:
summary: 注文の部分更新
responses:
"200":
description: OK
この定義では、serversのURLとpathsのキーを連結したhttps://api.example.com/v1/orders/{order_id}が完全なURLになり、getとpatchがそれぞれ1つのエンドポイントに当たります。ステージングと本番でベースURLだけを差し替えたい場合も、serversに複数のURLを並べればパスとメソッドの定義は共通のまま扱えます。書き方の全体像はOpenAPIとは|Swaggerとの違い・仕様書(YAML/JSON)の書き方とSwagger UIをわかりやすく解説を参照してください。
リソース指向で設計すべきでないAPI:手続き型・画面依存・単一クライアント
ROAは万能ではありません。次の条件に当てはまるなら、リソース指向を無理に貫くほうがコストになります。
操作が本質的に手続きで、名詞に還元すると意味を失う場合。「与信を計算する」「シミュレーションを実行する」のような処理は、結果をリソース化できても、リソース一覧やDELETEに意味がありません。カスタムメソッドが標準メソッドより多いAPIになったら、それはROAが合っていない兆候です。RPC的な方式に寄せたほうが素直で、その比較はtRPCとREST・GraphQLとの違いを徹底比較して理解を深めるが参考になります。
クライアントが画面ごとに異なる形のデータを必要とする場合。リソース単位で正規化されたAPIは、1画面を描くのに複数回の往復が必要になります(N+1リクエスト)。画面数が多くリソース間の関係が深いプロダクトでは、必要な形をクライアントが宣言できる方式が有利です。GraphQLでは単一のURLに問い合わせを集約するため、「URLとエンドポイントが1つずつ」という、ROAとは逆の構成になります。判断材料はREST APIとGraphQLの違いと使い分け|選定基準と運用コストにまとめています。
社内の1クライアントしか呼ばない内部API。アドレス可能性も接続性も、第三者が発見して辿ることに価値があるから効きます。呼び出し元が自社の1アプリだけなら、その価値は生まれず、設計規約の遵守コストだけが残ります。
逆に、複数チーム・外部パートナーが長期に使い、クライアントの寿命がサーバより長いAPIでは、ROAの制約が効きます。統一インターフェースは「新しい呼び出し元が仕様書を読まなくても振る舞いを予測できる」という形で回収されるため、利用者と年数が増えるほど利く投資です。外部連携を前提にしたAPIのURI設計やOpenAPIでの仕様化、既存システムとの接続を外部に任せる場合は、一創のAPI開発・システム連携で設計段階から相談を受け付けています。
よくある質問
APIエンドポイントとURLの違いや、リソース指向の設計で迷いやすい点を5つの問いに絞って回答します。
APIエンドポイントとURLの違いは何ですか?
URLはリソースの所在を示す文字列で、RFC 3986の定義ではURIの部分集合です。エンドポイントはRFCで定義された用語ではありませんが、実務ではベースURLとパスにHTTPメソッドを組み合わせた「1つの操作の入口」を指します。同じ/v1/orders/1001というURLでも、GET(取得)とDELETE(削除)は別のエンドポイントという扱いです。ドキュメントによってはホスト名だけやパスだけをエンドポイントと呼ぶこともあるため、仕様書ではどの粒度で呼ぶかを先に決めておきます。
リソース指向アーキテクチャとRESTは同じものですか?
同じではありません。RESTはFieldingが2000年に定義したアーキテクチャスタイル(6つの制約)で、ROAはそのRESTをWeb APIとして実装するための設計指針として『RESTful Web Services』(2007)が示したものです。RESTが「守るべき制約」、ROAが「どう設計すれば守れるか」という関係にあります。ROAはRFCのような標準仕様ではないため、準拠を判定する公式のテストもありません。
PUTとPOSTとPATCHの違いは何ですか?
POSTはコレクションに新しいリソースを作る操作で、冪等ではありません(同じリクエストを2回送ると2件できます)。PUTは指定URIのリソースを全置換する操作で冪等です。PATCHは部分更新で、RFC 5789の定義上は安全でも冪等でもありません。GoogleのAIP-134は更新にPATCHを推奨し、送らなかったフィールドが消えるPUTでの全置換を強く非推奨としています。
URIに動詞を入れてはいけないのはなぜですか?
動詞をHTTPメソッド側に集約するのが統一インターフェースの前提だからです。URIに動詞を入れると、同じ操作が/createOrderと/orders/newのように書き手ごとに増殖し、キャッシュや冪等性の判断もHTTPの意味論から外れます。どうしても手続き的な操作が必要なら、AIP-136のカスタムメソッド(/v1/orders/1001:cancel)としてコロンの右側に隔離します。
APIのページネーションはオフセットとカーソルのどちらを選ぶべきですか?
件数が増える見込みがあるならカーソル(キーセット)方式です。PostgreSQL公式が指摘するとおり、OFFSETはスキップする行もサーバ内部で計算するため大きな値で遅くなり、ページ送り中に行が挿入されると同じ行が重複して現れます。管理画面で「◯ページ目へジャンプ」が要件になっている場合だけ、オフセット方式に合理性があります。
関連記事
- API設計とは?リソース定義からURI・スキーマ・エラー・版管理までの実装手順:本記事の設計判断を工程順に並べた実装手順
- RESTとは?REST APIの仕組みと6原則・SOAP/GraphQLとの違いを実装目線で解説:ROAの土台になるRESTの6制約の解説
- REST API設計のベストプラクティス|URI・冪等性・エラー応答のレビュー基準:URI命名や冪等性をレビューで確認するチェック項目
- ページネーション設計とは?オフセットとカーソルの選び分けと実装を解説:コレクション取得の章の詳細版
- 冪等キーとは?Idempotency-Keyの発行・保存・再生の実装と二重課金の止め方を解説:POST再送対策の実装手順