Claude CodeでREST API設計を固める: エンドポイント、エラー、ページング、冪等性
Claude CodeでREST API設計を実装前に固める手順。OpenAPI、エラー、ページング、冪等性を具体例で整理。
REST APIは、ルートを何本か作れば完成するものではありません。実装前に「何をリソースと呼ぶか」「どのHTTPメソッドを使うか」「失敗時に何を返すか」「一覧をどうページングするか」「同じ依頼を2回受けたらどう扱うか」を決めておかないと、Claude Codeが速くコードを書いてくれても、あとでフロントエンド、モバイルアプリ、外部連携が別々の解釈を持ちます。
この記事の狙いは、Claude Codeで実装に入る前にREST API設計を固定することです。RESTは「Representational State Transfer」の略ですが、ここでは難しく考えず、注文、ユーザー、請求書のようなリソースをHTTPで操作する設計ルールと捉えてください。リソースは扱う対象、メソッドは操作、ステータスコードは結果の合図です。
公式リファレンスとして、HTTPメソッドとステータスコードはMDNのHTTP request methodsとHTTP response status codes、仕様書はOpenAPI Specificationを参照します。2026年6月3日時点でOpenAPIのlatestページは3.2.0ですが、この記事の例は多くのリンターや生成ツールで扱いやすいopenapi: 3.1.0にしています。
関連する実装の流れはClaude Codeで本番API開発、テスト観点はClaude Code APIテスト、公開前のレビューはClaude Codeコードレビューも合わせて確認してください。テンプレートやチェックリストをそのまま使いたい場合は/products/、チームでAPI設計レビューを定着させたい場合は/training/に整理しています。
先に語彙をそろえる
初心者がつまずきやすいのは、REST APIの設計用語が似ていることです。Claude Codeに依頼する前に、次の言葉をチーム内で同じ意味にしておきます。
| 用語 | 平易な説明 | この記事での例 |
|---|---|---|
| リソース | APIで扱う名詞。作成、取得、更新、削除の対象 | orders, customers |
| エンドポイント | リソースへ入るURLとメソッドの組み合わせ | GET /v1/orders |
| メソッド | HTTPで「何をしたいか」を示す動詞 | GET, POST, PATCH |
| ステータスコード | 成功、入力ミス、未認証などを数値で伝える合図 | 200, 201, 400, 404 |
| バリデーション | リクエストが正しい形か確認する処理 | メール形式、数量の最小値 |
| 冪等性 | 同じ操作を何度送っても最終状態が同じ性質 | 同じキーの注文作成を1回にする |
Claude Codeは文脈を読むのが得意ですが、曖昧な設計語を補完してくれる保証はありません。「注文作成APIを作って」だけでは、/createOrder、/orders/create、POST /ordersのどれを選ぶかが揺れます。まず言葉と設計方針を渡すことが、実装品質を上げる近道です。
4つのユースケースで設計を決める
同じ注文APIでも、使われ方で設計の優先順位は変わります。
1つ目はB2B SaaSの注文連携です。取引先が同じCSVを毎晩送るなら、POST /v1/ordersにはIdempotency-Keyを必須にし、同じキーの再送で二重注文を作らない設計が必要です。障害復旧で同じリクエストを再送する場面があるため、冪等性は後付けにしづらい要件です。
2つ目は管理画面の一覧APIです。担当者がstatus=paidで検索し、次のページへ進み、さらに別の担当者が注文を追加するかもしれません。page=2だけでは途中で並びがずれます。limitとafterを使うカーソルページングにし、並び順をcreatedAt desc, id descのように固定します。
3つ目はモバイルアプリ向けAPIです。古いアプリが数か月残るなら、レスポンスのフィールド名を急に変えられません。新しい必須項目やエラー形式の変更は/v2として分け、/v1の契約をOpenAPIに残します。小さな任意フィールドの追加まで毎回バージョンを上げる必要はありません。
4つ目に、社内サービス間APIもあります。ここでは利用者を一斉に更新しやすい反面、監視やアラートが甘くなりがちです。内部APIでも、ステータスコードとエラー形式を統一しないと、呼び出し側が毎回違う例外処理を書き始めます。
設計の流れを図で見る
実装前に固定する順番は次の通りです。上から下へ合意してからClaude Codeに渡すと、途中で設計が横滑りしにくくなります。
flowchart TD
A["Use cases"] --> B["Resources and endpoints"]
B --> C["OpenAPI contract"]
C --> D["Errors, pagination, idempotency"]
D --> E["Claude Code implementation"]
E --> F["Tests and review"]
エンドポイント表を実装前に固定する
最初の成果物はコードではなくエンドポイント表です。Claude Codeにこの表を渡すと、ルーティング、OpenAPI、テスト、ドキュメントが同じ設計から始まります。
| 目的 | メソッドとパス | 成功時 | 重要なルール |
|---|---|---|---|
| 注文一覧 | GET /v1/orders?status=paid&limit=20&after=ord_123 | 200 OK | カーソルページング。安定した並び順を固定 |
| 注文作成 | POST /v1/orders | 201 Created | Idempotency-Keyを受け付け、Locationを返す |
| 注文詳細 | GET /v1/orders/{orderId} | 200 OK | 存在しなければ404 |
| 注文更新 | PATCH /v1/orders/{orderId} | 200 OK | 部分更新。空の更新は400 |
| 注文取消 | POST /v1/orders/{orderId}/cancel | 200 OK | 状態遷移はサブリソース風に扱う |
原則は「名詞をURLに、動詞をメソッドに」です。ただし、注文取消のように業務上の状態遷移が強い操作は、無理にDELETEへ寄せるより/cancelを明示したほうが読みやすいことがあります。大事なのは例外をルール化し、同じ種類の操作で表記を揺らさないことです。
OpenAPIを設計のソースにする
OpenAPIは、APIのパス、パラメータ、リクエスト、レスポンスを機械が読める形で書く設計書です。Claude Codeに「このOpenAPIを正として実装して」と渡すと、実装の自由度を必要な範囲に絞れます。
openapi: 3.1.0
info:
title: Acme Orders API
version: 1.0.0
servers:
- url: https://api.example.com
paths:
/v1/orders:
get:
operationId: listOrders
summary: List orders
parameters:
- $ref: "#/components/parameters/StatusParam"
- $ref: "#/components/parameters/LimitParam"
- $ref: "#/components/parameters/AfterParam"
responses:
"200":
description: Orders are returned in a stable cursor order.
content:
application/json:
schema:
$ref: "#/components/schemas/OrderListResponse"
"400":
$ref: "#/components/responses/BadRequest"
post:
operationId: createOrder
summary: Create an order
parameters:
- $ref: "#/components/parameters/IdempotencyKey"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CreateOrderRequest"
responses:
"201":
description: Order created.
headers:
Location:
schema:
type: string
description: URL of the created order.
content:
application/json:
schema:
$ref: "#/components/schemas/OrderResponse"
"400":
$ref: "#/components/responses/BadRequest"
"409":
$ref: "#/components/responses/Conflict"
/v1/orders/{orderId}:
get:
operationId: getOrder
summary: Get one order
parameters:
- $ref: "#/components/parameters/OrderId"
responses:
"200":
description: Order found.
content:
application/json:
schema:
$ref: "#/components/schemas/OrderResponse"
"404":
$ref: "#/components/responses/NotFound"
patch:
operationId: updateOrder
summary: Update mutable order fields
parameters:
- $ref: "#/components/parameters/OrderId"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/UpdateOrderRequest"
responses:
"200":
description: Order updated.
content:
application/json:
schema:
$ref: "#/components/schemas/OrderResponse"
"400":
$ref: "#/components/responses/BadRequest"
"404":
$ref: "#/components/responses/NotFound"
components:
parameters:
OrderId:
name: orderId
in: path
required: true
schema:
type: string
StatusParam:
name: status
in: query
schema:
type: string
enum: [draft, paid, canceled]
LimitParam:
name: limit
in: query
schema:
type: integer
minimum: 1
maximum: 100
default: 20
AfterParam:
name: after
in: query
schema:
type: string
description: Cursor returned by the previous response.
IdempotencyKey:
name: Idempotency-Key
in: header
required: false
schema:
type: string
minLength: 16
maxLength: 128
schemas:
CreateOrderRequest:
type: object
required: [customerId, items]
properties:
customerId:
type: string
items:
type: array
minItems: 1
items:
type: object
required: [sku, quantity]
properties:
sku:
type: string
quantity:
type: integer
minimum: 1
UpdateOrderRequest:
type: object
minProperties: 1
properties:
status:
type: string
enum: [draft, paid, canceled]
OrderResponse:
type: object
required: [data]
properties:
data:
$ref: "#/components/schemas/Order"
OrderListResponse:
type: object
required: [data, pageInfo]
properties:
data:
type: array
items:
$ref: "#/components/schemas/Order"
pageInfo:
type: object
required: [nextCursor, hasMore]
properties:
nextCursor:
type: [string, "null"]
hasMore:
type: boolean
Order:
type: object
required: [id, status, totalCents, createdAt]
properties:
id:
type: string
status:
type: string
totalCents:
type: integer
createdAt:
type: string
format: date-time
ErrorResponse:
type: object
required: [error]
properties:
error:
type: object
required: [code, message, requestId]
properties:
code:
type: string
message:
type: string
requestId:
type: string
details:
type: array
items:
type: object
responses:
BadRequest:
description: Request validation failed.
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
Conflict:
description: Idempotency key conflicts with a different request.
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
NotFound:
description: Resource was not found.
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
この設計書をopenapi.yamlとして保存し、Claude Codeには「この契約から外れる実装を提案しない」と明記します。生成後はnpx @redocly/cli lint openapi.yamlのようなリンターで構文と参照を確認できます。実装言語がTypeScriptなら、OpenAPIから型やクライアントを生成する前に、人間が表の意味をレビューすることも忘れないでください。
エラー形式とステータスコードをそろえる
ステータスコードは「HTTPの外側の合図」、JSONのエラー本文は「人間とプログラムが読む詳細」です。200 OKで{ "success": false }を返すと、監視、SDK、リトライ処理が成功と誤解します。入力ミスは400、認証なしは401、権限不足は403、存在しないリソースは404、重複や冪等性の衝突は409のように、最初に表で固定します。
{
"error": {
"code": "VALIDATION_FAILED",
"message": "Request body has invalid fields.",
"requestId": "req_01J0RESTAPI001",
"details": [
{
"field": "items[0].quantity",
"reason": "must be greater than or equal to 1"
}
]
}
}
{
"error": {
"code": "IDEMPOTENCY_CONFLICT",
"message": "The same Idempotency-Key was used with a different request body.",
"requestId": "req_01J0RESTAPI002"
}
}
{
"error": {
"code": "ORDER_NOT_FOUND",
"message": "Order was not found.",
"requestId": "req_01J0RESTAPI003"
}
}
初心者向けに言えば、codeはプログラムが分岐するための短い名前、messageはログや画面で人間が読む説明、requestIdは問い合わせ時にサーバーログを探すための番号です。detailsはバリデーションエラーのときだけ増やせば十分です。
ページング、冪等性、バージョンを曖昧にしない
一覧APIは、最初からページングを決めます。件数が少ないうちはGET /ordersですべて返しても動きますが、あとでデータが増えるとレスポンスが重くなり、途中から仕様を変えることになります。カーソル方式なら、前回レスポンスのnextCursorを次のafterに渡します。
{
"data": [
{
"id": "ord_100",
"status": "paid",
"totalCents": 4800,
"createdAt": "2026-06-03T09:00:00Z"
}
],
"pageInfo": {
"nextCursor": "ord_099",
"hasMore": true
}
}
冪等性は、同じ依頼が複数回来ても結果を重複させないための設計です。特にPOST /ordersは、ネットワークエラーでクライアントが再送します。Idempotency-Keyとリクエスト本文のハッシュを一定時間保存し、同じキーと同じ本文なら前回の結果を返し、同じキーで本文が違えば409を返します。
バージョニングは、利用者に移行時間を渡す仕組みです。レスポンスから必須フィールドを消す、型を変える、既定の並び順を変える、新しい必須入力を増やすなら破壊的変更です。その場合は/v2を作ります。一方、任意フィールドを追加するだけなら、通常は/v1のままでもよいです。
Claude Codeへの依頼文
実装前レビュー用の依頼は、次のように具体化します。
このOpenAPI設計をレビューしてください。
観点:
- リソース名が名詞になっているか
- HTTPメソッドとステータスコードがMDNの意味に合っているか
- 400/401/403/404/409/500のエラー形式が同じか
- 一覧APIのページングが曖昧でないか
- POST作成に冪等性キーが必要な箇所がないか
- v1で壊してはいけないレスポンス変更が混ざっていないか
- TypeScript実装とテストを作る前に直すべき点を表で出してください
この依頼文を使うと、Claude Codeがいきなりファイル編集へ進む前に、設計レビュー担当として振る舞いやすくなります。実装を依頼するのは、レビューで直したOpenAPIを保存してからです。
よくある失敗例
失敗例1は、アクション風エンドポイントを増やすことです。POST /createOrder、POST /updateOrderStatus、GET /deleteOrderが混ざると、URLだけで対象と操作が読めません。基本はPOST /v1/orders、PATCH /v1/orders/{orderId}、DELETE /v1/orders/{orderId}に寄せ、例外は表に書きます。
失敗例2は、ステータスコードが揺れることです。入力ミスでも500、見つからない場合も200、作成成功なのに200と201が混ざる状態では、クライアントが正しい分岐を書けません。エラー形式だけでなく、成功時のコードも固定します。
失敗例3は、ページングの意味が曖昧なことです。page=2が「作成日時順」なのか「更新日時順」なのか決まっていないと、データ追加時に重複や取りこぼしが起きます。並び順、最大limit、次ページの有無をOpenAPIに書きます。
失敗例4は、バリデーションを実装だけに閉じ込めることです。コードにだけquantity >= 1があり、OpenAPIに書かれていないと、フロントエンドやSDK生成で同じ条件を再利用できません。入力制約は仕様にも実装にも置きます。
レビュー checklist
- リソース名は複数形の名詞で統一したか
- メソッドは
GET、POST、PATCH、DELETEの意味に合っているか - 成功時の
200、201、204を使い分けたか 400、401、403、404、409、500のエラーJSONが同じ形か- 一覧APIに
limit、after、nextCursor、hasMoreがあるか limitの最大値と既定値を決めたか- 作成系
POSTの再送で重複作成しないか - 破壊的変更を
/v2に分ける基準を書いたか - OpenAPIにバリデーション条件を記述したか
- Claude Codeに渡す前に、表とOpenAPIの内容が一致しているか
REST API設計は、きれいなURLを考える作業ではなく、利用者との約束を文章と機械可読な仕様に落とす作業です。Claude Codeは実装速度を上げますが、どの約束を守るべきかは人間が先に決めます。
実際に試した結果、Masaの注文APIではOpenAPI、エンドポイント表、エラーJSONを先に固定してからClaude Codeへ実装を頼んだほうが、レビューで出る手戻りが明確に減りました。特にIdempotency-Keyとカーソルページングを設計段階で入れたことで、あとから二重注文対策や一覧の取りこぼし対策を差し込む必要がなくなりました。
無料PDF: Claude Code はじめてのチートシート
まずは無料PDFで基本コマンドと最初の使い方をまとめて確認してください。登録後はそのままテンプレート集や導入相談にも進めます。
スパムは送りません。登録情報は厳重に管理します。
Claude Codeを仕事で使える形にしませんか?
無料PDFで基礎を固めたあと、すぐ使えるテンプレート集で試し、必要なら業務自動化や導入相談まで進められます。
この記事を書いた人
Masa
Claude Codeの実務活用、導入設計、収益導線改善を検証しているエンジニア。10言語の技術メディアを運営中。
関連書籍・参考図書
この記事のテーマに関連する書籍を楽天ブックスで探せます。
※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。
関連記事
Claude Code権限セーフティラダー: 初心者がallowを広げる順番
Claude Codeの権限をread-onlyからbuild、限定編集、deploy確認まで段階的に広げる安全な運用手順。
Claude Code Small PR Proof Pack: 小さなPRをレビュー可能にする証拠セット
Claude Codeの小さなPRに、差分・検証・公開URL・CTA・rollbackを添える実務チェックリスト。
Claude Codeのコミット前レビューゲート: 差分、テスト、CTAをまとめて止める型
Claude Codeでcommit前に差分をレビューする実践手順。build、公開URL、CTA、Gumroadリンク、未翻訳本文を検知します。