15 Best Practices for Designing REST APIs in 2026
A comprehensive guide to designing professional, consistent, and easy-to-consume REST APIs.
Introduction
A well-designed API is a pleasure to use: it's intuitive, consistent, and predictable. Developers consuming it can guess how an endpoint works before reading the documentation because it follows clear conventions. Conversely, a poorly designed API generates frustration, bugs, and constant support requests.
In this article we compile the 15 best practices every backend developer should follow when designing REST APIs in 2026. These conventions are proven by the world's most successful APIs (Stripe, GitHub, Shopify) and will help you create interfaces that other developers enjoy using.
1. Use Plural Nouns for Resources
Endpoints should represent resource collections using plural nouns. Avoid verbs in the URL, since the HTTP method already indicates the action. This makes the API predictable and follows REST conventions.
Correct: GET /users, POST /users, GET /users/123, DELETE /users/123
Incorrect: GET /getUser, POST /createUser, GET /getUserById/123
The exception is actions that don't naturally fit into CRUD, like POST /users/123/activate or POST /orders/456/refund. These action sub-resources are acceptable when the operation can't be cleanly expressed with standard HTTP methods.
2. Use HTTP Status Codes Correctly
HTTP status codes communicate the operation result in a standardized way. Using them correctly allows clients to handle responses generically without parsing the body.
2xx - Success: 200 OK (GET, successful PUT), 201 Created (successful POST, include Location header), 204 No Content (successful DELETE, no response body).
4xx - Client error: 400 Bad Request (invalid data), 401 Unauthorized (not authenticated), 403 Forbidden (authenticated but lacking permissions), 404 Not Found (resource doesn't exist), 409 Conflict (state conflict, such as duplicate), 422 Unprocessable Entity (validation failed), 429 Too Many Requests (rate limit exceeded).
5xx - Server error: 500 Internal Server Error (unexpected error), 502 Bad Gateway, 503 Service Unavailable (maintenance or overload).
3. Implement Pagination on Collections
Never return a full collection without pagination. Even if you have 100 records today, tomorrow you might have 100,000. Pagination protects both the server (memory, CPU) and the client (bandwidth, rendering).
Offset-based pagination: GET /users?page=2&limit=20. Simple to implement but inefficient for large datasets (high offsets are expensive in SQL) and prone to duplicates if records are added/removed during pagination.
Cursor-based pagination: GET /users?cursor=eyJpZCI6MTAwfQ&limit=20. More efficient and consistent, ideal for feeds and large lists. The cursor is an opaque token that points to the exact position in the dataset.
Always include pagination metadata in the response: total items, whether there are more pages, and next/prev links following the RFC 8288 standard with Link headers.
4. Version Your API from Day One
APIs evolve and breaking changes are inevitable. Versioning allows you to evolve the API without breaking existing clients. The most common and recommended strategy is URL versioning: /api/v1/users, /api/v2/users.
Alternatives include header versioning (Accept: application/vnd.myapi.v2+json) or query parameter versioning (/api/users?version=2). Each approach has trade-offs, but URL versioning is the most explicit and easy to understand.
Establish a clear deprecation policy: communicate in advance when a version will be removed, include Sunset and Deprecation headers in deprecated version responses, and provide clear migration guides.
5. Use Robust Authentication and Authorization
Authentication: Use JWT (JSON Web Tokens) with RS256 signing for stateless APIs, or sessions with httpOnly cookies for web applications. OAuth 2.0 with PKCE is the standard for third-party authorization. Never implement your own authentication system; use proven and audited libraries.
Authorization: Implement role-based (RBAC) or attribute-based (ABAC) access control. Each endpoint must explicitly verify that the authenticated user has permissions for the requested operation. Return 403 Forbidden (not 404) when the user lacks permissions, unless revealing the resource's existence is a security risk.
Always use HTTPS. Never send tokens or credentials over plain HTTP. Implement token rotation with short-lived refresh tokens and active revocation.
6. Design Consistent Error Responses
Define a standard error format and use it throughout the API. The RFC 7807 standard (Problem Details for HTTP APIs) is an excellent foundation that includes fields like type (URI identifying the error type), title (short description), status (HTTP code), detail (detailed description), and instance (URI of the specific error instance).
For validation errors, include an array of specific field-level errors: {"field": "email", "message": "Invalid email format", "code": "INVALID_FORMAT"}. This allows the client to display specific errors next to each form field.
Always include a machine-readable error code (not just the HTTP status) that the client can use for conditional logic. For example, USER_NOT_FOUND, EMAIL_ALREADY_EXISTS, INSUFFICIENT_PERMISSIONS.
7. Use Query Parameters for Filtering, Sorting, and Searching
Query parameters are the standard mechanism for modifying a collection query. Use descriptive and consistent names: GET /products?category=electronics&min_price=100&max_price=500&sort=-created_at&fields=id,name,price.
The - prefix for descending sort is a widely adopted convention. The fields parameter (sparse fieldsets) allows the client to request only the fields it needs, reducing response size and improving performance.
For complex searches that don't fit in query parameters, consider a POST search endpoint: POST /products/search with a JSON body expressing the complex query. This is a pragmatic deviation from pure REST, but it's an accepted industry practice.
8. Use camelCase for JSON, kebab-case for URLs
URLs should use kebab-case (lowercase with hyphens) because it's more readable and consistent with web conventions: /api/v1/user-profiles, /api/v1/shipping-addresses.
JSON fields should use camelCase (JavaScript/TypeScript convention): {"firstName": "Carlos", "lastName": "Garcia", "createdAt": "2026-01-15T10:30:00Z"}. If your API will be consumed primarily by Python or Ruby applications, snake_case is a valid alternative, but be consistent.
Dates should always follow ISO 8601 format: 2026-01-15T10:30:00Z for UTC timestamps, 2026-01-15T11:30:00+01:00 for timestamps with timezone. Never use Unix timestamps (seconds since epoch) as the primary format, although you can offer them as an additional field.
9. Implement Rate Limiting
Rate limiting protects your API from abuse, brute force attacks, and excessive usage by poorly coded clients. Implement limits per IP, per authenticated user, and per API key.
Communicate rate limiting limits via standard headers: X-RateLimit-Limit (total limit), X-RateLimit-Remaining (remaining requests), X-RateLimit-Reset (timestamp when the counter resets). When the limit is exceeded, return 429 Too Many Requests with a Retry-After header.
Consider different rate limiting tiers: public endpoints with more restrictive limits, authenticated endpoints with generous limits, and premium endpoints with elevated limits for paying clients.
10. Use HATEOAS Selectively
HATEOAS (Hypermedia as the Engine of Application State) is the REST principle of including links to related actions in responses. While pure HATEOAS is rarely implemented, including relevant links improves API discoverability.
A pragmatic approach is to include a _links or links object with available actions: {"id": 123, "name": "Order #123", "links": {"self": "/api/v1/orders/123", "items": "/api/v1/orders/123/items", "cancel": "/api/v1/orders/123/cancel"}}. This allows the client to dynamically discover available actions.
11. Support PATCH for Partial Updates
PUT replaces the entire resource, while PATCH partially updates it. In practice, most updates are partial (changing only the email, only the name), so PATCH is more efficient and safer. Implement both methods if your API needs it, but PATCH should be the primary method for updates.
Consider supporting JSON Patch (RFC 6902) for complex update operations: [{"op": "replace", "path": "/email", "value": "new@email.com"}, {"op": "remove", "path": "/phone"}]. This enables precise, atomic operations on the resource.
12. Implement Idempotency in Write Operations
PUT and DELETE operations are idempotent by definition: executing them multiple times produces the same result. For POST, implement idempotency via an Idempotency-Key header that the client generates (usually a UUID). If the server receives two requests with the same key, it returns the same response without duplicating the operation.
This is especially important for payment operations, order creation, and any action where duplication has serious consequences. Stripe popularized this pattern and it's now a standard practice in financial and e-commerce APIs.
13. Document with OpenAPI/Swagger
An API without documentation is an API nobody wants to use. OpenAPI 3.1 (formerly Swagger) is the standard for documenting REST APIs. Generate the specification from your code using libraries like swagger-jsdoc, drf-spectacular (Django), or springdoc-openapi (Spring Boot).
Offer an interactive interface with Swagger UI or Redoc where developers can try endpoints directly from the browser. Include request and response examples for every endpoint, clear parameter descriptions, and possible error codes.
14. Use Compression and Caching
Compression: Enable gzip or brotli compression on all responses. Brotli offers better compression ratios than gzip and is supported by all modern browsers. For APIs with large JSON responses, compression can reduce response size by 70-90%.
Caching: Implement proper HTTP caching headers: Cache-Control, ETag, and Last-Modified for resources that change infrequently. Use ETags with conditional requests (If-None-Match) to return 304 Not Modified when the resource hasn't changed, saving bandwidth and processing time.
15. Implement CORS Correctly
CORS (Cross-Origin Resource Sharing) is essential for APIs consumed from web browsers. Configure CORS headers restrictively: specify allowed origins explicitly (don't use * in production), define allowed HTTP methods, and limit the headers the client can send.
For public APIs, consider using a restricted wildcard. For internal APIs, limit origins to your known domains. Handle OPTIONS (preflight) requests efficiently with an appropriate cache duration via Access-Control-Max-Age.
Conclusion
Designing a good REST API is an exercise in consistency and empathy. Think about the developers who will consume your API and make their job as easy as possible. Follow established conventions, document thoroughly, and provide clear, actionable error messages.
The world's most successful APIs (Stripe, GitHub, Twilio) are admired not just for their functionality but for the quality of their design and documentation. Investing time in designing your API well from the start will save you countless hours of support, bugs, and future refactoring.