API Design Principles
When to trigger
- Designing a new API endpoint
- Adding routes to existing API
- Database schema work that affects API contract
- Keywords: "endpoint", "API", "route", "backend", "server", "REST", "GraphQL"
Core RESTful principles
Resource-oriented URLs
- Nouns, not verbs:
/users/123, not /getUser?id=123
- Pluralize resources:
/orders, not /order
- Nest only when expressing parent/child:
/users/:id/orders
- Max 2 levels deep — beyond that, use query params
HTTP methods (correct semantics)
| Method |
Use for |
Idempotent |
Safe |
| GET |
Read |
Yes |
Yes |
| POST |
Create |
No |
No |
| PUT |
Replace (full update) |
Yes |
No |
| PATCH |
Partial update |
No* |
No |
| DELETE |
Remove |
Yes |
No |
* PATCH can be idempotent depending on semantics.
Status codes (correct use)
- 200 OK — successful GET/PUT/PATCH with body
- 201 Created — successful POST creating resource
- 204 No Content — successful DELETE or action with no body
- 400 Bad Request — validation failure
- 401 Unauthorized — missing/invalid auth
- 403 Forbidden — authenticated but not authorized
- 404 Not Found — resource doesn't exist
- 409 Conflict — version mismatch, duplicate resource
- 422 Unprocessable Entity — semantic validation failure
- 429 Too Many Requests — rate limited
- 500 Internal Server Error — unhandled server fault
Response envelope
Consistent shape for all responses:
{
success: boolean
data: T | null
error: string | null
metadata?: { total, page, limit }
}
Endpoint design patterns
Pagination
- Cursor-based for large/changing sets:
?cursor=abc&limit=20
- Offset-based for small stable sets:
?page=1&limit=20
- Always cap
limit server-side (max 100)
Filtering
- Query params:
?status=active&created_after=2024-01-01
- Sort:
?sort=-created_at (minus prefix = descending)
Versioning
- URL path:
/v1/users, /v2/users (easiest to deprecate)
- Never introduce breaking changes to existing version
Security (mandatory)
- Authentication — every non-public endpoint checks auth first
- Authorization — row-level checks, not just auth-exists
- Input validation — Zod schema on every request body + query
- Rate limiting — public routes + AI/LLM routes especially
- CORS — whitelist, not
*
- Output filtering — never leak internal IDs or PII in error messages
- Webhook signatures — verify signature before trusting payload
Error handling
- Never expose stack traces to the client
- Log server-side with request ID
- Return structured error:
{ code: "INVALID_INPUT", message: "...", field: "email" }
- HTTP status code must match error type
Output format
## API Design Summary
### Endpoint
`<METHOD> /path/to/resource`
### Purpose
<what it does, who uses it>
### Request
- **Auth:** <required | optional>
- **Body schema:** Zod
- **Query params:** ...
### Response
- **200:** <shape>
- **Error cases:** 400, 401, 403, 404, 422, 429, 500
### Security checks
- [ ] Auth verified
- [ ] Authorization verified (row-level)
- [ ] Input validated (Zod)
- [ ] Rate limit applied
- [ ] PII not leaked in errors
### Dependencies
- Database tables: <list>
- External services: <list>
Rules
- RESTful first. Only use GraphQL / RPC if there's a concrete reason.
- No breaking changes to existing API versions. Ever.
- Every endpoint validates input — no "we'll add validation later".
- Every endpoint has a test (unit for business logic, integration for HTTP layer).
- Document before coding. OpenAPI spec or at least a Markdown contract.
- Rate limit on day 1 — retrofitting is painful.
1---2name: api-design3description: Backend API design specialist. Use when building REST/GraphQL APIs, designing endpoints, data models, or backend architecture. Covers RESTful principles, HTTP semantics, error handling, versioning, and OWASP-aligned security.4---56<!--7 Source: wshobson/agents (backend-development plugin)8 File: https://github.com/wshobson/agents/tree/main/plugins/backend-development/skills/api-design-principles9 Used by: builder agent (backend)10-->1112# API Design Principles1314## When to trigger15- Designing a new API endpoint16- Adding routes to existing API17- Database schema work that affects API contract18- Keywords: "endpoint", "API", "route", "backend", "server", "REST", "GraphQL"1920## Core RESTful principles2122### Resource-oriented URLs23- Nouns, not verbs: `/users/123`, not `/getUser?id=123`24- Pluralize resources: `/orders`, not `/order`25- Nest only when expressing parent/child: `/users/:id/orders`26- Max 2 levels deep — beyond that, use query params2728### HTTP methods (correct semantics)29| Method | Use for | Idempotent | Safe |30|--------|---------|------------|------|31| GET | Read | Yes | Yes |32| POST | Create | No | No |33| PUT | Replace (full update) | Yes | No |34| PATCH | Partial update | No* | No |35| DELETE | Remove | Yes | No |3637\* PATCH can be idempotent depending on semantics.3839### Status codes (correct use)40- **200** OK — successful GET/PUT/PATCH with body41- **201** Created — successful POST creating resource42- **204** No Content — successful DELETE or action with no body43- **400** Bad Request — validation failure44- **401** Unauthorized — missing/invalid auth45- **403** Forbidden — authenticated but not authorized46- **404** Not Found — resource doesn't exist47- **409** Conflict — version mismatch, duplicate resource48- **422** Unprocessable Entity — semantic validation failure49- **429** Too Many Requests — rate limited50- **500** Internal Server Error — unhandled server fault5152### Response envelope53Consistent shape for all responses:54```typescript55{56 success: boolean57 data: T | null58 error: string | null59 metadata?: { total, page, limit }60}61```6263## Endpoint design patterns6465### Pagination66- Cursor-based for large/changing sets: `?cursor=abc&limit=20`67- Offset-based for small stable sets: `?page=1&limit=20`68- Always cap `limit` server-side (max 100)6970### Filtering71- Query params: `?status=active&created_after=2024-01-01`72- Sort: `?sort=-created_at` (minus prefix = descending)7374### Versioning75- URL path: `/v1/users`, `/v2/users` (easiest to deprecate)76- Never introduce breaking changes to existing version7778## Security (mandatory)7980- **Authentication** — every non-public endpoint checks auth first81- **Authorization** — row-level checks, not just auth-exists82- **Input validation** — Zod schema on every request body + query83- **Rate limiting** — public routes + AI/LLM routes especially84- **CORS** — whitelist, not `*`85- **Output filtering** — never leak internal IDs or PII in error messages86- **Webhook signatures** — verify signature before trusting payload8788## Error handling8990- Never expose stack traces to the client91- Log server-side with request ID92- Return structured error: `{ code: "INVALID_INPUT", message: "...", field: "email" }`93- HTTP status code must match error type9495## Output format9697```markdown98## API Design Summary99100### Endpoint101`<METHOD> /path/to/resource`102103### Purpose104<what it does, who uses it>105106### Request107- **Auth:** <required | optional>108- **Body schema:** Zod109- **Query params:** ...110111### Response112- **200:** <shape>113- **Error cases:** 400, 401, 403, 404, 422, 429, 500114115### Security checks116- [ ] Auth verified117- [ ] Authorization verified (row-level)118- [ ] Input validated (Zod)119- [ ] Rate limit applied120- [ ] PII not leaked in errors121122### Dependencies123- Database tables: <list>124- External services: <list>125```126127## Rules128129- **RESTful first.** Only use GraphQL / RPC if there's a concrete reason.130- **No breaking changes** to existing API versions. Ever.131- **Every endpoint validates input** — no "we'll add validation later".132- **Every endpoint has a test** (unit for business logic, integration for HTTP layer).133- **Document before coding.** OpenAPI spec or at least a Markdown contract.134- **Rate limit on day 1** — retrofitting is painful.