API 仕様書の役割

API 仕様書は、バックエンドとフロントエンド、あるいはサービス間の「契約書」です。 仕様書が存在しない・または不完全な状態では、次のような問題が頻発します。

  • フロントエンドとバックエンドの実装が食い違い、結合テストで発覚して手戻りが発生する
  • エラーレスポンスの形式が統一されておらず、クライアント側のエラーハンドリングが複雑化する
  • 認証方式の変更を口頭で伝達した結果、一部のクライアントが旧方式のまま動き続ける

💡 API 仕様は「コードより先に書く」のが理想

API First 設計では、OpenAPI の仕様ファイルを先に書き、コードをそこから自動生成します。実装前に仕様の齟齬を検出できるため、手戻りコストが大幅に削減されます。

エンドポイント定義

各エンドポイントには次の情報を定義します。

定義項目内容
HTTP メソッドGET / POST / PUT / PATCH / DELETEGET
パスリソースを表す URI/orders/{orderId}
概要(summary)1行の短い説明注文詳細を取得する
説明(description)詳細な説明・制約・注意事項キャンセル済み注文も含む。削除済みは404を返す。
operationId一意の操作識別子(コード生成に使用)getOrderById
タググループ化のためのカテゴリorders

REST の命名規則: リソース名は複数形の名詞(/orders)、操作は HTTP メソッドで表現します。 動詞を URI に含める(/getOrder)のは避けてください。

認証・認可

方式概要定義すべき内容
Bearer Token(JWT)Authorization ヘッダにトークンを付与トークン取得エンドポイント、有効期限、リフレッシュ方法
API Keyヘッダまたはクエリに API キーを付与キー名(例: X-API-Key)、取得方法、権限スコープ
OAuth 2.0外部サービス認可認可フロー(Authorization Code / Client Credentials)、スコープ一覧
Basic 認証Base64 エンコードされた ID:Password非推奨の旨と代替手段を明記する

⚠️ 認証が不要なエンドポイントも明示する

「認証必須」の記載がないエンドポイントは意図的に Public なのか、書き漏れなのか判断できません。security: [](認証不要)を明示的に記述してください。

リクエスト仕様

リクエストには パスパラメータクエリパラメータヘッダボディの4種類があります。

種別定義すべき内容
パスパラメータ
{id}
名前・型(string / integer)・必須・説明・例・バリデーション(最大値・パターン等)
クエリパラメータ
?limit=20
名前・型・必須/任意・デフォルト値・許容範囲・説明
リクエストヘッダ ヘッダ名・必須/任意・説明(Content-Type, Authorization, Accept-Language 等)
リクエストボディ Content-Type・JSON Schema(各フィールドの型・必須・バリデーション・example)

レスポンス仕様

各 HTTP ステータスコードに対するレスポンスを定義します。

ステータス意味どのエンドポイントに定義するか
200 OK正常取得・更新成功GET / PUT / PATCH
201 Createdリソース作成成功POST
204 No Content削除成功(ボディなし)DELETE
400 Bad Requestリクエストパラメータ不正全エンドポイント
401 Unauthorized認証失敗認証必須エンドポイント全て
403 Forbidden権限不足権限チェックがあるエンドポイント
404 Not Foundリソースが存在しないパスパラメータでリソース指定するエンドポイント
409 Conflict重複リソースの作成など競合POST(一意制約あり)
429 Too Many Requestsレート制限超過全エンドポイント
500 Internal Server Errorサーバ内部エラー全エンドポイント

エラーレスポンス定義

エラーレスポンスのフォーマットをプロジェクト全体で統一します。RFC 7807(Problem Details for HTTP APIs)が業界標準です。

JSON — RFC 7807 準拠エラーレスポンス例
{
  "type": "https://errors.example.com/validation-error",
  "title": "Validation Error",
  "status": 400,
  "detail": "リクエストパラメータに不備があります",
  "instance": "/orders/create",
  "errors": [
    {
      "field": "quantity",
      "message": "1 以上の整数を指定してください",
      "rejectedValue": -1
    },
    {
      "field": "productId",
      "message": "必須項目です",
      "rejectedValue": null
    }
  ],
  "traceId": "550e8400-e29b-41d4-a716-446655440000"
}
フィールド役割必須
typeエラー種別を示す URI(ドキュメントへのリンク)推奨
titleエラーの短い概要必須
statusHTTP ステータスコード(数値)必須
detail人間が読めるエラー詳細推奨
instanceエラーが発生したリソースの URI任意
traceIdログ追跡用の一意 ID推奨

