api
npx machina-cli add skill mrsknetwork/supernova/api --openclawAPI Engineering
Default Stack (Ask First)
Before applying anything, ask:
"Can I use FastAPI for this API? And should I use REST or GraphQL? I'll choose based on your app's complexity."
API Style Decision Rule:
- Use FastAPI REST when: the app is primarily CRUD, data is flat or lightly relational, a single known client (your own frontend) consumes it.
- Use Strawberry GraphQL when: multiple clients consume different data shapes, entities are deeply relational (User -> Orders -> Products -> Reviews), or a mobile client needs field-level data efficiency.
If unsure, default to REST. Migrating from REST to GraphQL is less painful than over-engineering a GraphQL schema for a simple CRUD app.
Progressive Disclosure
- Load
references/fastapi-rest.mdfor advanced REST patterns (pagination, filtering, OpenAPI customization). - Load
references/graphql-strawberry.mdfor GraphQL schema, resolver, and dataloader patterns.
SOP: FastAPI REST Implementation
Step 1 - Contract-First Design
Define the OpenAPI schema before writing routes. Describe the endpoint in terms of:
- HTTP method and path (e.g.,
POST /api/v1/users/{id}/avatar) - Request body Pydantic model
- Response Pydantic model
- Status codes and error cases
FastAPI will auto-generate the OpenAPI spec from your code. Treat the generated spec as the contract - do not break it without a version bump.
Step 2 - Standard Response Envelopes
All endpoints return a consistent envelope. Never return a raw model.
Success:
# schemas/common.py
from typing import Generic, TypeVar
from pydantic import BaseModel
T = TypeVar("T")
class SuccessResponse(BaseModel, Generic[T]):
data: T
meta: dict = {}
class PaginatedResponse(BaseModel, Generic[T]):
data: list[T]
meta: dict # {"total": 100, "page": 1, "per_page": 20}
Error:
class ErrorDetail(BaseModel):
code: str # machine-readable: "USER_NOT_FOUND"
message: str # human-readable: "User with id ... was not found"
class ErrorResponse(BaseModel):
error: ErrorDetail
Step 3 - Route Structure
# api/v1/users.py
from fastapi import APIRouter, Depends, status
from uuid import UUID
router = APIRouter(prefix="/users", tags=["Users"])
@router.post("", response_model=SuccessResponse[UserOut], status_code=status.HTTP_201_CREATED)
async def create_user(
body: UserCreate,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user), # auth gate
) -> SuccessResponse[UserOut]:
user = await user_service.create(body, db)
return SuccessResponse(data=user)
Routers are thin. All logic lives in the service layer.
Step 4 - Authentication Dependency
# dependencies/auth.py
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/token")
async def get_current_user(token: str = Depends(oauth2_scheme), db: AsyncSession = Depends(get_db)) -> User:
payload = decode_jwt(token) # raises if invalid/expired
user = await user_repo.get_by_id(db, UUID(payload["sub"]))
if not user:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid credentials")
return user
Inject Depends(get_current_user) on any route that requires authentication. Never check auth manually inside a route handler.
Step 5 - Rate Limiting
# main.py
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded
limiter = Limiter(key_func=get_remote_address)
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
# On route:
@router.post("")
@limiter.limit("10/minute")
async def create_user(request: Request, ...):
Apply stricter limits on auth endpoints (5/minute) and looser limits on read endpoints (60/minute).
Step 6 - Versioning
All routes live under /api/v1/. When breaking changes are required, create /api/v2/ routers. Never silently break existing contract. Deprecate with an X-Deprecated: true response header before removal.
SOP: Strawberry GraphQL Implementation
Step 1 - Schema-First Design
Define the GraphQL SDL conceptually before writing Python:
type User {
id: ID!
email: String!
orders: [Order!]!
}
type Query {
user(id: ID!): User
}
type Mutation {
createUser(input: CreateUserInput!): User!
}
Then implement in Strawberry:
import strawberry
from uuid import UUID
@strawberry.type
class UserType:
id: UUID
email: str
@strawberry.field
async def orders(self, info: strawberry.types.Info) -> list["OrderType"]:
return await info.context["order_loader"].load(self.id)
Step 2 - N+1 Prevention with Dataloaders
Every relationship field on a GraphQL type must use a dataloader:
from strawberry.dataloader import DataLoader
async def load_orders_by_user_id(user_ids: list[UUID]) -> list[list[Order]]:
# Single DB query for all user_ids
rows = await order_repo.get_by_user_ids(db, user_ids)
by_user = {uid: [] for uid in user_ids}
for row in rows:
by_user[row.user_id].append(row)
return [by_user[uid] for uid in user_ids]
order_loader = DataLoader(load_fn=load_orders_by_user_id)
Never resolve relationships with individual per-record DB queries.
Step 3 - Context and Auth
from strawberry.fastapi import GraphQLRouter
async def get_context(request: Request, db: AsyncSession = Depends(get_db)) -> dict:
return {"db": db, "user": await get_current_user_optional(request), "order_loader": DataLoader(...)}
graphql_app = GraphQLRouter(schema, context_getter=get_context)
Source
git clone https://github.com/mrsknetwork/supernova/blob/main/skills/api/SKILL.mdView on GitHub Overview
Build HTTP APIs in Python with FastAPI (REST) or Strawberry (GraphQL) using a contract-first mindset. Enforce standard response envelopes, authentication via dependency injection, and rate limiting. Decide REST vs GraphQL based on your app’s complexity and client needs.
How This Skill Works
Define the OpenAPI contract for REST or a GraphQL schema before coding routes, then implement thin routers that delegate to a service layer. Always return standardized envelopes (SuccessResponse, PaginatedResponse, or ErrorResponse) and inject authentication via Depends. Wire rate limiting into the app to protect endpoints.
When to Use It
- You are building a primarily CRUD API with a single frontend client and want a stable contract.
- Multiple clients require different data shapes or field-level data efficiency.
- Entities are deeply relational (e.g., User -> Orders -> Products) and GraphQL offers benefits.
- You want a contract-first OpenAPI/spec-driven approach to govern changes.
- You need consistent envelopes, authentication, and rate limiting across endpoints.
Quick Start
- Step 1: Contract-First Design - Define the OpenAPI schema for REST or a GraphQL schema before coding routes.
- Step 2: Standard Response Envelopes - Implement SuccessResponse, PaginatedResponse, and ErrorResponse and return them from endpoints.
- Step 3: Route Structure and DI - Create thin routers, wire services, and use Depends for authentication plus a rate limiter in main.py.
Best Practices
- Ask early whether to use FastAPI REST or Strawberry GraphQL based on app complexity.
- Treat the OpenAPI or GraphQL schema as the contract and version it when breaking changes occur.
- Return a standard envelope for all endpoints: Success or Paginated on success, Error on failure.
- Inject authentication with Depends (e.g., get_current_user) rather than in-route logic.
- Configure a centralized rate limiter to guard endpoints and avoid hot spots.
Example Use Cases
- REST user management service built with FastAPI returning standard envelopes.
- GraphQL product catalog implemented with Strawberry for multiple client types.
- Multi-client analytics API that leverages field-level data shapes via GraphQL.
- Authenticated user settings endpoints protected by DI-based auth and rate limits.
- OpenAPI-first integration service that coordinates with external contracts.