REST API Design Guide: From Zero to Production-Ready
A complete, opinionated reference covering URL design, HTTP methods, response structure, versioning, security, and everything in between
New to REST? Start with our REST Guide introduction before diving into design decisions. This guide assumes you understand REST basics and are ready to build a production-quality API.
What Makes a Good REST API Design?
A great REST API has three qualities that are easy to describe but hard to achieve: consistency, predictability, and outstanding developer experience (DX).
The principle of least surprise is the foundation of good API design. A developer who understands one endpoint in your API should be able to guess how every other endpoint works — the same naming conventions, the same response structure, the same error format, the same status codes. Surprises in APIs are bugs waiting to be filed.
A well-designed API is also self-documenting. Looking at GET /users/123/orders?status=shipped&sort=created_at&order=desc, a developer should immediately understand what it does, what it returns, and how to modify it — without reading documentation.
Key design decisions covered in this guide:
- URL and resource naming conventions
- Correct HTTP method usage
- Consistent JSON response structure
- Meaningful HTTP status codes
- Structured error responses
- API versioning strategy
- Authentication and security
- Pagination, filtering, and sorting
- Rate limiting
URL & Resource Design
Your URLs are the public interface of your API. Once published, changing them breaks clients. Get them right from the start.
Rule 1: Use Nouns, Not Verbs
Resources are things (nouns), not actions (verbs). The HTTP method provides the action. /users is correct; /getUsers is not. This is the single most common URL design mistake.
Rule 2: Use Plural Nouns
Use plural resource names consistently: /users, /orders, /products. Even when accessing a single resource (/users/123), the collection name stays plural. This avoids the awkward decision of whether a specific URL uses singular or plural and creates a predictable pattern.
Rule 3: Lowercase Letters and Hyphens Only
URLs are case-sensitive on most servers. Stick to lowercase. Use hyphens (-) for multi-word resource names: /user-profiles, /order-items. Never use underscores (_) or camelCase (userProfiles) in URLs.
Rule 4: Use Hierarchy for Nested Resources
When a resource belongs to another resource, express that relationship in the URL path: /users/123/orders retrieves orders belonging to user 123. Limit nesting to two or three levels maximum — deeper hierarchies become unwieldy.
Rule 5: Filters, Sorting, and Pagination via Query Parameters
Query parameters modify the response, they don't change the resource identity: /users?status=active&role=admin&sort=created_at&page=2&limit=20.
Good vs Bad URL Patterns
| Bad URL | Good URL | Why |
|---|---|---|
/getUsers |
GET /users |
Verb in URL — the HTTP method carries the action |
/user/123 |
/users/123 |
Singular resource name — always use plural |
/Users/Create |
POST /users |
Action in URL and wrong casing — use HTTP method and lowercase |
/orders?userId=123&getAll=true |
/users/123/orders |
Use resource hierarchy instead of filter params for ownership |
/UserProfiles |
/user-profiles |
CamelCase in URL — use lowercase with hyphens |
/deleteOrder/456 |
DELETE /orders/456 |
Action in URL — use DELETE HTTP method |
HTTP Methods — Using Them Correctly
See our complete HTTP Methods guide for detailed coverage with examples. Here is the quick reference for CRUD mapping:
| Method | CRUD Operation | Example | Idempotent? |
|---|---|---|---|
| GET | Read | GET /users/123 |
Yes |
| POST | Create | POST /users |
No |
| PUT | Full Replace | PUT /users/123 |
Yes |
| PATCH | Partial Update | PATCH /users/123 |
No* |
| DELETE | Delete | DELETE /users/123 |
Yes |
*PATCH can be made idempotent with careful implementation.
The most common mistake: using POST for everything. Teams new to REST often create endpoints like POST /users/update or POST /orders/delete. This defeats the semantic meaning of HTTP, breaks caching, and makes the API harder to understand. Use the right method for each operation.
Response Structure & JSON Design
Every response from your API should follow the same structure. Consistency here is non-negotiable.
The Envelope Pattern
Wrap all responses in a consistent envelope with data, meta, and errors keys:
// Success response — single resource:
{
"data": {
"id": "usr_123",
"name": "Jane Smith",
"email": "jane@example.com",
"created_at": "2026-03-24T10:30:00Z"
},
"meta": {}
}
// Success response — collection:
{
"data": [
{"id": "usr_123", "name": "Jane Smith"},
{"id": "usr_124", "name": "John Doe"}
],
"meta": {
"pagination": {
"page": 1,
"limit": 20,
"total": 142,
"total_pages": 8
}
}
}
// Error response:
{
"errors": [{
"code": "VALIDATION_ERROR",
"message": "Email is required",
"field": "email"
}]
}
Naming Conventions
Choose snake_case or camelCase and use it throughout every response in your API. Never mix them. snake_case (created_at, user_name) is more common in REST APIs and is used by GitHub, Stripe, and Twilio. camelCase (createdAt, userName) is common in JavaScript ecosystems. Neither is wrong — consistency is what matters.
Timestamps
Always use ISO 8601 format in UTC: "created_at": "2026-03-24T10:30:00Z". Never use Unix timestamps or locale-specific date formats in API responses — they are ambiguous and difficult to parse universally.
IDs
String UUIDs ("id": "usr_550e8400-e29b-41d4-a716-446655440000") are preferable to sequential integer IDs in public APIs. Integer IDs leak information (total record counts, creation order) and are trivial to enumerate. Prefixed UUIDs (like Stripe's cus_, ch_ prefixes) add type information that aids debugging.
Null vs Omitted Fields
Choose one strategy and stick to it. Either always include all fields (with null for missing values) or omit fields that have no value. The most important thing is that clients can rely on the structure being predictable.
HTTP Status Codes — Use Them Correctly
Status codes communicate the outcome of a request. Using them correctly lets clients handle responses without parsing the body. See our complete HTTP Status Codes guide for the full reference.
Key rules:
- Never return 200 for errors.
200 OKmeans success. If there was an error, use a 4xx or 5xx code. - Use 201 Created for successful resource creation (POST). Include a
Locationheader pointing to the new resource. - Use 204 No Content for successful deletions or updates with no response body.
- 4xx codes are client errors — the client did something wrong (bad input, not authenticated, not authorized, resource not found).
- 5xx codes are server errors — something went wrong on your side. Never expose internal details in 5xx responses.
- Use 429 Too Many Requests for rate limiting with a
Retry-Afterheader.
| Situation | Correct Status Code |
|---|---|
| Successful GET, PUT, PATCH | 200 OK |
| Successful POST (resource created) | 201 Created |
| Successful DELETE | 204 No Content |
| Invalid request body or parameters | 400 Bad Request |
| Missing or invalid authentication | 401 Unauthorized |
| Authenticated but no permission | 403 Forbidden |
| Resource not found | 404 Not Found |
| Rate limit exceeded | 429 Too Many Requests |
| Unexpected server error | 500 Internal Server Error |
Error Response Design
Every API error should return a structured JSON body. Never return an empty body with a 4xx status code — clients need to understand what went wrong. See our Error Handling guide for more detail.
The consistent error format:
{
"errors": [
{
"code": "VALIDATION_ERROR",
"message": "Email is required",
"field": "email"
}
]
}
// Multiple validation errors:
{
"errors": [
{
"code": "VALIDATION_ERROR",
"message": "Email is required",
"field": "email"
},
{
"code": "VALIDATION_ERROR",
"message": "Name must be at least 2 characters",
"field": "name"
}
]
}
// Authentication error:
{
"errors": [
{
"code": "UNAUTHORIZED",
"message": "Authentication token is missing or invalid"
}
]
}
Every error object should include:
code— machine-readable string constant (e.g.,VALIDATION_ERROR,USER_NOT_FOUND). Clients use this to handle specific error types programmatically.message— human-readable description. Safe to display to developers but avoid end-user-facing copy in the API layer.field(optional) — the field that caused the error, for validation errors.
Never include stack traces, database error messages, or internal implementation details in error responses.
API Versioning Strategy
Versioning is how you make breaking changes without breaking existing clients. See our API Versioning guide for detailed coverage.
Why Versioning is Necessary
Once your API is consumed by clients you don't control, you cannot make breaking changes without a version boundary. Breaking changes include: removing fields, changing field types, renaming endpoints, changing authentication schemes, and altering response structures.
Versioning Approaches
URL path versioning (recommended):
GET /v1/users/123
GET /v2/users/123
Most common approach. Explicit, visible, bookmarkable, and easy to route at the infrastructure level. Used by Stripe, GitHub, Twilio, and most major APIs.
Header versioning:
GET /users/123
Accept: application/vnd.api+json;version=2
Cleaner URLs but less visible. Harder to test in a browser or share as a URL. Developers must read documentation to discover available versions.
Query parameter versioning (not recommended):
GET /users/123?version=2
Pollutes the query string and makes caching more complex. Avoid this approach.
Recommendation: Use URL path versioning. Start with /v1/ from day one, even if you never release a /v2/. The cost is negligible and the future flexibility is valuable.
Authentication & Security
Security is not optional. For a full security guide, see REST API Security Best Practices. For authentication details, see our Authentication guide.
HTTPS always. Never serve your API over plain HTTP in production. Use HTTPS for all endpoints without exception. Enable HSTS to prevent protocol downgrade attacks.
Authentication Methods
- API Keys: Simple server-to-server authentication. Pass in
Authorization: Bearer {key}header — never in the URL (keys in URLs end up in server logs). - JWT (JSON Web Tokens): Stateless, self-contained tokens ideal for user authentication. Include an expiry (
expclaim) and validate on every request. Access tokens should expire in 15 minutes to 1 hour. - OAuth 2.0: Industry standard for delegated authorization. Use when third-party applications need to act on behalf of your users.
// Correct — API key in Authorization header:
GET /v1/users
Authorization: Bearer sk_live_abc123...
// Wrong — API key in URL (visible in logs, browser history):
GET /v1/users?api_key=sk_live_abc123...
Pagination, Filtering & Sorting
Every list endpoint must support pagination. Returning unlimited records is a performance and security risk. See our Pagination guide for complete coverage.
Offset/Limit Pagination
GET /users?page=2&limit=20
// Response:
{
"data": [...],
"meta": {
"pagination": {
"page": 2,
"limit": 20,
"total": 142,
"total_pages": 8
}
}
}
Cursor Pagination
For large datasets or real-time data, cursor-based pagination is more efficient and consistent:
GET /users?cursor=eyJpZCI6MTIzfQ&limit=20
// Response includes next cursor:
{
"data": [...],
"meta": {
"next_cursor": "eyJpZCI6MTQzfQ",
"has_more": true
}
}
Filtering and Sorting
// Filter by multiple fields:
GET /orders?status=shipped&user_id=123
// Sort results:
GET /users?sort=created_at&order=desc
// Combine filtering, sorting, and pagination:
GET /products?category=electronics&sort=price&order=asc&page=1&limit=50
Rate Limiting
Rate limiting protects your API from abuse, ensures fair usage, and prevents DDoS attacks. See our Rate Limiting guide for implementation details.
Every rate-limited API should return standard headers on every response:
HTTP/1.1 200 OK
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 856
X-RateLimit-Reset: 1711277400
When a client exceeds their rate limit:
HTTP/1.1 429 Too Many Requests
Retry-After: 60
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1711277400
Content-Type: application/json
{
"errors": [{
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests. Please retry after 60 seconds."
}]
}
API Design Checklist
Use this checklist before releasing any REST API to production:
- URLs use nouns (not verbs), plural form, lowercase letters, and hyphens
- HTTP methods are used correctly: GET reads, POST creates, PUT replaces, PATCH updates, DELETE removes
- POST for everything is NOT used — correct HTTP methods throughout
- All responses follow a consistent envelope structure:
{ data, meta, errors } - Naming convention (snake_case or camelCase) is consistent in all responses
- Timestamps use ISO 8601 format in UTC
- HTTP status codes accurately reflect the outcome (no 200 for errors)
- All error responses include a machine-readable
codeand human-readablemessage - API is versioned from the start (
/v1/URL path) - Authentication is implemented on all non-public endpoints
- HTTPS is enforced — HTTP is not supported
- All list endpoints support pagination with consistent parameters
- Rate limiting is implemented with standard response headers
- API is documented (OpenAPI/Swagger spec or equivalent)
- All inputs are validated server-side
- Error responses never expose stack traces or internal details
Frequently Asked Questions
What is the most important rule in REST API design?
Consistency. A well-designed REST API should be predictable — if you understand one endpoint, you should be able to guess how others work. Use the same naming conventions, response structure, error format, and status codes throughout your entire API. Inconsistency is the single biggest source of developer frustration with APIs.
Should I use camelCase or snake_case in REST APIs?
Both are valid, but you must pick one and use it everywhere. snake_case (created_at, user_name) is more common in REST APIs and is the convention used by GitHub, Stripe, and Twilio. camelCase (createdAt, userName) is common in JavaScript-heavy ecosystems. The most important rule is consistency — never mix them within the same API.
How many endpoints should a REST API have?
There is no fixed number. A REST API should have one set of endpoints per resource type. A typical resource generates five operations: list (GET collection), create (POST collection), read (GET single), update (PUT/PATCH single), delete (DELETE single). The total count scales with the number of resource types in your domain model. Avoid creating dozens of purpose-specific endpoints — if you find yourself doing this, consider whether your resource model is too granular.
Should REST API endpoints be versioned from the start?
Yes. Version your API from day one, even for internal APIs. Adding versioning to an unversioned API requires updating all existing clients simultaneously — a coordination problem that scales poorly. Starting with /v1/ costs almost nothing and gives you the ability to make breaking changes in /v2/ without disrupting existing consumers. The question is never "if" you'll need a v2, but "when."
Related Guides
HTTP Methods
Complete guide to GET, POST, PUT, PATCH, and DELETE with examples.
Status Codes
Every HTTP status code with when to use it in REST APIs.
Authentication
API keys, JWT, OAuth 2.0 — securing your REST API.
Security
HTTPS, input validation, OWASP Top 10, and security checklist.
Pagination
Offset, cursor, and keyset pagination patterns.
Versioning
URL path, header, and query param versioning strategies.
Rate Limiting
Implementing and communicating rate limits in REST APIs.
Error Handling
Consistent error formats and client-side handling strategies.