Tips & Tricks (更新: 2026/6/3)

Claude CodeでREST API設計を固める: エンドポイント、エラー、ページング、冪等性

Claude CodeでREST API設計を実装前に固める手順。OpenAPI、エラー、ページング、冪等性を具体例で整理。

Claude CodeでREST API設計を固める: エンドポイント、エラー、ページング、冪等性

REST APIは、ルートを何本か作れば完成するものではありません。実装前に「何をリソースと呼ぶか」「どのHTTPメソッドを使うか」「失敗時に何を返すか」「一覧をどうページングするか」「同じ依頼を2回受けたらどう扱うか」を決めておかないと、Claude Codeが速くコードを書いてくれても、あとでフロントエンド、モバイルアプリ、外部連携が別々の解釈を持ちます。

この記事の狙いは、Claude Codeで実装に入る前にREST API設計を固定することです。RESTは「Representational State Transfer」の略ですが、ここでは難しく考えず、注文、ユーザー、請求書のようなリソースをHTTPで操作する設計ルールと捉えてください。リソースは扱う対象、メソッドは操作、ステータスコードは結果の合図です。

公式リファレンスとして、HTTPメソッドとステータスコードはMDNのHTTP request methodsHTTP 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/createPOST /ordersのどれを選ぶかが揺れます。まず言葉と設計方針を渡すことが、実装品質を上げる近道です。

4つのユースケースで設計を決める

同じ注文APIでも、使われ方で設計の優先順位は変わります。

1つ目はB2B SaaSの注文連携です。取引先が同じCSVを毎晩送るなら、POST /v1/ordersにはIdempotency-Keyを必須にし、同じキーの再送で二重注文を作らない設計が必要です。障害復旧で同じリクエストを再送する場面があるため、冪等性は後付けにしづらい要件です。

2つ目は管理画面の一覧APIです。担当者がstatus=paidで検索し、次のページへ進み、さらに別の担当者が注文を追加するかもしれません。page=2だけでは途中で並びがずれます。limitafterを使うカーソルページングにし、並び順を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_123200 OKカーソルページング。安定した並び順を固定
注文作成POST /v1/orders201 CreatedIdempotency-Keyを受け付け、Locationを返す
注文詳細GET /v1/orders/{orderId}200 OK存在しなければ404
注文更新PATCH /v1/orders/{orderId}200 OK部分更新。空の更新は400
注文取消POST /v1/orders/{orderId}/cancel200 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 /createOrderPOST /updateOrderStatusGET /deleteOrderが混ざると、URLだけで対象と操作が読めません。基本はPOST /v1/ordersPATCH /v1/orders/{orderId}DELETE /v1/orders/{orderId}に寄せ、例外は表に書きます。

失敗例2は、ステータスコードが揺れることです。入力ミスでも500、見つからない場合も200、作成成功なのに200201が混ざる状態では、クライアントが正しい分岐を書けません。エラー形式だけでなく、成功時のコードも固定します。

失敗例3は、ページングの意味が曖昧なことです。page=2が「作成日時順」なのか「更新日時順」なのか決まっていないと、データ追加時に重複や取りこぼしが起きます。並び順、最大limit、次ページの有無をOpenAPIに書きます。

失敗例4は、バリデーションを実装だけに閉じ込めることです。コードにだけquantity >= 1があり、OpenAPIに書かれていないと、フロントエンドやSDK生成で同じ条件を再利用できません。入力制約は仕様にも実装にも置きます。

レビュー checklist

  • リソース名は複数形の名詞で統一したか
  • メソッドはGETPOSTPATCHDELETEの意味に合っているか
  • 成功時の200201204を使い分けたか
  • 400401403404409500のエラーJSONが同じ形か
  • 一覧APIにlimitafternextCursorhasMoreがあるか
  • limitの最大値と既定値を決めたか
  • 作成系POSTの再送で重複作成しないか
  • 破壊的変更を/v2に分ける基準を書いたか
  • OpenAPIにバリデーション条件を記述したか
  • Claude Codeに渡す前に、表とOpenAPIの内容が一致しているか

REST API設計は、きれいなURLを考える作業ではなく、利用者との約束を文章と機械可読な仕様に落とす作業です。Claude Codeは実装速度を上げますが、どの約束を守るべきかは人間が先に決めます。

実際に試した結果、Masaの注文APIではOpenAPI、エンドポイント表、エラーJSONを先に固定してからClaude Codeへ実装を頼んだほうが、レビューで出る手戻りが明確に減りました。特にIdempotency-Keyとカーソルページングを設計段階で入れたことで、あとから二重注文対策や一覧の取りこぼし対策を差し込む必要がなくなりました。

#claude-code #rest-api #openapi #typescript #backend
無料

無料PDF: Claude Code はじめてのチートシート

まずは無料PDFで基本コマンドと最初の使い方をまとめて確認してください。登録後はそのままテンプレート集や導入相談にも進めます。

スパムは送りません。登録情報は厳重に管理します。

Claude Codeを仕事で使える形にしませんか?

無料PDFで基礎を固めたあと、すぐ使えるテンプレート集で試し、必要なら業務自動化や導入相談まで進められます。

Masa

この記事を書いた人

Masa

Claude Codeの実務活用、導入設計、収益導線改善を検証しているエンジニア。10言語の技術メディアを運営中。

PR

関連書籍・参考図書

この記事のテーマに関連する書籍を楽天ブックスで探せます。

※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。