--- name: api-design description: > Designs RESTful APIs, typed schema membranes, request/response validation contracts, and API documentation. Use when creating or modifying API routes, defining request/response schemas, designing error responses, or standardizing API contracts. concerns: [security, system-one-balance] --- # API Design & Membrane Architecture Governs API design and boundary membranes, ensuring typed validation, input clamping, predictable HTTP semantics, and consistent structured error contracts. --- ## Phase 0: Setup and Protocols 1. **Membrane Principles**: - **Boundary Isolation**: Route handlers serve as the transport membrane. Core business services and data access layers remain transport-agnostic (never import web framework request objects or HTTP status codes into domain logic). - **Contract-First Validation**: Define typed schemas (e.g. Pydantic, Zod, TypeScript interfaces) before implementing endpoint logic. - **Dependency Injection**: Inject database sessions, repositories, and authentication state into route handlers; avoid importing global singletons directly. --- ## Phase 1: Ingest and Check 1. **Identify Endpoint Requirements**: - Resource name, target path, HTTP method (`GET`, `POST`, `PUT`, `PATCH`, `DELETE`). - Expected input payload, query parameters, route path variables, and headers. - Response model, status codes (200, 201, 204), and error failure modes. 2. **Input Boundary Clamping**: - Enforce explicit upper bounds on all strings (e.g. `min_length=1`, `max_length=200` for names; `max_length=50000` for content). - Enforce pagination limits: `1 <= limit <= 50` (or `100` max). - Constrain array inputs: clamp batch endpoints to maximum batch sizes. --- ## Phase 2: Processing (Core Logic) ### Step 1: Define Typed Contracts ```python # 1. Request & Response Schemas class ResourceCreateRequest(BaseModel): name: str = Field(..., min_length=1, max_length=200) description: str | None = Field(None, max_length=2000) class ResourceResponse(BaseModel): id: str name: str description: str | None = None created_at: datetime ``` ### Step 2: Implement Route Handler ```python # 2. Endpoint Implementation @router.post("/resources", response_model=ResourceResponse, status_code=201) async def create_resource( payload: ResourceCreateRequest, repo: ResourceRepository = Depends(get_resource_repo), ): entity = await repo.create(payload.model_dump()) return ResourceResponse.model_validate(entity) ``` ### Step 3: Enforce HTTP & Error Semantics - **HTTP Methods**: - `POST`: Resource creation / command execution (201 Created or 200 OK). - `GET`: Idempotent retrieval (200 OK). - `PUT`: Full resource replacement (200 OK). - `PATCH`: Partial state update (200 OK). - `DELETE`: Resource removal (204 No Content). - **Structured Error Responses**: - Return standardized error objects (e.g. RFC 7807 problem details or structured error payloads): ```json { "error": { "code": "RESOURCE_NOT_FOUND", "message": "Resource with id 'xyz' was not found", "details": {} } } ``` - Standard status codes: 400 (Validation), 401 (Unauthenticated), 403 (Unauthorized), 404 (Not Found), 409 (Conflict). - **Timezone Invariant**: Always serialize timestamps in UTC using ISO-8601 (`YYYY-MM-DDTHH:MM:SSZ`). --- ## Phase 3: The System One Balance Pass - **Augment, Don't Handcuff**: - Do NOT over-complicate simple internal endpoints with bloated enterprise middleware. - Keep route handlers thin: route logic should unpack requests, call services/repos, and map to response models. - Fail open on safe internal queries; enforce rigorous clamping at external boundary layers. --- ## Phase 4: Output Execution & Verification 1. Verify: - Route docstrings populate OpenAPI / Swagger schemas accurately. - All response models are strongly typed without bare `dict` or `Any` escapes. - Input clamping fields prevent memory exhaustion and DoS. 2. Deliver the schema definition, route implementation, and corresponding test case.