レート制限・ページネーション

レート制限と ページネーションは仕様書で明示的に定義しないと、クライアント側が適切に実装できません。

定義項目内容・例
レート制限(制限値)100 リクエスト / 分 / API Key
レスポンスヘッダX-RateLimit-Limit / X-RateLimit-Remaining / X-RateLimit-Reset
429 時の Retry-AfterRetry-After: 30(秒数)または日時
ページネーション方式offset / cursor ベース
クエリパラメータlimit(デフォルト20、最大100)/ offset または cursor
レスポンスtotal(総件数)/ hasMore(次ページ有無)/ nextCursor

OpenAPI 形式のテンプレート

YAML — OpenAPI 3.1 テンプレート
openapi: "3.1.0"
info:
  title: Order Service API
  version: "2.3.0"
  description: |
    注文管理サービスの REST API 仕様書。
    認証には Bearer Token(JWT)を使用する。
  contact:
    name: Backend Team
    email: backend@example.com
  license:
    name: Private

servers:
  - url: https://api.example.com/v2
    description: 本番環境
  - url: https://staging-api.example.com/v2
    description: ステージング環境

security:
  - bearerAuth: []   # デフォルトで全エンドポイントに認証を適用

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
  schemas:
    Order:
      type: object
      required: [id, status, totalAmount, createdAt]
      properties:
        id:
          type: string
          format: uuid
          example: "550e8400-e29b-41d4-a716-446655440000"
        status:
          type: string
          enum: [pending, confirmed, shipped, delivered, cancelled]
        totalAmount:
          type: integer
          description: 合計金額(税込み、円)
          minimum: 0
        createdAt:
          type: string
          format: date-time
    Error:
      type: object
      required: [title, status, detail]
      properties:
        type:
          type: string
          format: uri
        title:
          type: string
        status:
          type: integer
        detail:
          type: string
        traceId:
          type: string

paths:
  /orders:
    get:
      summary: 注文一覧を取得する
      operationId: listOrders
      tags: [orders]
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            default: 20
            minimum: 1
            maximum: 100
          description: 1 ページの取得件数
        - name: cursor
          in: query
          schema:
            type: string
          description: ページネーション用カーソル
        - name: status
          in: query
          schema:
            type: string
            enum: [pending, confirmed, shipped, delivered, cancelled]
          description: ステータスでフィルタ
      responses:
        "200":
          description: 注文一覧取得成功
          headers:
            X-RateLimit-Limit:
              schema:
                type: integer
              description: 1分あたりの上限リクエスト数
            X-RateLimit-Remaining:
              schema:
                type: integer
              description: 残りリクエスト数
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Order'
                  hasMore:
                    type: boolean
                  nextCursor:
                    type: string
                    nullable: true
        "401":
          description: 認証失敗
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        "429":
          description: レート制限超過
          headers:
            Retry-After:
              schema:
                type: integer
              description: 次のリクエストまでの待機秒数
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

Python で API 仕様を自動検証する

① OpenAPI 仕様ファイルの整合性チェック

Python — openapi-spec-validator で仕様ファイルを検証
"""
openapi-spec-validator で仕様ファイルの構文・スキーマ整合性をチェックする。

インストール:
  pip install openapi-spec-validator pyyaml
"""
import sys
import yaml
from openapi_spec_validator import OpenAPIV31SpecValidator
from openapi_spec_validator.exceptions import OpenAPISpecValidatorError

def validate_spec(path: str) -> None:
    with open(path, encoding="utf-8") as f:
        spec = yaml.safe_load(f)

    validator = OpenAPIV31SpecValidator(spec)
    errors = list(validator.iter_errors())

    if not errors:
        print(f"✅ {path} — 仕様ファイルに問題はありません")
        # エンドポイント一覧を表示
        paths = spec.get("paths", {})
        print(f"\n登録エンドポイント数: {sum(len(v) for v in paths.values())}")
        for path_key, methods in sorted(paths.items()):
            for method, op in methods.items():
                op_id = op.get("operationId", "-")
                summary = op.get("summary", "-")
                print(f"  {method.upper():7} {path_key:<40} {op_id} — {summary}")
    else:
        print(f"❌ {path} — {len(errors)} 件のエラーが見つかりました\n")
        for i, err in enumerate(errors, 1):
            print(f"  [{i}] {err.message}")
            if err.path:
                print(f"       場所: {' > '.join(str(p) for p in err.path)}")
        sys.exit(1)

