REST API Design Best Practices: A Practical Guide for 2026
REST API design best practices: resource naming, status codes, error format, pagination, versioning, auth, rate limiting, idempotency and OpenAPI docs.

REST API design best practices boil down to one idea: be predictable. Use nouns for resources, HTTP methods for actions, correct status codes, one consistent error format, and the same pagination, filtering and authentication patterns on every endpoint. Add versioning, rate limiting, idempotency for retries and an OpenAPI description, and your API becomes easy to integrate with and cheap to maintain. Here is how we approach each of these.
1. Resource naming
Model your API around resources (things), not actions.
- Use plural nouns:
/customers,/invoices,/invoices/{id}. - Nest only one level for clear ownership:
/customers/{id}/invoices. Deeper nesting gets awkward; use filters instead (/invoices?customer_id=42). - Use lowercase and hyphens in paths:
/payment-methods, not/PaymentMethodsor/payment_methods. - Pick one JSON field casing (
snake_caseorcamelCase) and never mix. - Use stable, opaque IDs. UUIDs or ULIDs avoid leaking record counts and make IDs hard to guess.
For operations that really are actions, model them as a sub-resource or state change instead of a verb in the URL:
POST /invoices/123/payments # record a payment
POST /orders/456/cancellation # cancel an order
PATCH /users/789 {"status": "suspended"}
2. Use HTTP methods as intended
The semantics are defined in RFC 9110:
| Method | Use | Safe | Idempotent |
|---|---|---|---|
GET |
Read a resource or collection | Yes | Yes |
POST |
Create a resource or trigger processing | No | No |
PUT |
Replace a resource entirely | No | Yes |
PATCH |
Partially update a resource | No | Not necessarily |
DELETE |
Remove a resource | No | Yes |
Never change data on GET. Crawlers, prefetchers and caches assume GET is safe.
3. Return the right status codes
Clients branch on status codes, so be precise. The ones you need most:
| Code | When |
|---|---|
200 OK |
Successful read or update with a body |
201 Created |
Resource created; include a Location header |
202 Accepted |
Work queued for asynchronous processing |
204 No Content |
Success with no body (e.g. delete) |
400 Bad Request |
Malformed request (invalid JSON, wrong types) |
401 Unauthorized |
Missing or invalid authentication |
403 Forbidden |
Authenticated but not allowed |
404 Not Found |
Resource does not exist (or the caller may not know it exists) |
409 Conflict |
State conflict, e.g. duplicate or version mismatch |
422 Unprocessable Content |
Well-formed but fails validation |
429 Too Many Requests |
Rate limit exceeded |
500 Internal Server Error |
Unexpected server failure |
503 Service Unavailable |
Temporary outage or maintenance |
Do not return 200 with {"success": false} in the body. It breaks monitoring, client libraries and retries.
4. One consistent error format
Every error from every endpoint should have the same shape. Rather than inventing one, use Problem Details from RFC 9457, which replaced RFC 7807. It uses the application/problem+json media type:
HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json
{
"type": "https://api.example.com/problems/validation-error",
"title": "Your request is not valid.",
"status": 422,
"detail": "Two fields failed validation.",
"instance": "/invoices",
"errors": [
{ "field": "due_date", "message": "Must be a date after today." },
{ "field": "lines", "message": "At least one line is required." }
]
}
Rules we follow:
- A stable machine-readable
type(or code) that clients can switch on. - Human-readable
title/detailthat never leak stack traces, SQL or internal hostnames. - Field-level errors for validation failures.
- A request or trace ID in a header (e.g.
X-Request-Id) so support can find the log line.
5. Pagination, filtering and sorting
Never return unbounded collections. Two common pagination styles:
Offset pagination (?page=3&per_page=50) is simple and supports jumping to a page, but gets slow on large tables and can skip or repeat items when data changes.
Cursor pagination (?cursor=eyJpZCI6MTIzfQ&limit=50) uses an opaque pointer to the last item. It stays fast and consistent on large, changing datasets, and is our default for feeds and large lists.
{
"data": [ { "id": "inv_01", "total": 120.00 } ],
"meta": { "limit": 50 },
"links": { "next": "/invoices?cursor=eyJpZCI6Imludl8wMSJ9&limit=50" }
}
Keep filtering and sorting predictable:
- Filters as query parameters:
?status=paid&created_after=2026-01-01. - Sorting with a clear convention:
?sort=-created_at,total(minus for descending). - Enforce a maximum page size on the server.
- Optionally support sparse fieldsets:
?fields=id,total,status.
6. Versioning
You will need to make breaking changes eventually. Decide how before launch.
- URL versioning (
/v1/invoices) is the most visible and easiest to route, cache and document. It is what we use for most public APIs. - Header or media-type versioning keeps URLs clean but is harder to test in a browser and to cache.
Whatever you choose:
- Add fields freely — additive changes should not need a new version, and clients should ignore unknown fields.
- Only bump the major version for breaking changes (removing or renaming fields, changing types or semantics).
- Announce deprecations early, and consider the
DeprecationandSunsetresponse headers to signal them. - Run old and new versions in parallel for a published period.
7. Authentication and authorisation
- HTTPS only. Reject or redirect plain HTTP.
- For third-party or user-delegated access, use OAuth 2.0 with short-lived access tokens and refresh tokens.
- For server-to-server integrations, API keys or client credentials are fine — send them in a header (
Authorization: Bearer …), never in the URL where they end up in logs. - For first-party SPAs and mobile apps, token-based auth such as Laravel Sanctum keeps things simple.
- Check authorisation on every request and every object. Broken object-level authorisation — changing
/invoices/123to/invoices/124and seeing someone else’s data — is one of the most common API vulnerabilities. See the OWASP API Security Top 10. - Scope keys and tokens to the minimum permissions needed, and make them easy to rotate.
8. Rate limiting
Rate limits protect your service and your other customers.
- Limit per API key or user, with stricter limits on expensive or sensitive endpoints (login, search, exports).
- Return
429 Too Many Requestswith aRetry-Afterheader. - Tell clients where they stand. Many APIs send
X-RateLimit-Limit/X-RateLimit-Remainingheaders; the IETF is standardisingRateLimitandRateLimit-Policyfields in an Internet-Draft that is still in progress as of September 2026.
9. Idempotency for safe retries
Networks fail. If a client sends POST /payments and the connection drops, it does not know whether the payment happened. Retrying without protection can charge the customer twice.
The common solution is an idempotency key: the client sends a unique value with the request, and the server stores the result keyed by it. A repeated request with the same key returns the stored result instead of repeating the action.
POST /payments
Idempotency-Key: 8e03978e-40d5-43e8-bc93-6894a57f9324
Content-Type: application/json
{ "invoice_id": "inv_01", "amount": 120.00 }
Implementation notes:
- Store the key, a hash of the request body and the response for a limited time (for example, 24 hours).
- If the same key arrives with a different body, return an error (for example
422or409). - Handle concurrent requests with the same key using a lock.
- An
Idempotency-Keyheader field is being standardised in an IETF draft; the pattern is already widely used by payment APIs.
10. Document with OpenAPI
An API without accurate docs is an API people will integrate with incorrectly. Describe it with the OpenAPI Specification — version 3.2.0 was released in September 2025, and 3.1 remains widely supported by tools.
- Keep the OpenAPI file in the same repository as the code and review changes in pull requests.
- Generate interactive docs, client SDKs and mock servers from it.
- Validate requests and responses against the spec in automated tests so docs never drift.
- Include realistic examples, error responses and authentication requirements for every endpoint.
11. Operational details that save pain later
- Timestamps in ISO 8601 with time zone (
2026-09-10T08:30:00Z). - Money as integer minor units or decimal strings, with a currency code — never floating point.
- Compression (gzip or Brotli) and
ETag/If-None-Matchfor cacheable reads. - Webhooks signed with an HMAC so receivers can verify them, retried with backoff.
- Logging and tracing with request IDs across services.
- Health endpoint (
/health) for load balancers and monitoring.
REST API design checklist
- Plural nouns, shallow nesting, consistent casing
- Correct HTTP methods; no side effects on
GET - Precise status codes; no
200for errors - RFC 9457 Problem Details for every error
- Pagination on every collection, with a max page size
- A versioning strategy and deprecation policy
- HTTPS, tokens in headers, object-level authorisation checks
- Rate limits with
429andRetry-After - Idempotency keys on non-idempotent writes such as payments
- OpenAPI description kept in sync by tests
Building or fixing an API?
Good API design is much cheaper at the start than after a dozen clients depend on your mistakes. If you are planning a new API, or need an existing one cleaned up, documented and secured, our REST API development service can help. Get in touch and tell us what your API needs to do.