REST API Design Checklist
A practical reference covering every decision point when designing a REST API — from URL structure and HTTP semantics to versioning, pagination, and error shapes. Use this as a review checklist before shipping.
Resource URL Structure
Follow these rules for every endpoint you design:
# Collections — plural nouns GET /users # list all users POST /users # create a new user # Specific resource — ID in path segment GET /users/42 # get user 42 PUT /users/42 # full replace PATCH /users/42 # partial update DELETE /users/42 # delete # Nested resource (max 2 levels) GET /users/42/orders # orders belonging to user 42 GET /users/42/orders/7 # specific order for user 42 # WRONG — verbs in URL POST /createUser # bad GET /users/getAll # bad POST /users/42/delete # bad
HTTP Method Semantics
| Method | Idempotent | Safe | Use for | Success code |
|---|---|---|---|---|
| GET | Yes | Yes | Read resource or collection | 200 |
| POST | No | No | Create resource | 201 + Location |
| PUT | Yes | No | Full replace (all fields) | 200 or 204 |
| PATCH | No* | No | Partial update (only sent fields) | 200 or 204 |
| DELETE | Yes | No | Remove resource | 204 |
| HEAD | Yes | Yes | Check existence / headers only | 200 or 404 |
| OPTIONS | Yes | Yes | CORS preflight, allowed methods | 200 |
*PATCH is only idempotent if using JSON Patch format (RFC 6902). Merge-patch operations are not guaranteed idempotent.
Status Code Quick Reference
200 OK — Successful GET, PUT, PATCH with response body 201 Created — Successful POST; include Location: /users/42 204 No Content — Successful DELETE or PUT/PATCH with no body 304 Not Modified — GET with conditional If-None-Match / If-Modified-Since 400 Bad Request — Malformed JSON, wrong types, missing required fields 401 Unauthorized — No auth token, expired token, invalid signature 403 Forbidden — Valid token but insufficient permissions 404 Not Found — Resource does not exist (or hidden for security) 405 Method Not Allowed — PUT on a collection, etc. 409 Conflict — Duplicate unique field, optimistic lock version mismatch 410 Gone — Resource permanently deleted (use over 404 for caching) 422 Unprocessable — Syntactically valid but semantically wrong (validation) 429 Too Many Requests — Rate limited; include Retry-After header 500 Internal Error — Server-side failure; return trace_id, log full error 503 Service Unavailable — Dependency down; include Retry-After header
Filtering, Sorting & Pagination
# Filtering — query params per field
GET /orders?status=pending&created_after=2024-01-01
# Range filters with operators
GET /products?price[gte]=10&price[lte]=100
GET /products?price_min=10&price_max=100 # simpler alternative
# Sorting — comma-separated, prefix minus for desc
GET /users?sort=-created_at,name # newest first, then by name asc
# Cursor pagination (recommended for large data)
GET /posts?limit=20
# Response includes:
# { "data": [...], "meta": { "next_cursor": "eyJpZCI6MjB9", "has_more": true } }
GET /posts?cursor=eyJpZCI6MjB9&limit=20
# Field selection (sparse fieldsets)
GET /users?fields=id,name,email # only return requested fields Consistent Error Response Shape
// 422 Validation error
{
"error": {
"code": "VALIDATION_FAILED",
"message": "Request validation failed",
"details": [
{ "field": "email", "issue": "must be a valid email address" },
{ "field": "age", "issue": "must be a positive integer" }
],
"trace_id": "req_01hxyz..."
}
}
// 404 Not found
{
"error": {
"code": "USER_NOT_FOUND",
"message": "No user found with id 42",
"trace_id": "req_01habc..."
}
}
// 429 Rate limited
{
"error": {
"code": "RATE_LIMITED",
"message": "Too many requests",
"retry_after": 30
}
}
// Also set headers: X-RateLimit-Limit, X-RateLimit-Remaining, Retry-After Versioning Strategy
# Option 1: URL versioning (most common, cache-friendly) GET /v1/users GET /v2/users # Option 2: Request header versioning GET /users X-API-Version: 2 # Option 3: Content-type negotiation (RFC 6838) Accept: application/vnd.myapi.v2+json # Deprecation lifecycle headers Deprecation: Tue, 01 Jan 2025 00:00:00 GMT Sunset: Mon, 01 Jul 2025 00:00:00 GMT Link: <https://devdocs.example.com/v2/migration>; rel="successor-version"
HATEOAS Basics
// Minimal HATEOAS — self link + available actions
{
"id": 42,
"status": "pending",
"total": 199.99,
"_links": {
"self": { "href": "/orders/42", "method": "GET" },
"cancel": { "href": "/orders/42/cancel", "method": "POST" },
"invoice": { "href": "/orders/42/invoice","method": "GET" }
}
}
// Paginated collection with navigation links
{
"data": [...],
"_links": {
"self": { "href": "/posts?page=3" },
"next": { "href": "/posts?page=4" },
"prev": { "href": "/posts?page=2" },
"first": { "href": "/posts?page=1" },
"last": { "href": "/posts?page=12" }
},
"meta": { "total": 234, "per_page": 20 }
} Pre-Ship Checklist
| Area | Checklist Item |
|---|---|
| URLs | Plural nouns, no verbs, kebab-case, max 2 nesting levels |
| Methods | GET safe, PUT/DELETE idempotent, POST only for creation |
| Status codes | 201 + Location on create, 204 on delete, 422 for validation |
| Errors | Consistent envelope, machine-readable code, trace_id |
| Pagination | Cursor-based, max limit enforced, meta with has_more |
| Versioning | Strategy chosen and documented, sunset headers on old versions |
| Security | Auth on all non-public routes, 401 vs 403 used correctly |
| Docs | OpenAPI spec published, examples for every endpoint |
Frequently Asked Questions
What is the correct way to name REST API resources?▼
Always use plural nouns for collections: /users, /orders, /products. Never use verbs in the URL — the HTTP method conveys the action. Nest resources only when the child truly cannot exist without the parent, and limit nesting to two levels deep: /users/42/orders is fine, /users/42/orders/7/items/3/reviews gets unwieldy. Use kebab-case for multi-word segments: /blog-posts, not /blogPosts or /blog_posts. IDs go in the path for specific resources: GET /users/42, not GET /users?id=42.
Which HTTP status codes should every API return?▼
At minimum: 200 OK (successful GET/PUT/PATCH), 201 Created (successful POST that creates a resource, include Location header pointing to new resource), 204 No Content (successful DELETE or PUT with no body), 400 Bad Request (invalid input — include error details in body), 401 Unauthorized (missing or invalid authentication), 403 Forbidden (authenticated but lacks permission), 404 Not Found (resource does not exist), 409 Conflict (optimistic locking collision, duplicate key), 422 Unprocessable Entity (validation errors with field-level details), 429 Too Many Requests (rate limited), 500 Internal Server Error (never expose stack traces).
What are the best API versioning strategies?▼
URL versioning (/v1/users) is the most widely used and cache-friendly approach — clients can see the version at a glance and proxy caches work correctly. Header versioning (Accept: application/vnd.api+json;version=2) keeps URLs clean but is harder to test in a browser. Avoid query param versioning (?v=1) because it mixes routing concerns with filtering. Whatever strategy you choose, publish a sunset policy: deprecate at least 6 months before removing a version and return a Sunset response header.
How should REST APIs handle pagination?▼
Cursor-based pagination is recommended for large or frequently-changing datasets. Return a next_cursor (opaque base64 token encoding the last seen ID + timestamp) and use it as a query param: GET /posts?cursor=eyJpZCI6MTAwfQ&limit=20. Avoid offset pagination for large tables — OFFSET 10000 causes a full index scan. For simpler cases, return total_count, page, per_page, and links (self, next, prev) in a meta object. Always cap maximum page size (e.g., limit=100) and default to a safe value (limit=20).
What is HATEOAS and do I actually need it?▼
HATEOAS (Hypermedia as the Engine of Application State) means embedding links in API responses that describe available actions, so clients discover the API dynamically without hard-coding URLs. A fully HATEOAS response includes _links: { self: { href: '/orders/42' }, cancel: { href: '/orders/42/cancel', method: 'POST' } }. In practice, most public APIs skip full HATEOAS. A pragmatic middle ground: include a self link on every resource and a next/prev on paginated collections. Full HATEOAS is most valuable for machine-to-machine APIs where clients must be decoupled from server URL structure.
How do I design consistent error responses?▼
Adopt a standard error envelope and apply it everywhere. A good structure: { error: { code: 'VALIDATION_FAILED', message: 'Human-readable summary', details: [ { field: 'email', issue: 'must be a valid email address' } ] } }. The code field is a machine-readable string constant your clients can switch on. Never return different error shapes for different endpoints. For 500 errors, log the full stack server-side but return only a generic message and a trace_id clients can quote in support tickets.
Related Products
on GumroadDevPlaybook Interview Vault
210+ real technical interview questions with model answers, asked at Google, Meta, Amazon, Microsoft, Stripe, and Shopify. Covers Frontend (55), Backend (55), System Design (50), and DSA (50). Available in English and Traditional Chinese.
Buy on Gumroad →TypeScript Quick Reference Toolkit
5 practical reference files for TypeScript developers: cheatsheet, TS vs JS comparison, 20 utility types with real examples, tsconfig best practices template, and 15 common mistakes guide. Print it. Keep it on your desk.
Buy on Gumroad →Developer Interview Vault
210 technical interview questions with model answers, key points, and follow-up questions. Covers Frontend, Backend, System Design, and DSA — bilingual English + Traditional Chinese (ZH-TW). PWYW from $29.
Buy on Gumroad →You Might Also Need
OpenAPI Validator
Paste your OpenAPI 3.x spec (YAML or JSON) to validate structure, find missing required fields, detect duplicate paths, check for missing operationIds, and get errors and warnings with path references.
Open free tool →
HTTP Status Code Reference
Complete HTTP status code reference. Search all 1xx, 2xx, 3xx, 4xx, and 5xx codes with plain-English descriptions, real-world use cases, and when to use each in REST APIs.
Open free tool →
API Request Builder
Build and test HTTP requests visually with any method, headers, and body. Generate cURL commands instantly. Free online API testing tool with Pro unlimited access. No signup required.
Open free tool →
More Free Tools
Related Articles
Complete HTTP Status Codes Reference for REST API Devs
The complete HTTP status codes reference for REST API developers. Every 1xx, 2xx, 3xx, 4xx, and 5xx code explained with real-world API examples and best practices.
curl 深度使用手冊:API 測試從入門到進階
curl 是每位開發者必備的 CLI 工具。本文涵蓋 GET/POST/PUT/DELETE 請求、認證、header 設定、檔案上傳、除錯技巧,以及 REST API 測試的完整實戰範例。
HTTP Status Codes Explained: Complete Guide for Developers
Every HTTP status code group explained with real-world examples. Know exactly when to use 200, 201, 301, 401, 404, 429, 500, and more.
REST API Best Practices: Endpoint Naming, Versioning, and Error Handling
Design clean, consistent REST APIs with proper URL naming, HTTP methods, versioning, error formats, pagination, auth, and rate limiting.
Understanding REST API Design Best Practices
A practical guide to designing REST APIs that developers love — covering resource modeling, HTTP semantics, status codes, versioning, and error handling.
Enjoying this tool? Get more like it.
Weekly developer tool guides, productivity playbooks & AI tricks. Free. No spam.
Unlock AI-Powered Dev Tools
- ⚡ AI Code Review, Doc Generator & SQL Builder
- ⚡ All premium templates & early access
- ⚡ Member discounts on Gumroad products
$9/mo after trial · Cancel anytime
Want the full toolkit?
Get DevPlaybook Pro
Every template, guide, boilerplate, and automation script in one bundle. 13 premium products — grab them all at once and save big.
MIT licensed · Instant download · No subscription
See what's included → Browse all deals →