api-route-design
Use when designing RESTful API endpoints in FastAPI or Python projects. Triggers for: creating GET/POST/PUT/DELETE endpoints, request validation with Pydantic, response formatting with JSON schemas, status code selection, pagination, filtering, or sorting parameters. NOT for: GraphQL APIs, WebSocket handlers, or non-RESTful endpoints.
下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o api-route-design.zip https://jpskill.com/download/17372.zip && unzip -o api-route-design.zip && rm api-route-design.zip
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/17372.zip -OutFile "$d\api-route-design.zip"; Expand-Archive "$d\api-route-design.zip" -DestinationPath $d -Force; ri "$d\api-route-design.zip"
完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。
💾 手動でダウンロードしたい(コマンドが難しい人向け)
- 1. 下の青いボタンを押して
api-route-design.zipをダウンロード - 2. ZIPファイルをダブルクリックで解凍 →
api-route-designフォルダができる - 3. そのフォルダを
C:\Users\あなたの名前\.claude\skills\(Win)または~/.claude/skills/(Mac)へ移動 - 4. Claude Code を再起動
⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。
🎯 このSkillでできること
下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。
📦 インストール方法 (3ステップ)
- 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
- 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
- 3. 展開してできたフォルダを、ホームフォルダの
.claude/skills/に置く- · macOS / Linux:
~/.claude/skills/ - · Windows:
%USERPROFILE%\.claude\skills\
- · macOS / Linux:
Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。
詳しい使い方ガイドを見る →- 最終更新
- 2026-05-18
- 取得日時
- 2026-05-18
- 同梱ファイル
- 2
📖 Skill本文(日本語訳)
※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。
API Route Design Skill
適切なバリデーション、レスポンスのフォーマット、および HTTP セマンティクスを備えた RESTful API の専門的な設計と実装。
クイックリファレンス
| Pattern | Example | Purpose |
|---|---|---|
| リソースのリスト | @router.get("/fees/", response_model=List[FeeOut]) |
コレクションの取得 |
| ID による取得 | @router.get("/fees/{fee_id}") |
単一リソースの取得 |
| 作成 | @router.post("/fees/", response_model=FeeOut, status_code=201) |
新規リソースの作成 |
| 更新 | @router.put("/fees/{fee_id}") |
リソースの完全な更新 |
| パッチ | @router.patch("/fees/{fee_id}") |
リソースの部分的な更新 |
| 削除 | @router.delete("/fees/{fee_id}", status_code=204) |
リソースの削除 |
URL の命名規則
/v1/{resource} # コレクションエンドポイント
/v1/{resource}/{id} # 単一リソースエンドポイント
/v1/{resource}/{id}/sub # ネストされたリソースエンドポイント
ルール:
- 小文字を使用し、複数単語の場合はハイフンを使用:
/student-feesであり、/studentFeesではない - コレクションには複数名詞を使用:
/usersであり、/userではない - HTTP メソッドをセマンティックに使用: GET (読み取り)、POST (作成)、PUT/PATCH (更新)、DELETE (削除)
HTTP ステータスコード
| Code | Usage | Example |
|---|---|---|
| 200 | OK | GET、PUT、PATCH の成功 |
| 201 | Created | POST の成功 (リソースが作成された) |
| 202 | Accepted | 非同期操作が開始された |
| 204 | No Content | DELETE の成功 |
| 400 | Bad Request | 無効な入力、バリデーション失敗 |
| 401 | Unauthorized | 認証がないか無効 |
| 403 | Forbidden | 認証済みだが認可されていない |
| 404 | Not Found | リソースが存在しない |
| 422 | Unprocessable Entity | バリデーションエラー (Pydantic) |
| 500 | Internal Server Error | 予期しないサーバーエラー |
リクエストのバリデーションパターン
パスパラメータ
from fastapi import APIRouter, HTTPException
from typing import Annotated
router = APIRouter()
@router.get("/fees/{fee_id}")
async def get_fee(fee_id: int):
fee = await get_fee_by_id(fee_id)
if not fee:
raise HTTPException(status_code=404, detail="Fee not found")
return fee
クエリパラメータ (ページネーション、フィルタリング、ソート)
@router.get("/fees/", response_model=List[FeeOut])
async def list_fees(
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000),
status: str | None = Query(None, pattern="^(pending|paid|overdue)$"),
sort_by: str = Query("created_at", enum=["created_at", "amount", "due_date"]),
sort_order: str = Query("desc", enum=["asc", "desc"]),
):
return await paginate_fees(
skip=skip,
limit=limit,
status=status,
sort_by=sort_by,
sort_order=sort_order,
)
リクエストボディ (Pydantic モデル)
from pydantic import BaseModel
from datetime import datetime
class FeeCreate(BaseModel):
student_id: int
amount: float = Field(..., gt=0)
due_date: datetime
description: str | None = None
class FeeUpdate(BaseModel):
amount: float | None = Field(None, gt=0)
status: str | None = Field(None, pattern="^(pending|paid|overdue)$")
due_date: datetime | None = None
@router.post("/fees/", response_model=FeeOut, status_code=201)
async def create_fee(fee_in: FeeCreate):
return await create_fee_db(fee_in)
@router.patch("/fees/{fee_id}", response_model=FeeOut)
async def update_fee(fee_id: int, fee_in: FeeUpdate):
return await update_fee_db(fee_id, fee_in)
レスポンスモデル
標準レスポンスエンベロープ
class FeeOut(BaseModel):
id: int
student_id: int
amount: float
status: str
created_at: datetime
due_date: datetime
class PaginatedResponse(BaseModel):
data: List[FeeOut]
total: int
skip: int
limit: int
has_more: bool
エラーレスポンス
class ErrorResponse(BaseModel):
error: str
detail: str | None = None
code: str | None = None
完全なエンドポイントの例
from fastapi import APIRouter, Depends, HTTPException, Query, status
from typing import List, Annotated
router = APIRouter(prefix="/v1/fees", tags=["fees"])
@router.get(
"/",
response_model=PaginatedResponse[FeeOut],
summary="List fees",
description="Retrieve a paginated list of fees with optional filtering.",
)
async def list_fees(
skip: Annotated[int, Query(0, ge=0)] = 0,
limit: Annotated[int, Query(100, ge=1, le=1000)] = 100,
status: Annotated[str | None, Query(pattern="^(pending|paid|overdue)$")] = None,
_current_user: User = Depends(get_current_user),
) -> PaginatedResponse[FeeOut]:
fees, total = await get_fees(
skip=skip, limit=limit, status=status, user=_current_user
)
return PaginatedResponse(
data=fees,
total=total,
skip=skip,
limit=limit,
has_more=(skip + limit) < total,
)
@router.get(
"/{fee_id}",
response_model=FeeOut,
responses={404: {"model": ErrorResponse}},
)
async def get_fee(
fee_id: int,
_current_user: User = Depends(get_current_user),
) -> FeeOut:
fee = await get_fee_by_id(fee_id)
if not fee:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Fee not found",
)
return fee
@router.post(
"/",
response_model=FeeOut,
status_code=status.HTTP_201_CREATED,
responses={400: {"model": ErrorResponse}},
)
async def create_fee(
fee_in: FeeCreate,
_current_user: User = Depends(get_current_user),
) -> FeeOut:
return await create_fee_db(fee_in, created_by=_current_user.id)
他の Skill との統合
| Skill | Integration Point |
|---|---|
@fastapi-app |
main.py でのルーター登録 |
@sqlmodel-crud |
エンドポイントでのデータベース操作 |
@jwt-auth |
保護されたルートのための Depends(get_current_user) |
@api-client |
この API 設計のコンシューマー |
品質チェックリスト
- [ ] ページネーション標準:
skip/limitをhas_moreインジケーターとともに使用 - [ ] フィルタリング
📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開
API Route Design Skill
Expert design and implementation of RESTful APIs with proper validation, response formatting, and HTTP semantics.
Quick Reference
| Pattern | Example | Purpose |
|---|---|---|
| List resource | @router.get("/fees/", response_model=List[FeeOut]) |
Retrieve collection |
| Get by ID | @router.get("/fees/{fee_id}") |
Retrieve single resource |
| Create | @router.post("/fees/", response_model=FeeOut, status_code=201) |
Create new resource |
| Update | @router.put("/fees/{fee_id}") |
Full resource update |
| Patch | @router.patch("/fees/{fee_id}") |
Partial resource update |
| Delete | @router.delete("/fees/{fee_id}", status_code=204) |
Remove resource |
URL Naming Conventions
/v1/{resource} # Collection endpoints
/v1/{resource}/{id} # Single resource endpoints
/v1/{resource}/{id}/sub # Nested resource endpoints
Rules:
- Use lowercase, hyphens for multi-word:
/student-feesnot/studentFees - Use plural nouns for collections:
/usersnot/user - Use HTTP methods semantically: GET (read), POST (create), PUT/PATCH (update), DELETE (remove)
HTTP Status Codes
| Code | Usage | Example |
|---|---|---|
| 200 | OK | Successful GET, PUT, PATCH |
| 201 | Created | Successful POST (resource created) |
| 202 | Accepted | Async operation started |
| 204 | No Content | Successful DELETE |
| 400 | Bad Request | Invalid input, validation failed |
| 401 | Unauthorized | Missing or invalid auth |
| 403 | Forbidden | Authenticated but not authorized |
| 404 | Not Found | Resource doesn't exist |
| 422 | Unprocessable Entity | Validation errors (Pydantic) |
| 500 | Internal Server Error | Unexpected server error |
Request Validation Patterns
Path Parameters
from fastapi import APIRouter, HTTPException
from typing import Annotated
router = APIRouter()
@router.get("/fees/{fee_id}")
async def get_fee(fee_id: int):
fee = await get_fee_by_id(fee_id)
if not fee:
raise HTTPException(status_code=404, detail="Fee not found")
return fee
Query Parameters (Pagination, Filtering, Sorting)
@router.get("/fees/", response_model=List[FeeOut])
async def list_fees(
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000),
status: str | None = Query(None, pattern="^(pending|paid|overdue)$"),
sort_by: str = Query("created_at", enum=["created_at", "amount", "due_date"]),
sort_order: str = Query("desc", enum=["asc", "desc"]),
):
return await paginate_fees(
skip=skip,
limit=limit,
status=status,
sort_by=sort_by,
sort_order=sort_order,
)
Request Body (Pydantic Models)
from pydantic import BaseModel
from datetime import datetime
class FeeCreate(BaseModel):
student_id: int
amount: float = Field(..., gt=0)
due_date: datetime
description: str | None = None
class FeeUpdate(BaseModel):
amount: float | None = Field(None, gt=0)
status: str | None = Field(None, pattern="^(pending|paid|overdue)$")
due_date: datetime | None = None
@router.post("/fees/", response_model=FeeOut, status_code=201)
async def create_fee(fee_in: FeeCreate):
return await create_fee_db(fee_in)
@router.patch("/fees/{fee_id}", response_model=FeeOut)
async def update_fee(fee_id: int, fee_in: FeeUpdate):
return await update_fee_db(fee_id, fee_in)
Response Models
Standard Response Envelope
class FeeOut(BaseModel):
id: int
student_id: int
amount: float
status: str
created_at: datetime
due_date: datetime
class PaginatedResponse(BaseModel):
data: List[FeeOut]
total: int
skip: int
limit: int
has_more: bool
Error Response
class ErrorResponse(BaseModel):
error: str
detail: str | None = None
code: str | None = None
Complete Endpoint Example
from fastapi import APIRouter, Depends, HTTPException, Query, status
from typing import List, Annotated
router = APIRouter(prefix="/v1/fees", tags=["fees"])
@router.get(
"/",
response_model=PaginatedResponse[FeeOut],
summary="List fees",
description="Retrieve a paginated list of fees with optional filtering.",
)
async def list_fees(
skip: Annotated[int, Query(0, ge=0)] = 0,
limit: Annotated[int, Query(100, ge=1, le=1000)] = 100,
status: Annotated[str | None, Query(pattern="^(pending|paid|overdue)$")] = None,
_current_user: User = Depends(get_current_user),
) -> PaginatedResponse[FeeOut]:
fees, total = await get_fees(
skip=skip, limit=limit, status=status, user=_current_user
)
return PaginatedResponse(
data=fees,
total=total,
skip=skip,
limit=limit,
has_more=(skip + limit) < total,
)
@router.get(
"/{fee_id}",
response_model=FeeOut,
responses={404: {"model": ErrorResponse}},
)
async def get_fee(
fee_id: int,
_current_user: User = Depends(get_current_user),
) -> FeeOut:
fee = await get_fee_by_id(fee_id)
if not fee:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Fee not found",
)
return fee
@router.post(
"/",
response_model=FeeOut,
status_code=status.HTTP_201_CREATED,
responses={400: {"model": ErrorResponse}},
)
async def create_fee(
fee_in: FeeCreate,
_current_user: User = Depends(get_current_user),
) -> FeeOut:
return await create_fee_db(fee_in, created_by=_current_user.id)
Integration with Other Skills
| Skill | Integration Point |
|---|---|
@fastapi-app |
Router registration in main.py |
@sqlmodel-crud |
Database operations in endpoints |
@jwt-auth |
Depends(get_current_user) for protected routes |
@api-client |
Consumer of this API design |
Quality Checklist
- [ ] Pagination standard: Use
skip/limitwithhas_moreindicator - [ ] Filtering: Query params for common filter fields
- [ ] Sorting:
sort_byandsort_orderparameters - [ ] Status codes: 201 for POST, 204 for DELETE, 404 for not found
- [ ] Response models: All endpoints use
response_model - [ ] Documentation:
summaryanddescriptionfor OpenAPI - [ ] Error handling: Consistent error response format
Pagination Standard
@router.get("/items/", response_model=PaginatedResponse[ItemOut])
async def list_items(
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000),
) -> PaginatedResponse[ItemOut]:
items, total = await get_items(skip=skip, limit=limit)
return PaginatedResponse(
data=items,
total=total,
skip=skip,
limit=limit,
has_more=(skip + limit) < total,
)
Filtering & Sorting Standard
@router.get("/items/")
async def list_items(
# Filtering
category: str | None = None,
status: str | None = Query(None, pattern="^(active|inactive)$"),
min_amount: float | None = Query(None, ge=0),
# Sorting
sort_by: str = Query("created_at", enum=["created_at", "amount", "name"]),
sort_order: str = Query("desc", enum=["asc", "desc"]),
):
return await get_items(
filters={"category": category, "status": status, "min_amount": min_amount},
order_by=f"{sort_order} {sort_by.lstrip('-')}",
) 同梱ファイル
※ ZIPに含まれるファイル一覧。`SKILL.md` 本体に加え、参考資料・サンプル・スクリプトが入っている場合があります。
- 📄 SKILL.md (7,795 bytes)
- 📎 scripts/verify.py (1,902 bytes)