api-design
REST API design patterns for resource naming, HTTP methods, status codes, pagination strategy, error envelopes, versioning, and rate limiting. Use when designing, reviewing, or refactoring HTTP APIs a
SKILL.mdskills/api-design/SKILL.md
GitHubAPI Design
Design resource-oriented HTTP APIs with stable contracts, predictable errors, and explicit pagination and versioning choices.
When to Activate
- Design a new HTTP or REST endpoint
- Review existing route naming or handler contracts
- Choose between offset and cursor pagination
- Define status codes and error payloads
- Introduce a breaking API change
- Add rate limiting or idempotency behavior
- Generate or review an OpenAPI spec
Resource Design
| Concern | Preferred | Avoid |
|---|---|---|
| Collections | /users |
/getUsers |
| Single resource | /users/{user_id} |
/user?id=123 |
| Nested resource | /users/{user_id}/tokens |
/userTokens/{user_id} |
| Actions | /jobs/{job_id}:cancel when no resource exists |
/cancelJob/{job_id} |
Rules:
- Use plural nouns for collections.
- Use kebab-case for multi-word path segments.
- Put identifiers in the path and filters in the query string.
- Model actions as resources first; use action suffixes only when CRUD does not fit.
BAD
GET /getUserOrders?userId=42&limit=5000
GOOD
GET /users/42/orders?limit=100&cursor=eyJpZCI6Im9yZF8xIn0=
HTTP Semantics
| Method | Use When | Idempotent | Response |
|---|---|---|---|
GET |
Read resources | Yes | 200, 206, 304 |
POST |
Create resource or submit command | No | 201, 202 |
PUT |
Replace full resource | Yes | 200, 204 |
PATCH |
Partial update | Usually | 200, 204 |
DELETE |
Remove resource | Yes | 204 |
| Situation | Code | Notes |
|---|---|---|
| Created synchronously | 201 Created |
Return Location header |
| Accepted async work | 202 Accepted |
Return job resource |
| Validation failure | 400 Bad Request or 422 Unprocessable Entity |
Match validator semantics |
| Authentication missing | 401 Unauthorized |
Include auth challenge when applicable |
| Permission denied | 403 Forbidden |
Caller is authenticated |
| Missing resource | 404 Not Found |
Do not leak existence across tenants |
| Version conflict | 409 Conflict |
ETag or business conflict |
| Rate limited | 429 Too Many Requests |
Include reset headers |
Pagination
| Strategy | Use When | Strengths | Tradeoffs |
|---|---|---|---|
| Offset pagination | Small admin lists, stable datasets | Simple for humans | Slow on large offsets, duplicate/skipped rows under writes |
| Cursor pagination | User-facing feeds, large tables, append-heavy data | Stable under writes, faster scans | Harder to debug manually |
Default:
- Use cursor pagination for externally consumed list endpoints.
- Use offset pagination only for bounded internal tooling or page-number UX.
{
"data": [{ "id": "usr_123", "email": "a@example.com" }],
"page": {
"next_cursor": "eyJpZCI6InVzcl8xMjMifQ==",
"has_more": true
}
}
Error Envelope
Use one shape across the API:
{
"error": {
"code": "validation_error",
"message": "email must be a valid address",
"details": [
{ "field": "email", "reason": "invalid_format" }
],
"request_id": "req_01HV..."
}
}
Rules:
codeis stable and machine-readable.messageis human-readable and safe to log.detailsis optional and field-scoped.- Always include
request_idfor support correlation.
BAD
{ "message": "Something went wrong" }
GOOD
{
"error": {
"code": "rate_limited",
"message": "Too many requests",
"request_id": "req_123"
}
}
Versioning and Rate Limiting
| Approach | Preferred | Avoid |
|---|---|---|
| Public API version | /v1/... at the router boundary |
Version per endpoint ad hoc |
| Breaking change rollout | Ship /v2, deprecate /v1, publish sunset date |
Mutate /v1 response shape silently |
| Additive changes | New optional fields | Reusing field names with changed meaning |
| Header | Meaning |
|---|---|
X-RateLimit-Limit |
Total quota in current window |
X-RateLimit-Remaining |
Requests left in window |
X-RateLimit-Reset |
Unix timestamp when quota resets |
Retry-After |
Seconds until retry after 429 |
Checklist
- Paths use plural nouns and stable identifiers
- HTTP method matches the state transition
- Success and failure codes are explicit for each endpoint
- List endpoints declare pagination strategy and bounds
- Error responses use the shared error envelope
- Breaking changes are isolated behind a version boundary
- Rate-limited endpoints return quota headers
- Examples show realistic URLs and payloads