plugins/powerups/skills/self-documenting-apis/SKILL.md
Use when building or refactoring FastAPI endpoints. Ensures endpoints have proper docstrings, response models, and request models so auto-generated docs (Swagger/ReDoc) are the single source of truth — no separate API reference file to maintain.
npx skillsauth add jackyliang/powerups self-documenting-apisInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
FastAPI auto-generates OpenAPI docs at /docs (Swagger UI) and /redoc (ReDoc). When endpoints have proper docstrings and typed models, these docs are comprehensive enough to replace any hand-written API reference. The goal: never maintain a separate api-reference.md — the code IS the documentation.
Apply this skill when:
Skip when:
The docstring becomes the endpoint description in Swagger UI. Write it for the developer consuming your API.
@router.post("/v1/connections/oauth")
async def create_oauth_session(
body: OAuthSessionRequest,
developer: CurrentDeveloper,
):
"""Start OAuth flow for an end user.
Creates a Nango Connect Session and returns a URL to redirect the end user to.
The end user completes OAuth in their browser, then call POST /v1/connections/confirm.
"""
Rules:
Never return raw dicts. Define a Pydantic model so the response schema appears in docs.
# Bad — docs show no response schema
@router.get("/v1/connections/{id}")
async def get_connection(...):
return {"id": conn.id, "status": conn.status}
# Good — docs show full response schema with field descriptions
class ConnectionResponse(BaseModel):
id: str
end_user_id: str
provider: str
status: str = Field(description="Connection status: active, error, deleting")
created_at: datetime
@router.get("/v1/connections/{id}", response_model=ConnectionResponse)
async def get_connection(...) -> ConnectionResponse:
...
Key points:
response_model= on the decorator so FastAPI validates AND documents the responseField(description=...) for non-obvious fieldsresponse_model=list[ConnectionResponse]class SyncRequest(BaseModel):
connection_id: str = Field(description="UUID of the connection to sync")
resources: list[str] = Field(
description="Resources to sync (e.g., ['tickets', 'articles'])"
)
Use Field for:
ge=1, le=1000)Declare non-200 status codes so they appear in docs:
@router.post(
"/v1/syncs/{id}/trigger",
status_code=202,
responses={409: {"description": "Sync already running"}},
)
async def trigger_sync(...):
"""Trigger a sync run in the background."""
Group related endpoints with tags so Swagger UI organizes them into sections:
router = APIRouter(tags=["Connections"])
This replaces the need for section headers in a manual API reference.
For list endpoints, use a consistent wrapper:
class PaginatedResponse(BaseModel, Generic[T]):
items: list[T]
total: int
limit: int
offset: int
This documents the pagination contract once, consistently.
After creating or modifying endpoints, check the auto-docs:
/docs (Swagger UI) or /redocIf a consumer can't understand the endpoint from /docs alone, the docstring or model is missing something.
1. Docstring — what it does, context, side effects
2. Response — Pydantic model with Field(description=...)
3. Request — Pydantic model with Field(description=...)
4. Status — response_model=, status_code=, responses={}
5. Tags — APIRouter(tags=["Section"])
6. Verify — check /docs after changes
| Don't | Do |
|-------|-----|
| Return raw dicts | Define a response model |
| Write a separate api-reference.md | Let FastAPI generate docs from code |
| Document fields in the docstring | Use Field(description=...) on the model |
| Skip the docstring ("it's obvious") | Write it — /docs is your public contract |
| Use Any or dict as response type | Define a typed model, even for simple responses |
| Maintain two sources of truth | Code is the docs — update the code, docs update automatically |
development
Run PM-grade discovery before building any user-facing feature — problem statement, jobs-to-be-done, core flow, decision matrix. Output is a short brief with open decisions surfaced as explicit questions. Invoked by plan-driven-development and give-me-five.
testing
Ultra-short replies — answer a quick question, draft a short text/social post, or draft a short email. No preamble, no offers to elaborate, drafts under 480 characters (280 for X), never em-dashes.
development
Reconcile shipped code with the plan in both directions — additive drift (unplanned work that landed) and subtractive drift (dead files, stale TODOs, completed deferred items). Run as part of PDD's post-completion audit, before /simplify.
data-ai
Generate 5 meaningfully distinct UI/UX variants of the same screen in parallel (one subagent each), reachable via ?style=1...5, so the user can compare and pick. Calling again on a chosen style refines within that direction.