if __name__ == "__main__":
    spec_file = sys.argv[1] if len(sys.argv) > 1 else "openapi.yaml"
    validate_spec(spec_file)

② 実装と仕様のズレを検出(Flask 版)

Python — Flask ルートと OpenAPI 仕様のエンドポイント差分チェック
"""
Flask アプリに登録されたルートと OpenAPI 仕様のエンドポイントを比較し、
「仕様にあるが実装がない」「実装にあるが仕様がない」エンドポイントを検出する。

インストール:
  pip install pyyaml flask
"""
import re
import yaml
from pathlib import Path


def get_spec_endpoints(spec_path: str) -> set[tuple[str, str]]:
    """OpenAPI 仕様からエンドポイント(method, path)セットを返す。"""
    spec = yaml.safe_load(Path(spec_path).read_text(encoding="utf-8"))
    endpoints = set()
    for path, methods in spec.get("paths", {}).items():
        for method in methods:
            if method.lower() not in ("get", "post", "put", "patch", "delete"):
                continue
            # OpenAPI の {id} 形式を Flask の  形式に変換
            flask_path = re.sub(r"\{(\w+)\}", r"<\1>", path)
            endpoints.add((method.upper(), flask_path))
    return endpoints


def get_flask_endpoints(app) -> set[tuple[str, str]]:
    """Flask アプリからエンドポイントセットを返す。"""
    endpoints = set()
    for rule in app.url_map.iter_rules():
        for method in rule.methods or []:
            if method in ("HEAD", "OPTIONS"):
                continue
            endpoints.add((method, rule.rule))
    return endpoints


if __name__ == "__main__":
    # Flask アプリのインポート(実際の app モジュールに合わせて変更)
    from app import create_app  # type: ignore
    flask_app = create_app()

    spec_eps = get_spec_endpoints("openapi.yaml")
    flask_eps = get_flask_endpoints(flask_app)

    only_in_spec = spec_eps - flask_eps
    only_in_flask = flask_eps - spec_eps

    if only_in_spec:
        print("⚠️  仕様に定義があるが実装が存在しないエンドポイント:")
        for method, path in sorted(only_in_spec):
            print(f"   {method:7} {path}")

    if only_in_flask:
        print("\n⚠️  実装が存在するが仕様に定義がないエンドポイント:")
        for method, path in sorted(only_in_flask):
            print(f"   {method:7} {path}")

    if not only_in_spec and not only_in_flask:
        print("✅ 仕様と実装のエンドポイントは一致しています")

③ API レスポンスのスキーマ自動検証

Python — schemathesis で OpenAPI 仕様に基づく自動テスト
"""
schemathesis でステージング環境に対して OpenAPI 仕様に基づく
自動テストを実行し、仕様との不整合を検出する。

インストール:
  pip install schemathesis

実行:
  python -m schemathesis run openapi.yaml \
    --url https://staging-api.example.com/v2 \
    --auth-type bearer --auth "$TOKEN" \
    --checks all
"""

# pytest との組み合わせ(CI 用)
import schemathesis

schema = schemathesis.from_path(
    "openapi.yaml",
    base_url="https://staging-api.example.com/v2",
)

@schema.parametrize()
def test_api(case):
    """仕様書に定義されたすべてのエンドポイントに対してランダムな値でリクエストを送り、
    レスポンスが仕様のスキーマに準拠しているか検証する。"""
    response = case.call()
    case.validate_response(response)

まとめ

API 仕様書で定義すべき要素

□ エンドポイント(HTTP メソッド・パス・概要・operationId・タグ)
□ 認証・認可(方式・トークン取得方法・スコープ・認証不要エンドポイントの明示)
□ リクエスト(パスパラメータ・クエリ・ヘッダ・ボディの型/必須/バリデーション)
□ レスポンス(全 HTTP ステータスのスキーマ・example)
□ エラーレスポンス形式(RFC 7807 準拠・traceId・field errors)
□ レート制限(制限値・レスポンスヘッダ・Retry-After)
□ ページネーション(方式・パラメータ・レスポンス構造)
□ バージョニング戦略(URI バージョン v1/v2 または Accept ヘッダ)