Agent Skills: API Route Design Skill

|

UncategorizedID: aiskillstore/marketplace/api-route-design

Install this agent skill to your local

pnpm dlx add-skill https://github.com/aiskillstore/marketplace/tree/HEAD/skills/awais68/api-route-design

Skill Files

Browse the full folder contents for api-route-design.

Download Skill

Loading file tree…

skills/awais68/api-route-design/SKILL.md

Skill Metadata

Name
api-route-design
Description
|

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-fees not /studentFees
  • Use plural nouns for collections: /users not /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/limit with has_more indicator
  • [ ] Filtering: Query params for common filter fields
  • [ ] Sorting: sort_by and sort_order parameters
  • [ ] Status codes: 201 for POST, 204 for DELETE, 404 for not found
  • [ ] Response models: All endpoints use response_model
  • [ ] Documentation: summary and description for 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('-')}",
    )