REST APIでは、URL、HTTPメソッド、status code、bodyを一つの契約として設計します。
最初に決めるのはendpointの数ではなく、利用者が扱うリソースと、失敗・再試行・変更時の挙動です。
1. URLはリソースを表す
注文を扱うなら、URLには動詞ではなくリソース名を置き、操作はHTTPメソッドで表します。

GET /orders
POST /orders
GET /orders/{orderId}
PATCH /orders/{orderId}
DELETE /orders/{orderId}
POST /createOrderやPOST /deleteOrderを増やすより、同じリソースへ標準メソッドを対応させると、clientやmiddlewareが意味を予測しやすくなります。
ただし、すべての業務操作をCRUDへ無理に押し込む必要はありません。「注文を確定する」が独立した状態遷移なら、確定というsub-resourceを作る方法があります。
POST /orders/{orderId}/confirmations
URLの単数・複数、case、ID形式は、既存API内で統一します。階層を深くしすぎず、子リソースが親なしでも識別できるなら/items/{itemId}のような短いURLも検討します。
2. HTTPメソッドの意味を守る
| メソッド | 主な意味 | safe | idempotent |
|---|---|---|---|
| GET | 表現を取得する | はい | はい |
| POST | collectionへ作成・処理を依頼する | いいえ | 標準では保証しない |
| PUT | 指定URIの表現を置き換える | いいえ | はい |
| PATCH | 一部を変更する | いいえ | patch形式と実装による |
| DELETE | 指定URIとの関連を削除する | いいえ | はい |
RFC 9110のidempotentは、同じrequestを複数回送った時、serverへ意図した効果が一回と同じになる性質です。ログが複数行残るなど、付随する処理が完全に一回になるという意味ではありません。
GETでデータ変更を行うと、crawler、prefetch、cacheが意図せず実行する危険があります。読み取り用メソッドに副作用を持たせません。
3. status codeとエラーbodyを分ける
status codeはHTTP共通の大分類、bodyはアプリ固有の詳細を伝えます。
| 状況 | 代表的なstatus |
|---|---|
| 取得成功 | 200 OK |
| 作成成功 | 201 Created |
| bodyなしで成功 | 204 No Content |
| 入力形式が不正 | 400 Bad Request |
| 認証が必要または無効 | 401 Unauthorized |
| 認証済みだが許可されない | 403 Forbidden |
| リソースがない | 404 Not Found |
| 現在状態と競合する | 409 Conflict |
| 処理できない入力 | 422 Unprocessable Content |
| requestが多すぎる | 429 Too Many Requests |
| server内部の失敗 | 500 Internal Server Error |
すべての失敗を200と独自success: falseで返すと、HTTP client、監視、retry判断が使いにくくなります。
Problem Detailsを使う
RFC 9457は、HTTP APIの機械可読なエラー形式を定義します。media typeはapplication/problem+jsonです。
{
"type": "https://api.example.com/problems/insufficient-stock",
"title": "在庫が不足しています",
"status": 409,
"detail": "注文数3に対して在庫は2です",
"instance": "/orders/order_123"
}
typeはproblem種類を識別するURI、titleは種類の短い説明、detailは今回の発生内容です。stack trace、SQL、token、内部host名を返しません。入力項目ごとのerrorを拡張memberとして足す場合も、schemaを文書化します。
4. 後方互換性を先に考える
API clientはserverと同時に更新されるとは限りません。次の変更は一般に壊れやすい変更です。
- fieldの削除・名前変更・型変更
- 必須入力の追加
- enumへ、古いclientが処理できない値を追加
- status codeやエラー形式の変更
- paginationや並び順の既定変更
新fieldの追加も、clientが未知fieldを拒否する設計なら壊れます。client側は不要なfieldを無視し、server側は移行期間に新旧形式を扱えるようにします。
versionはURLの/v1/、header、media typeなどで表せます。方式より重要なのは、何を一つのversion境界とし、古いversionをいつまで提供し、利用状況をどう測って終了するかです。
GET /v1/orders/order_123
小さな追加ごとにversionを増やさず、互換な追加と破壊的変更を分けます。廃止予定は文書、response header、利用者への通知で伝え、移行確認前に旧versionを消しません。
5. retryされるrequestを安全にする
通信切断時、clientは「serverが処理する前に失敗した」のか「処理後にresponseだけ失った」のか分かりません。PUTやDELETEはHTTP上idempotentになるよう設計します。
作成用POSTを安全にretryしたい場合、idempotency keyをAPI契約として導入できます。
POST /payments
Idempotency-Key: 8f8614a0-7c36-4cb8-9a88-7c69978cc92f
Content-Type: application/json
{"orderId":"order_123","amount":5000}
serverは認証主体、endpoint、key、request内容を結び付け、同じkeyと同じ内容の再送には最初の結果を返します。同じkeyで内容が違うrequestは拒否します。
keyの保存期間、同時requestの競合、失敗結果を保存するか、下流処理まで一回にする方法を決めます。headerを受け取るだけでは冪等性になりません。DBのunique制約やtransaction、外部決済側のidempotency機能と組み合わせます。
最小の設計チェック
リソース
- URLから対象が分かるか
- 認可をresourceごとにserver側で確認するか
- actionを無理にGETへ置いていないか
エラー
- status codeとproblem typeが一貫しているか
- clientがretry可能か判断できるか
- 秘密情報や内部実装を返していないか
変更
- 古いclientが新responseを読めるか
- 破壊的変更のversion、移行期間、終了条件があるか
- 利用中の旧versionを観測できるか
再試行
- timeout後の再送で二重作成・二重課金しないか
- idempotency keyとrequest内容を照合するか
- 同時実行でも一回の効果にできるか
認証、rate limit、pagination、OpenAPIは重要ですが、個別の設計テーマです。最初にresource、error、version、idempotencyの契約を固めてから追加します。
まとめ
変更に強いREST APIは、リソースをURL、操作をHTTPメソッドで表し、status codeとProblem Detailsで失敗を伝えます。clientを同時更新できない前提で後方互換性を守り、通信失敗によるretryを冪等に処理します。endpoint一覧より先に、失敗と変更の契約を設計することが重要です。