REST API Design
Base standard: Zalando RESTful API Guidelines
Scope: REST only. GraphQL/gRPC/WebSocket require separate guidelines.
JS ecosystem deviations from Zalando (explicit, intentional):
| Topic |
Zalando |
This skill |
| JSON property & query param naming |
snake_case |
camelCase (JS/TS convention) |
| Versioning default |
Media-type versioning |
URL path /v1/ (more practical) |
| Error body format |
RFC 7807 Problem JSON |
Custom format below |
URL / Path Rules
- Nouns only, plural:
/users, /orders, /products/{productId}/reviews
- kebab-case for path segments:
/order-items, /payment-methods
- No trailing slash:
/users not /users/
- Max one nesting level for relationships:
/users/{userId}/orders — avoid deeper nesting; flatten with query params when needed (e.g., ?userId=123 instead of /users/{userId}/orders/{orderId}/items)
- No verbs in path by default:
/orders + POST, not /create-order — exception: RPC-style actions with no clean resource mapping (e.g., /orders/{id}/cancel, /payments/{id}/refund)
HTTP Methods & Status Codes
| Action |
Method |
Success |
Notes |
| List |
GET |
200 |
Must be paginated if unbounded |
| Get one |
GET |
200 |
404 if not found |
| Create |
POST |
201 |
Return created resource in body |
| Full replace |
PUT |
200 |
Idempotent — requires full body |
| Partial update |
PATCH |
200 |
Send only changed fields |
| Delete |
DELETE |
204 |
No body |
| Batch operations |
POST |
200 / 202 |
200 with per-item results in body; 202 if async. Avoid 207 (WebDAV-specific) |
Use most specific code available. Always include 429 with rate-limit headers when throttling.
Common error codes: 400 (validation), 401 (unauthenticated), 403 (unauthorized), 404 (not found), 409 (conflict), 422 (unprocessable entity).
JSON Naming
- Properties:
camelCase — userId, createdAt, totalAmount
- Query parameters:
camelCase — ?sortBy=createdAt&pageSize=20
- Enum values:
UPPER_SNAKE_CASE — "status": "ORDER_PENDING"
- Date/time properties: end with
At suffix — createdAt, expiresAt, publishedAt
- Boolean properties: start with
is / has — isActive, hasVerified
- Array property names: plural —
items, tags, orderLines
Request/Response Structure
Top-level must always be a JSON object — never a bare array:
// List response
{ "data": [...], "pagination": { ... } }
// Single resource
{ "data": { "id": "...", ... } }
Follow project-existing envelope if one already exists.
Error response — consistent format across all endpoints:
{
"error": {
"code": "VALIDATION_ERROR", // UPPER_SNAKE_CASE, machine-readable
"message": "Human-readable text",
"details": [{ "field": "email", "message": "Invalid format" }]
}
}
Filtering, Sorting, Pagination
- Filtering: query params —
?status=ACTIVE&userId=123
- Sorting:
?sortBy=createdAt&order=desc
- Pagination — prefer cursor-based for large/unbounded datasets:
- Cursor:
?cursor=<token>&pageSize=20
- Offset:
?page=1&pageSize=20 (for small datasets / UI tables only)
- Pagination response shape must be consistent across all list endpoints
Versioning
- Default: URL path versioning —
/v1/users
- Breaking changes (require version bump): removing fields, changing field types, changing auth model
- Non-breaking (no bump): adding optional fields, new endpoints, relaxing validation
- Keep old version alive until all clients migrated; signal deprecation with
Sunset header
Design Checklist
Before finalizing an API contract:
Optional Modules
Load these references only when the feature involves the specific concern:
- Idempotency keys (payment, order creation, notifications): see
references/idempotency.md
- File uploads: see
references/file-upload.md
- Webhook design: see
references/webhooks.md
1---2name: api-design3description: Use when designing new API endpoints, reviewing API contracts, aligning FE/BE on schema, adding versioning strategy, defining error response structure, or any task involving API interface decisions. Skip when implementing existing well-defined API specs with no design decisions needed.4---56# REST API Design78**Base standard**: [Zalando RESTful API Guidelines](https://opensource.zalando.com/restful-api-guidelines/)9**Scope**: REST only. GraphQL/gRPC/WebSocket require separate guidelines.1011**JS ecosystem deviations from Zalando** (explicit, intentional):12| Topic | Zalando | This skill |13|-------|---------|-----------|14| JSON property & query param naming | `snake_case` | `camelCase` (JS/TS convention) |15| Versioning default | Media-type versioning | URL path `/v1/` (more practical) |16| Error body format | RFC 7807 Problem JSON | Custom format below |1718---1920## URL / Path Rules2122- **Nouns only, plural**: `/users`, `/orders`, `/products/{productId}/reviews`23- **kebab-case** for path segments: `/order-items`, `/payment-methods`24- **No trailing slash**: `/users` not `/users/`25- **Max one nesting level** for relationships: `/users/{userId}/orders` — avoid deeper nesting; flatten with query params when needed (e.g., `?userId=123` instead of `/users/{userId}/orders/{orderId}/items`)26- **No verbs** in path by default: `/orders` + POST, not `/create-order` — exception: RPC-style actions with no clean resource mapping (e.g., `/orders/{id}/cancel`, `/payments/{id}/refund`)2728## HTTP Methods & Status Codes2930| Action | Method | Success | Notes |31|--------|--------|---------|-------|32| List | GET | 200 | Must be paginated if unbounded |33| Get one | GET | 200 | 404 if not found |34| Create | POST | 201 | Return created resource in body |35| Full replace | PUT | 200 | Idempotent — requires full body |36| Partial update | PATCH | 200 | Send only changed fields |37| Delete | DELETE | 204 | No body |38| Batch operations | POST | 200 / 202 | 200 with per-item results in body; 202 if async. Avoid 207 (WebDAV-specific) |3940Use most specific code available. Always include `429` with rate-limit headers when throttling.4142Common error codes: `400` (validation), `401` (unauthenticated), `403` (unauthorized), `404` (not found), `409` (conflict), `422` (unprocessable entity).4344## JSON Naming4546- **Properties**: `camelCase` — `userId`, `createdAt`, `totalAmount`47- **Query parameters**: `camelCase` — `?sortBy=createdAt&pageSize=20`48- **Enum values**: `UPPER_SNAKE_CASE` — `"status": "ORDER_PENDING"`49- **Date/time properties**: end with `At` suffix — `createdAt`, `expiresAt`, `publishedAt`50- **Boolean properties**: start with `is` / `has` — `isActive`, `hasVerified`51- **Array property names**: plural — `items`, `tags`, `orderLines`5253## Request/Response Structure5455**Top-level must always be a JSON object** — never a bare array:5657```58// List response59{ "data": [...], "pagination": { ... } }6061// Single resource62{ "data": { "id": "...", ... } }63```6465Follow project-existing envelope if one already exists.6667**Error response** — consistent format across all endpoints:68```69{70 "error": {71 "code": "VALIDATION_ERROR", // UPPER_SNAKE_CASE, machine-readable72 "message": "Human-readable text",73 "details": [{ "field": "email", "message": "Invalid format" }]74 }75}76```7778## Filtering, Sorting, Pagination7980- **Filtering**: query params — `?status=ACTIVE&userId=123`81- **Sorting**: `?sortBy=createdAt&order=desc`82- **Pagination** — prefer cursor-based for large/unbounded datasets:83 - Cursor: `?cursor=<token>&pageSize=20`84 - Offset: `?page=1&pageSize=20` (for small datasets / UI tables only)85- Pagination response shape must be consistent across all list endpoints8687## Versioning8889- **Default**: URL path versioning — `/v1/users`90- **Breaking changes** (require version bump): removing fields, changing field types, changing auth model91- **Non-breaking** (no bump): adding optional fields, new endpoints, relaxing validation92- Keep old version alive until all clients migrated; signal deprecation with `Sunset` header9394## Design Checklist9596Before finalizing an API contract:97- [ ] Path uses plural nouns, kebab-case, no trailing slash98- [ ] No verbs in URL99- [ ] All list endpoints paginated with consistent shape100- [ ] JSON properties are camelCase; enums are UPPER_SNAKE_CASE101- [ ] Error response follows project error format102- [ ] No sensitive data in URLs (use body or headers)103- [ ] Breaking vs non-breaking change assessed104- [ ] Idempotency considered for POST operations with side effects105106## Optional Modules107108Load these references only when the feature involves the specific concern:109110- **Idempotency keys** (payment, order creation, notifications): see `references/idempotency.md`111- **File uploads**: see `references/file-upload.md`112- **Webhook design**: see `references/webhooks.md`