Web Solutions

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.

GPTLabAI team 7 min read

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 /PaymentMethods or /payment_methods.
  • Pick one JSON field casing (snake_case or camelCase) 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/detail that 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 Deprecation and Sunset response 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/123 to /invoices/124 and 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 Requests with a Retry-After header.
  • Tell clients where they stand. Many APIs send X-RateLimit-Limit / X-RateLimit-Remaining headers; the IETF is standardising RateLimit and RateLimit-Policy fields 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 422 or 409).
  • Handle concurrent requests with the same key using a lock.
  • An Idempotency-Key header 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-Match for 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 200 for 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 429 and Retry-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.

Have a project in mind? Let’s talk.

Whether you run a business or a research group, tell us what you need built, fixed or evaluated. You get a free consultation and a clear written estimate — no obligation.

  • Free consultation
  • Written scope and estimate
  • We reply within one working day
Contact us