| name | api-design-patterns |
| description | This skill should be used when designing REST APIs, GraphQL schemas, or API architectures - covers endpoint design, versioning strategies, error handling, pagination, authentication patterns, and OpenAPI documentation for backend-architect and api-designer agents. |
API Design Patterns
Overview
Design APIs that are intuitive, maintainable, and scalable. This skill teaches systematic API design following REST best practices and modern patterns.
Core principle: Good API design makes client development effortless.
When to Use
Use when:
- Designing new APIs
- Refactoring existing endpoints
- Planning API versioning
- Documenting API contracts
- Deciding REST vs GraphQL
- Designing error responses
REST vs GraphQL Decision
| Factor | REST | GraphQL |
|---|---|---|
| Use when | Simple CRUD, public APIs | Complex queries, mobile apps |
| Learning curve | Low | Medium-High |
| Caching | Easy (HTTP cache) | Complex |
| Over/under-fetching | Common issue | Solved |
| Tooling | Mature, widespread | Growing |
| Real-time | Add complexity | Built-in subscriptions |
Start with REST unless you have specific GraphQL needs.
REST API Design Patterns
Endpoint Naming
Resource-based URLs:
✅ GET /api/users
✅ GET /api/users/:id
✅ POST /api/users
✅ PATCH /api/users/:id
✅ DELETE /api/users/:id
❌ GET /api/getUsers
❌ POST /api/createUser
❌ GET /api/user-list
Nested resources:
✅ GET /api/users/:id/posts
✅ POST /api/users/:id/posts
✅ GET /api/posts/:id/comments
❌ GET /api/getUserPosts/:userId
Actions (when needed):
✅ POST /api/users/:id/activate
✅ POST /api/orders/:id/cancel
✅ POST /api/posts/:id/publish
HTTP Methods
| Method | Use For | Idempotent | Safe |
|---|---|---|---|
| GET | Retrieve data | Yes | Yes |
| POST | Create resource | No | No |
| PUT | Replace resource | Yes | No |
| PATCH | Update resource | No | No |
| DELETE | Remove resource | Yes | No |
Status Codes
Success:
200 OK- GET, PATCH, DELETE success201 Created- POST success (return created resource)204 No Content- DELETE success (no body)
Client errors:
400 Bad Request- Invalid input401 Unauthorized- Missing/invalid auth403 Forbidden- Auth valid but no permission404 Not Found- Resource doesn't exist422 Unprocessable Entity- Validation errors429 Too Many Requests- Rate limit exceeded
Server errors:
500 Internal Server Error- Unexpected error503 Service Unavailable- Temporarily down
Response Format
Success response:
{
"data": {
"id": "user_123",
"name": "John Doe",
"email": "john@example.com"
}
}
Error response:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid email format",
"details": [
{
"field": "email",
"message": "Must be a valid email address"
}
]
}
}
Pagination
Cursor-based (recommended for large datasets):
GET /api/users?limit=20&cursor=abc123
Response:
{
"data": [...],
"pagination": {
"next_cursor": "def456",
"has_more": true
}
}
Offset-based (simpler, less efficient):
GET /api/users?limit=20&offset=40
Response:
{
"data": [...],
"pagination": {
"total": 1000,
"page": 3,
"per_page": 20
}
}
Filtering & Sorting
GET /api/users?status=active&role=admin&sort=-created_at
Query parameters:
- Filters: status, role, etc.
- Sort: field name, prefix with - for descending
- Search: q=search+term
Versioning
URL versioning (recommended):
/api/v1/users
/api/v2/users
Header versioning:
Accept: application/vnd.api.v1+json
Strategy:
- v1 is default
- Maintain previous version during deprecation period
- Announce deprecation 6-12 months early
- Remove old version only when usage <1%
Authentication
Bearer token (recommended):
Authorization: Bearer eyJhbGc...
API key:
X-API-Key: your-api-key-here
Session cookie:
Cookie: session=abc123
Rate Limiting
Response headers:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1640000000
Rate limit response:
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests. Try again in 60 seconds.",
"retry_after": 60
}
}
OpenAPI Documentation
Generate from code (recommended):
// Using tRPC
export const userRouter = router({
list: publicProcedure
.meta({
openapi: {
method: 'GET',
path: '/api/users',
tags: ['users'],
},
})
.input(z.object({ limit: z.number().optional() }))
.output(z.array(UserSchema))
.query(async ({ input }) => {
return db.users.findMany({ take: input.limit })
}),
})
Manual OpenAPI spec:
openapi: 3.0.0
info:
title: My API
version: 1.0.0
paths:
/api/users:
get:
summary: List users
parameters:
- name: limit
in: query
schema:
type: integer
responses:
'200':
description: Success
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/User'
Common Patterns
Bulk operations:
POST /api/users/bulk
Body: [{ create user 1 }, { create user 2 }]
Response:
{
"created": 5,
"failed": 2,
"errors": [...]
}
Batch requests:
POST /api/batch
Body: [
{ method: "GET", path: "/users/1" },
{ method: "GET", path: "/posts/1" }
]
Webhooks:
POST /api/webhooks
Body: {
"url": "https://your-site.com/webhook",
"events": ["user.created", "user.updated"]
}
Resources
- REST API guidelines: restfulapi.net
- OpenAPI spec: swagger.io/specification
- API design guide: apistylebook.com
Good API design frontloads thinking to eliminate client confusion. Design for developers, document thoroughly, version carefully.