design-api · diff

git:20260923.68c095e to git:20261006.39320f8

31 added, 176 removed. Audit A to A.

---
name: design-api
description: >
Design REST and GraphQL APIs: naming, versioning, error shapes, auth. Use
when "design an API", "create endpoints", "structure my API responses",
"plan API architecture", "REST vs GraphQL", or "API contract".
license: MIT
effort: high
---
# API Design Skill
**Degree of freedom: MIXED.** Resource model, error shape, and REST vs
GraphQL `[HIGH freedom]`; pre-design docs/schema/grep
`[LOW freedom — run exactly]`.
Design clean, consistent, and developer-friendly APIs.
## How to reason
1. **Survey** — existing docs, schema, and similar endpoints
2. **Model** — resources, relations, REST vs GraphQL
3. **Contract** — paths, statuses, error shape, auth, pagination
4. **Check** — naming matches this repo; no duplicate endpoint
## Worked example
> **Survey:** `orders` has `user_id`; no `GET /users/:id/orders`; clients use `useQuery`.
> **Model:** order is a nested user resource, not a `/getUserOrders` RPC.
> **Contract:** `GET /users/:id/orders` → `{ data, meta }`; shared `{ error: { code, message, details } }`.
> **Check:** plural kebab-case; list paginated; 401/404/422 only from the status table.
## Self-critique before reporting
- **Pre-design stated** — docs, schema, and similar endpoints were checked out loud
- **One error shape** — every failure uses `{ error: { code, message, details } }`
- **Lists paginate** — no unbounded `GET /resources`
- **Right owner** — live 4xx/5xx repro → `debug-fe-be-integration`; product scope still fuzzy → `design-prd`
## Pre-design checks [LOW freedom — run exactly]
**Before designing any API:**
### 1. Check Existing API Documentation
- The running backend's docs route (`/api-docs`, `/docs`, `/swagger`, `/openapi.json`)
- The repo's API README or naming-conventions doc (grep `naming` under `docs/` and `src/api/`)
### 2. Verify Database Schema
Use Supabase MCP to understand existing data structure:
```sql
- -- Check table schema
SELECT column_name, data_type, is_nullable
FROM information_schema.columns WHERE table_name = 'your_table';
-
- -- Check enum values
- SELECT enum_range(NULL::your_enum_name);
-
- -- Check foreign keys
- SELECT tc.constraint_name, kcu.column_name, ccu.table_name AS foreign_table
- FROM information_schema.table_constraints tc
- JOIN information_schema.key_column_usage kcu ON tc.constraint_name = kcu.constraint_name
- JOIN information_schema.constraint_column_usage ccu ON tc.constraint_name = ccu.constraint_name
- WHERE tc.table_name = 'your_table' AND tc.constraint_type = 'FOREIGN KEY';
```
+ Also check enum values (`enum_range`) and foreign keys (`information_schema.table_constraints`):
+ [references/examples.md](references/examples.md) §Pre-design schema probes.
+
### 3. Check for Existing Endpoints
Use `Grep` to search for similar endpoints already implemented:
```
Grep: "router.get|router.post" to find existing route patterns
Grep: "useQuery|useMutation" to find existing frontend integrations
```
### 4. Verification Statement (REQUIRED)
Before designing, state:
```
"Pre-design check:
- Existing API docs reviewed: [YES/NO]
- Database schema verified: [tables/enums checked]
- Similar endpoints found: [list or none]
- Naming conventions confirmed: [YES/NO]"
```
---
## REST API Design [HIGH freedom]
### URL Structure
```
GET /resources # List
GET /resources/:id # Get one
POST /resources # Create
PUT /resources/:id # Replace
PATCH /resources/:id # Update
DELETE /resources/:id # Delete
```
### Naming Conventions
| Do | Don't |
|----|-------|
| `/users` | `/getUsers`, `/user-list` |
| `/users/:id` | `/user/:id`, `/users/get/:id` |
| `/users/:id/orders` | `/getUserOrders` |
| Plural nouns | Verbs, singular |
| kebab-case | camelCase, snake_case |
### Examples
```
GET /users # List users
GET /users/123 # Get user 123
GET /users/123/orders # User's orders
GET /users/123/orders/456 # Specific order
POST /users/123/orders # Create order for user
```
---
## Request/Response Format [LOW freedom — run this shape]
- ### Request Body
-
- ```json
- {
- "name": "John Doe",
- "email": "john@example.com",
- "role": "admin"
- }
- ```
-
- ### Successful Response
-
- ```json
- {
- "data": {
- "id": "123",
- "name": "John Doe",
- "email": "john@example.com",
- "createdAt": "2024-01-15T10:30:00Z"
- }
- }
- ```
-
- ### List Response (with pagination)
+ - **Request body** — flat JSON, camelCase keys, no envelope.
+ - **Success** — `{ "data": { ... } }` for one resource; `{ "data": [...], "meta": { total, page, perPage, totalPages } }` for lists.
+ - **Error** — one shape everywhere: `{ "error": { "code", "message", "details" } }`; `code` is machine-readable, `details` is a per-field list.
```json
{
- "data": [
- { "id": "1", "name": "John" },
- { "id": "2", "name": "Jane" }
- ],
- "meta": {
- "total": 100,
- "page": 1,
- "perPage": 20,
- "totalPages": 5
- }
+ "error": {
+ "code": "VALIDATION_ERROR",
+ "message": "Invalid input data",
+ "details": [
+ { "field": "email", "message": "Invalid email format" }
+ ]
+ }
}
```
- ### Error Response
-
- ```json
- {
- "error": {
- "code": "VALIDATION_ERROR",
- "message": "Invalid input data",
- "details": [
- { "field": "email", "message": "Invalid email format" },
- { "field": "name", "message": "Name is required" }
- ]
- }
- }
- ```
+ Request, single, and list bodies in full: [references/examples.md](references/examples.md) §Request body through §List response.
---
## HTTP Status Codes [LOW freedom — use this table]
### Success (2xx)
| Code | When to Use |
|------|-------------|
| `200 OK` | GET, PUT, PATCH success |
| `201 Created` | POST created new resource |
| `204 No Content` | DELETE success, no body |
### Client Errors (4xx)
| Code | When to Use |
|------|-------------|
| `400 Bad Request` | Invalid request body |
| `401 Unauthorized` | Not authenticated |
| `403 Forbidden` | Authenticated but not allowed |
| `404 Not Found` | Resource doesn't exist |
| `409 Conflict` | Resource conflict (duplicate) |
| `422 Unprocessable` | Validation failed |
| `429 Too Many` | Rate limited |
### Server Errors (5xx)
| Code | When to Use |
|------|-------------|
| `500 Internal Error` | Unexpected server error |
| `502 Bad Gateway` | Upstream service failed |
| `503 Unavailable` | Service temporarily down |
---
## Query Parameters
- ### Filtering
-
- ```
- GET /users?role=admin
- GET /users?role=admin&status=active
- GET /orders?createdAfter=2024-01-01
- ```
-
- ### Sorting
-
- ```
- GET /users?sort=name # Ascending
- GET /users?sort=-createdAt # Descending (prefix with -)
- GET /users?sort=role,-name # Multiple fields
- ```
-
- ### Pagination
-
- ```
- GET /users?page=2&perPage=20
- GET /users?offset=40&limit=20
- GET /users?cursor=abc123 # Cursor-based
- ```
-
- ### Field Selection
+ - **Filtering** — `?role=admin&status=active`, date bounds as `?createdAfter=2024-01-01`.
+ - **Sorting** — `?sort=name`; `-` prefix for descending; comma-separated for multiple (`?sort=role,-name`).
+ - **Pagination** — `?page=2&perPage=20` by default; `?cursor=abc123` for large or live lists; never an unbounded list.
+ - **Field selection** — `?fields=id,name,email`; relations via `?include=orders,profile`.
- ```
- GET /users?fields=id,name,email
- GET /users?include=orders,profile
- ```
+ Example URLs for each: [references/examples.md](references/examples.md) §Query parameters.
---
## Versioning
- ### URL Path (Recommended)
-
- ```
- GET /v1/users
- GET /v2/users
- ```
-
- ### Header
-
- ```
- GET /users
- Accept: application/vnd.api+json;version=2
- ```
+ Default: URL path (`GET /v1/users`, `GET /v2/users`). Header versioning
+ (`Accept: application/vnd.api+json;version=2`) is the alternative:
+ [references/examples.md](references/examples.md) §Versioning.
---
## Authentication
- ### Bearer Token
-
- ```
- Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
- ```
-
- ### API Key
-
- ```
- X-API-Key: your-api-key
- # or
- ?apiKey=your-api-key
- ```
+ Default: `Authorization: Bearer <token>`. API key via `X-API-Key` header (or `?apiKey=`) is the
+ alternative for server-to-server callers: [references/examples.md](references/examples.md) §Authentication.
---
## Common Patterns
- ### Bulk Operations
-
- ```
- POST /users/bulk
- {
- "create": [{ "name": "John" }, { "name": "Jane" }],
- "update": [{ "id": "1", "name": "Updated" }],
- "delete": ["2", "3"]
- }
- ```
-
- ### Search
-
- ```
- POST /users/search
- {
- "query": "john",
- "filters": { "role": "admin" },
- "sort": { "field": "name", "order": "asc" }
- }
- ```
-
- ### Actions (non-CRUD)
+ - **Bulk operations** — `POST /users/bulk` with `{ create: [...], update: [...], delete: [ids] }`.
+ - **Search** — `POST /users/search` with `{ query, filters, sort }` when the criteria outgrow query strings.
+ - **Actions (non-CRUD)** — a verb sub-resource under the noun: `POST /orders/123/cancel`, `POST /users/123/verify-email`, `POST /payments/123/refund`.
- ```
- POST /orders/123/cancel
- POST /users/123/verify-email
- POST /payments/123/refund
- ```
+ Bodies for bulk and search: [references/examples.md](references/examples.md) §Bulk operations, §Search.
---
## API Design Checklist [LOW freedom — do not skip]
### Consistency
- [ ] Consistent naming conventions
- [ ] Consistent response format
- [ ] Consistent error format
- [ ] Consistent pagination
### Usability
- [ ] Intuitive URLs
- [ ] Clear documentation
- [ ] Meaningful error messages
- [ ] Sensible defaults
### Security
- [ ] Authentication required
- [ ] Authorization checked
- [ ] Input validation
- [ ] Rate limiting
### Performance
- [ ] Pagination for lists
- [ ] Field selection available
- [ ] Efficient queries
- [ ] Caching headers
---
## Documentation Template
- ```markdown
- ## Create User
-
- Create a new user account.
-
- **Endpoint:** `POST /users`
-
- **Authentication:** Required (Bearer token)
-
- **Request Body:**
- | Field | Type | Required | Description |
- |-------|------|----------|-------------|
- | name | string | Yes | User's full name |
- | email | string | Yes | Valid email address |
- | role | string | No | User role (default: "user") |
-
- **Response:** `201 Created`
- \`\`\`json
- {
- "data": {
- "id": "123",
- "name": "John Doe",
- "email": "john@example.com",
- "role": "user",
- "createdAt": "2024-01-15T10:30:00Z"
- }
- }
- \`\`\`
-
- **Errors:**
- - `400` - Invalid request body
- - `409` - Email already exists
- - `422` - Validation failed
- ```
+ Per endpoint: title, one-line purpose, `**Endpoint:**`, `**Authentication:**`, request-body
+ table (field, type, required, description), response status with a JSON example, and the error
+ list. Full template: [references/examples.md](references/examples.md) §Documentation template.