REST API Best Practices
A practical guide to designing maintainable REST APIs with consistent resources, HTTP methods, status codes, validation, error handling, pagination, security, caching, and documentation.
A REST API can work correctly and still be difficult to use, maintain, or extend. Problems often appear when resource names are inconsistent, HTTP methods are used incorrectly, error responses have different shapes, pagination behaves differently between endpoints, or clients have to guess how an API is supposed to work.
REST API best practices are mostly about consistency and predictable behavior. A well-designed API gives clients clear resource URLs, appropriate HTTP methods, meaningful status codes, stable response structures, useful errors, and documented rules.
This guide covers practical REST API design principles that can be applied to new APIs as well as existing APIs that need to become easier to consume and maintain.
What Makes a REST API Well Designed?
A good REST API should make common operations predictable. Developers should be able to understand how to retrieve, create, update, and delete resources without learning a different convention for every endpoint.
| Area | Recommended approach |
|---|---|
| Resources | Use clear and consistent resource-oriented URLs |
| HTTP methods | Use methods according to their intended semantics |
| Status codes | Return status codes that describe the result |
| Responses | Keep response structures predictable |
| Errors | Use a consistent error format |
| Validation | Validate input at the API boundary |
| Pagination | Use a documented and consistent pagination strategy |
| Security | Use authentication, authorization, and transport security |
| Documentation | Document endpoints, parameters, responses, and errors |
Use Resource-Oriented URLs
REST APIs generally work with resources rather than actions. URLs should identify the resource being operated on, while the HTTP method describes the operation.
GET /users
GET /users/42
POST /users
PATCH /users/42
DELETE /users/42This is usually clearer than putting actions directly into every URL. For example, an API does not normally need separate endpoints such as /getUsers, /createUser, and /deleteUser when the HTTP method already communicates the intended operation.
| Less consistent | Resource-oriented |
|---|---|
| GET /getUsers | GET /users |
| POST /createUser | POST /users |
| POST /deleteUser/42 | DELETE /users/42 |
| POST /updateUser/42 | PATCH /users/42 |
Keep Resource Names Consistent
Choose a naming convention and use it throughout the API. Mixing singular and plural resource names makes an API harder to learn and increases the chance of client-side mistakes.
GET /users
GET /users/42
GET /users/42/orders
GET /orders/1001Plural nouns are a common convention for collection resources. The important point is not that one naming style is universally mandatory, but that the same convention should be applied consistently.
Use HTTP Methods Correctly
HTTP methods already provide semantics that REST APIs can use to communicate intent. GET is generally used to retrieve representations, POST to create resources or perform operations that do not fit another method, PUT to replace a resource, PATCH to partially modify a resource, and DELETE to remove a resource.
| Method | Typical use | Safe | Idempotent |
|---|---|---|---|
| GET | Retrieve a resource | Yes | Yes |
| POST | Create or submit data | No | No |
| PUT | Replace a resource | No | Yes |
| PATCH | Partially modify a resource | No | Not inherently |
| DELETE | Remove a resource | No | Yes |
The distinction between PUT and PATCH is particularly important. PUT conventionally represents replacement of the target resource, while PATCH is intended for partial modifications. The exact implementation should be documented so clients know what fields are required and how omitted fields are treated.
Choose Status Codes Deliberately
HTTP status codes communicate the high-level result of a request. Returning the same status code for successful operations, validation failures, missing resources, and server errors makes it much harder for clients to respond correctly.
| Status | Typical API meaning |
|---|---|
| 200 OK | Request succeeded |
| 201 Created | A resource was successfully created |
| 204 No Content | Request succeeded without a response body |
| 400 Bad Request | Request is invalid or cannot be processed as submitted |
| 401 Unauthorized | Authentication is required or invalid |
| 403 Forbidden | The authenticated client is not allowed to perform the operation |
| 404 Not Found | The requested resource was not found |
| 409 Conflict | Request conflicts with the current resource state |
| 422 Unprocessable Content | Request syntax is valid but the content cannot be processed |
| 429 Too Many Requests | The client exceeded a rate limit |
| 500 Internal Server Error | Unexpected server-side failure |
Return Consistent Response Structures
Clients become easier to implement when similar endpoints return data in predictable structures. Inconsistent response shapes force frontend and backend developers to add special cases for individual endpoints.
{
"id": 42,
"name": "Alice",
"email": "[email protected]"
}The exact JSON structure is a design decision, but fields should have stable meanings and types. If an endpoint returns an object in one situation and an array in another without a documented reason, clients have to handle unnecessary ambiguity.
Keep Error Responses Predictable
Error handling deserves the same consistency as successful responses. A client should be able to inspect an error response and determine what happened without parsing completely different formats for every endpoint.
{
"error": {
"code": "INVALID_EMAIL",
"message": "The email address is invalid",
"field": "email"
}
}An error response can contain a machine-readable code, a human-readable message, and additional information about the affected field or resource. The exact structure should be standardized across the API.
Validate Input at the API Boundary
Every API should validate data received from clients before using it. Validation should cover required fields, data types, allowed values, string lengths, numeric ranges, formats, and other business constraints that are appropriate for the endpoint.
{
"name": "Alice",
"email": "[email protected]",
"age": 28
}Validation should produce useful errors rather than simply returning a generic failure. If several fields are invalid, an API can return structured information describing which fields failed and why.
Client-side validation can improve user experience, but it should not replace server-side validation. The API must treat incoming data as untrusted regardless of whether another application already validated it.
Use Pagination for Large Collections
Returning thousands or millions of records from one endpoint can increase response size, memory usage, processing time, and network traffic. Collection endpoints should therefore use pagination when the result set can become large.
GET /users?page=2&limit=25A paginated response can include the requested records together with metadata describing the result set. The exact pagination model can be page-based, offset-based, cursor-based, or another documented approach.
{
"data": [
{
"id": 26,
"name": "User 26"
}
],
"pagination": {
"page": 2,
"limit": 25,
"total": 125
}
}Cursor-based pagination can be more suitable for large or frequently changing datasets because it does not depend on a fixed numeric offset. Whichever strategy is selected, clients should be able to understand how to request the next set of results.
Define Filtering, Sorting, and Search Consistently
Collection endpoints often need filtering and sorting in addition to pagination. Consistent query parameter conventions make these features easier to discover and use.
GET /products?category=books&sort=price&order=asc
GET /users?status=active&sort=createdAt&order=descAvoid giving every endpoint a completely different query parameter vocabulary unless there is a strong reason. If one endpoint uses sort and another uses orderBy for the same concept, clients have to learn unnecessary differences.
Avoid Uncontrolled Response Sizes
Pagination is one way to control response size, but APIs can also use field selection or expansion rules when resources contain large amounts of data. Returning every possible field for every request can waste bandwidth and processing time.
If an API supports field selection, the syntax and behavior should be documented clearly. The server should also enforce sensible limits so that one request cannot unexpectedly produce an enormous response.
Design Nested Resources Carefully
Nested URLs can express relationships between resources, but excessive nesting can make URLs difficult to understand and maintain.
GET /users/42/orders
GET /users/42/orders/1001A shallow relationship can be useful when the parent resource provides important context. However, deeply nested URLs can become cumbersome.
GET /users/42/orders/1001/items/7/reviews/3When nesting becomes excessive, consider whether the child resource can be addressed directly or whether filtering can express the relationship more simply.
Use Authentication and Authorization Separately
Authentication answers the question of who the client is. Authorization determines what that authenticated client is allowed to do. These are related but separate concerns.
For example, a user may be authenticated successfully but still be forbidden from deleting another user's resources. The API should enforce authorization on the server rather than assuming that a valid authentication token grants access to every endpoint.
| Situation | Typical response |
|---|---|
| No valid authentication | 401 Unauthorized |
| Authenticated but not permitted | 403 Forbidden |
| Authenticated and permitted | Process the requested operation |
Protect API Credentials
API keys, access tokens, client secrets, and other credentials should not be exposed unnecessarily. They should be transmitted over HTTPS and stored using appropriate secret-management mechanisms rather than being hard-coded into public client applications.
Use HTTPS
APIs that handle authentication credentials, personal information, or other sensitive data should use HTTPS. Encryption protects data in transit and helps prevent attackers from reading or modifying requests and responses while they travel between client and server.
HTTPS should be treated as a baseline transport-security requirement rather than an optional optimization. Redirecting HTTP traffic to HTTPS can help enforce the secure scheme, but sensitive endpoints should be designed to operate securely from the beginning.
Apply Rate Limiting
Rate limiting restricts how frequently a client can make requests during a defined period. It can protect APIs from accidental overload, abusive traffic, brute-force attempts, and unexpectedly expensive operations.
HTTP/1.1 429 Too Many Requests
Retry-After: 60A useful rate-limiting design should document the relevant limits and explain what clients should do after receiving a 429 response. The limits may differ between authentication endpoints, read operations, write operations, and resource-intensive operations.
Use Caching Where Appropriate
Caching can reduce repeated work and improve response times when data does not need to be regenerated for every request. HTTP provides caching mechanisms such as Cache-Control and ETag that can be used to communicate caching behavior between servers, clients, and intermediary caches.
Cache-Control: max-age=300
ETag: "users-v42"Caching should reflect how frequently the underlying data changes and whether the response contains information that should be cached. Sensitive or highly dynamic data requires more careful cache-control rules.
Make Idempotency Clear
Idempotency means that making the same request multiple times has the same intended effect as making it once. HTTP defines different semantics for methods, and API designers should understand those semantics when implementing operations.
This becomes particularly important for operations that may be retried because of network failures. For example, a client may not know whether a request reached the server before the connection failed. For certain create or payment-like operations, an idempotency mechanism can prevent accidental duplicate processing.
POST /orders
Idempotency-Key: 8f4c2b1e-7d6a-4c1a-9b32-123456789abcVersion APIs Deliberately
APIs evolve as applications change. New fields can often be added without breaking existing clients, but removing fields, changing their meanings, or altering response structures can be a breaking change.
There are several approaches to API versioning, including URL-based versions, headers, or content negotiation. The important part is to define a clear compatibility policy and communicate breaking changes before clients are forced to migrate.
GET /api/v1/users
GET /api/v2/usersVersioning does not eliminate the need for backward compatibility. If every minor change creates a new API version, maintaining multiple versions can become expensive. Prefer compatible additions when possible and reserve breaking changes for cases where they are actually necessary.
Prefer Backward-Compatible Changes
Adding an optional response field is generally less disruptive than renaming or removing an existing field. Similarly, adding a new endpoint is usually less disruptive than changing the behavior of an existing endpoint.
| Change | Compatibility concern |
|---|---|
| Add an optional response field | Usually low |
| Add a new endpoint | Usually low |
| Add an optional request field | Usually low |
| Remove a response field | High |
| Rename a field | High |
| Change a field type | High |
| Change existing endpoint semantics | High |
Document the API with OpenAPI
API documentation should describe how endpoints work rather than forcing developers to infer behavior from examples. OpenAPI is a widely used specification format for describing HTTP APIs, including paths, parameters, request bodies, responses, schemas, and authentication requirements.
paths:
/users/{id}:
get:
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
"200":
description: User found
"404":
description: User not foundA machine-readable API description can support documentation interfaces, client generation, validation, testing, and collaboration between frontend and backend teams.
Document Examples and Edge Cases
Good API documentation should not describe only the successful path. Developers also need to know what happens when authentication fails, a resource does not exist, validation fails, pagination reaches the end, or a rate limit is exceeded.
Examples are especially useful for request bodies and error responses. A small realistic example can communicate expected data types and structure more quickly than a long prose explanation.
Keep JSON Naming Consistent
JSON field naming should follow a consistent convention throughout the API. Common choices include camelCase and snake_case. Either can work, but switching conventions between endpoints creates unnecessary client-side complexity.
{
"firstName": "Alice",
"lastName": "Smith",
"createdAt": "2026-09-21T10:30:00Z"
}Consistency should also apply to related concepts. If one endpoint calls a timestamp createdAt, another endpoint should not use creationDate for the same concept without a specific reason.
Handle Dates and Times Explicitly
Date and time values can cause interoperability problems when APIs do not define their format and timezone behavior clearly. ISO 8601-style representations are commonly used because they provide an unambiguous textual format.
{
"createdAt": "2026-09-21T10:30:00Z"
}The API documentation should make clear whether timestamps represent UTC, include an explicit offset, or follow another convention. Clients should not have to guess how a timestamp should be interpreted.
Avoid Leaking Internal Database Structure
An API does not have to expose database tables and columns directly. Internal storage structures can change while the public API remains stable. Treating the API as its own contract makes it easier to change the implementation later.
For example, an internal database might split a person's information across several tables, while the API can expose a single user resource containing the fields clients actually need. This separation also prevents accidental exposure of internal fields.
Separate Public API Contracts from Internal Models
Database models, internal service objects, and API response models often have different requirements. Mapping internal data to an explicit API representation gives developers control over which fields are exposed and how they are named.
Use Consistent Request and Response Headers
HTTP headers communicate metadata such as content type, caching rules, authorization information, correlation identifiers, and content negotiation preferences. APIs should use standard headers where possible and document custom headers that clients are expected to understand.
Content-Type: application/json
Accept: application/json
Authorization: Bearer <token>Using standard HTTP mechanisms reduces the amount of custom behavior clients need to learn. Custom headers can still be useful when they represent application-specific requirements, but they should have clearly documented semantics.
Add Request Correlation IDs
A correlation or request identifier can make production debugging easier by allowing a specific API request to be connected with server-side logs and downstream operations.
X-Request-ID: 7f2c1a9eThe exact header name and propagation strategy can vary between systems. The important principle is that logs should contain enough context to trace a request without exposing sensitive information.
Test API Behavior
API testing should cover more than successful requests. Tests should verify validation failures, authentication and authorization behavior, missing resources, conflicts, malformed input, pagination boundaries, rate limits, and important business rules.
Automated tests are particularly valuable for API contracts because a backend change can otherwise break clients without immediately producing an obvious server-side error. Testing representative request and response structures helps detect these changes early.
Test APIs with Mock Responses
Mock APIs and mock responses can help frontend developers work before the production backend is complete. They are also useful for testing loading states, empty results, validation errors, authentication failures, and other conditions that may be difficult to reproduce against a live system.
{
"error": {
"code": "USER_NOT_FOUND",
"message": "The requested user does not exist"
}
}A mock should reflect the real API contract as closely as possible. Otherwise, frontend code may be built around response shapes that later turn out to be incorrect.
Design for Empty Results
An empty collection is usually a valid result rather than an error. Clients should be able to distinguish between a successful request that returned no records and a request that failed.
{
"data": [],
"pagination": {
"page": 1,
"limit": 25,
"total": 0
}
}The API should document how empty collections, missing optional fields, null values, and missing resources are represented. Consistent conventions prevent client-side ambiguity.
Do Not Overuse HTTP Status Codes
Although HTTP provides many status codes, an API does not need a unique status for every business condition. A small, well-documented set of status codes is often easier for clients to handle.
Business-specific information can be placed in a structured response body while the HTTP status communicates the general category of the result. This separates protocol-level information from application-specific details.
Make APIs Observable
Production APIs should provide enough telemetry to understand performance and failures. Useful information can include request duration, status code, endpoint, request identifier, and carefully selected application metrics.
Logging should be balanced with privacy and security requirements. Request bodies, authorization tokens, passwords, and other sensitive information should not be logged indiscriminately.
Avoid Breaking Changes Without a Migration Path
When a breaking change is unavoidable, clients should have a documented migration path. Depending on the API, this may involve a new version, a deprecation period, migration documentation, or a temporary compatibility layer.
Deprecation should be communicated clearly. Clients need enough information to identify affected endpoints and understand what they should use instead.
REST API Design Checklist
- Use consistent resource-oriented URLs.
- Choose HTTP methods according to their intended semantics.
- Return meaningful and documented HTTP status codes.
- Keep request and response structures predictable.
- Use a consistent error response format.
- Validate all untrusted input on the server.
- Paginate potentially large collections.
- Use consistent filtering and sorting conventions.
- Separate authentication from authorization.
- Protect credentials and sensitive data.
- Use HTTPS for API communication.
- Apply appropriate rate limits.
- Use caching when the data and endpoint support it.
- Document API contracts with OpenAPI or another suitable specification.
- Test successful and unsuccessful request paths.
- Preserve backward compatibility where practical.
Frequently Asked Questions
What are the most important REST API best practices?
Use consistent resource-oriented URLs, appropriate HTTP methods, meaningful status codes, predictable response and error structures, server-side validation, authentication and authorization, pagination, security controls, and clear documentation.
Should REST API URLs use nouns or verbs?
Resource-oriented REST APIs generally use nouns to identify resources, while HTTP methods describe the operation. For example, GET /users retrieves users and POST /users creates a user.
Should I use PUT or PATCH for updates?
PUT is conventionally used when replacing a resource representation, while PATCH is intended for partial modifications. The API should document the exact behavior because implementations can differ.
What status code should an API return after creating a resource?
201 Created is commonly used when a request successfully creates a resource. The response can also provide a representation of the created resource and information about its location when appropriate.
How should REST APIs handle errors?
Use meaningful HTTP status codes together with a consistent structured error body. Machine-readable error codes, human-readable messages, and field-specific validation details can make errors easier for clients to handle.
Should every REST API use pagination?
Pagination is especially important for collection endpoints whose result sets can become large. Small, fixed-size collections may not need it, but APIs should consider future growth rather than assuming a collection will always remain small.
Is OpenAPI required for a REST API?
OpenAPI is not required to implement a REST API, but a machine-readable API specification can make documentation, validation, testing, client generation, and collaboration significantly easier.
Helpful REST API Development Tools
Different stages of API development benefit from different tools. REST API mock generators can provide test endpoints and sample responses before a backend is complete, while HTTP request builders make it easier to construct and inspect requests. HTTP response formatters can improve the readability of returned data, OpenAPI viewers can make API specifications easier to explore, and JSON formatters can help inspect and validate JSON request and response bodies during development and debugging.
Conclusion
REST API best practices are primarily about creating a predictable contract between clients and servers. Consistent resource URLs, HTTP methods, status codes, response structures, validation rules, and error formats reduce the amount of special-case logic that clients need to implement.
Security, pagination, caching, versioning, testing, and documentation become increasingly important as an API grows. The goal is not to apply every possible convention mechanically, but to establish clear rules that remain consistent across the API and can evolve without unnecessarily breaking existing clients.