Marketplace api-route-design
install
source · Clone the upstream repo
git clone https://github.com/aiskillstore/marketplace
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/aiskillstore/marketplace "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/awais68/api-route-design" ~/.claude/skills/aiskillstore-marketplace-api-route-design && rm -rf "$T"
manifest:
skills/awais68/api-route-design/SKILL.mdsource content
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 | | Retrieve collection |
| Get by ID | | Retrieve single resource |
| Create | | Create new resource |
| Update | | Full resource update |
| Patch | | Partial resource update |
| Delete | | 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:
not/student-fees/studentFees - Use plural nouns for collections:
not/users/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 |
|---|---|
| Router registration in |
| Database operations in endpoints |
| for protected routes |
| Consumer of this API design |
Quality Checklist
- Pagination standard: Use
/skip
withlimit
indicatorhas_more - Filtering: Query params for common filter fields
- Sorting:
andsort_by
parameterssort_order - Status codes: 201 for POST, 204 for DELETE, 404 for not found
- Response models: All endpoints use
response_model - Documentation:
andsummary
for OpenAPIdescription - 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('-')}", )