50 REST and OpenAPI Patterns for Production-Grade APIs
AI generated
{ }
GET
REST · OpenAPI · API Design · HTTP
50 REST and OpenAPI Patterns for Production-Grade APIs
from resource design to safe versioning

Anyone who designs REST APIs without clear patterns builds in inconsistencies that multiply over months. Shaping resource structure, status codes, error formats, pagination and versioning according to proven patterns separates APIs that survive in production from ones that produce breaking changes with every release.

18 min read Resources · Status Codes · Pagination · Versioning · Security OpenAPI 3.1 · HTTP/1.1 · HTTP/2

1. What REST patterns actually solve

A REST pattern is not an academic convention but a proven solution structure for a recurring API design problem. The difference between a consistent API and a chaotic one rarely lies in the technology used, but in the decisions made during design: How are resources named? What does a 200 response actually mean? How does the API tell the client that its request was invalid? These decisions, once made consistently and documented, reduce the cognitive load for developers who consume or maintain the API.

In practice, you see APIs where errors come back as 200 OK with a {"error": true} body, resources are named plural in one place and singular in another, and pagination works via query parameters on some endpoints and via a custom header on others. Such inconsistencies arise because every feature was implemented as an isolated decision without shared REST patterns. The following sections cover the fifty most important patterns, from resource design through error formats to OpenAPI specifications and idempotency.

2. Resource design: nouns, hierarchies and sub-resources

The most fundamental REST pattern is the consistent use of nouns instead of verbs in URL paths. A URL represents a resource, not an action. /orders is correct, /getOrders is not. HTTP methods take over the semantics of the action: GET /orders reads, POST /orders creates, PUT /orders/{id} replaces completely, PATCH /orders/{id} updates partially, DELETE /orders/{id} deletes. This REST pattern makes the API self-explanatory and enables HTTP caching, because GET requests are marked as safe and idempotent.

Hierarchical resources are represented through nested URL paths, but only up to a sensible depth. /customers/{customerId}/orders/{orderId} is understandable and expresses the relationship. /customers/{id}/orders/{orderId}/items/{itemId}/metadata/{key} is too deep, here you should flatten the sub-resources and use references in the response body instead. The REST pattern for actions on resources that cannot be expressed as an HTTP method (for example, cancelling an order) is a dedicated sub-path: POST /orders/{id}/cancellation, the noun stands for the action, without verbs in the path.


# REST Resource Design Patterns, curl examples

# List resource (Collection)
GET /api/v1/orders?status=pending&limit=20&cursor=eyJpZCI6MTIzfQ==

# Single resource
GET /api/v1/orders/4711

# Sub-resource (order items)
GET /api/v1/orders/4711/items

# Action as sub-resource (not a verb in path)
POST /api/v1/orders/4711/cancellation
Content-Type: application/json
{ "reason": "customer_request", "notify": true }

# Partial update with PATCH
PATCH /api/v1/orders/4711
Content-Type: application/merge-patch+json
{ "shippingAddress": { "city": "Berlin" } }

# Idempotent creation with PUT + client-generated ID
PUT /api/v1/carts/session-abc-123
Content-Type: application/json
{ "items": [] }

Plural resource names are the clear convention: /products instead of /product, /users instead of /user. Consistency matters more than the grammatical elegance of individual names. Kebab-case for multi-word path segments: /shipping-addresses instead of /shippingAddresses. Query parameters use camelCase: ?sortField=createdAt. These conventions may seem petty, but in large teams without written API guidelines, exactly this is where inconsistencies arise that developers later have to paper over with adapter code.

3. HTTP status codes: precise instead of convenient

The most common mistake in API design is overusing 200 OK and 500 Internal Server Error. The correct REST pattern: status codes communicate semantics, not just success or failure. 201 Created signals that a new resource was created and the URL of the new resource is in the Location header. 204 No Content signals a successful operation with no return value, typical for DELETE. 202 Accepted indicates that a request was accepted but processing happens asynchronously, with a link to the status resource in the response.

In the error space, precision matters even more. 400 Bad Request means syntactically or semantically invalid input. 401 Unauthorized means missing or invalid authentication. 403 Forbidden means authenticated but not authorized. 404 Not Found means the resource does not exist. 409 Conflict means a state conflict (for example, a duplicate key). 422 Unprocessable Content is the correct code for validation errors, the request is syntactically correct but cannot be processed on a content level. 429 Too Many Requests for rate limiting, always with a Retry-After header. This precision allows clients to implement differentiated error handling instead of treating every error the same.

4. Error formats: RFC 9457 and consistent error bodies

RFC 9457 (Problem Details for HTTP APIs, successor to RFC 7807) defines a standardized JSON error format supported by all modern API frameworks. The REST pattern: every error path returns the same structure type. The required fields are type (a URI reference describing the error type), title (a human-readable summary of the error) and status (the HTTP status code). Optional but recommended: detail (a concrete error description for this request), instance (a URI identifying the specific error case) and domain-specific extension fields such as violations for validation errors.

The Content-Type header for problem detail responses is application/problem+json. This lets clients automatically distinguish between normal and error responses. The REST pattern for validation errors: an overarching problem detail object with status: 422 and an array violations that describes each invalid field with its path and the error message. This pattern can be parsed directly by API clients and saves custom error format documentation, because RFC 9457 is known and self-explanatory.


// RFC 9457 Problem Detail, 422 Validation Error
// Content-Type: application/problem+json
{
  "type": "https://mironsoft.de/errors/validation-failed",
  "title": "Validation failed",
  "status": 422,
  "detail": "3 fields contain invalid values.",
  "instance": "/api/v1/orders/requests/req-7f3a9c",
  "violations": [
    {
      "field": "shippingAddress.postalCode",
      "message": "Postal code must be 5 digits",
      "rejectedValue": "123"
    },
    {
      "field": "items[0].quantity",
      "message": "Quantity must be at least 1",
      "rejectedValue": 0
    },
    {
      "field": "paymentMethod",
      "message": "Unknown payment method",
      "rejectedValue": "bitcoin_unsupported"
    }
  ]
}

5. Pagination: cursor, offset and link headers

Offset-based pagination (?page=3&pageSize=20) is intuitive but has a fundamental problem: if a record is deleted or inserted between two API calls, the pages shift. The correct REST pattern for production-grade APIs with frequently changing data volumes is cursor-based pagination. The cursor encodes a stable reference point, typically the ID or the timestamp of the last element on the current page. ?cursor=eyJpZCI6NDcxMX0=&limit=20 delivers the next page regardless of changes that occurred in the meantime.

The REST pattern for communicating navigation links: the HTTP Link header per RFC 5988 with rel="next", rel="prev" and rel="first". Alternatively, or in addition, a pagination object in the response body. The advantage of the link header: clients do not need to construct the URL themselves but can simply follow the next link. This decouples clients from the pagination implementation, a breaking change to the cursor format only changes the header value, not the client logic. The response body should never calculate a maximum page count if the data volume is not known; instead, the absence of a next link indicates the end of the collection.

6. Versioning: URL path, headers and content negotiation

API versioning is unavoidable once an API is consumed by external clients. The best-known REST pattern is URL path versioning: /api/v1/orders, /api/v2/orders. The advantage: explicit, easily cacheable, clearly identifiable in logs and monitoring. The disadvantage: URLs are actually resource identifiers, not version markers, /api/v1/orders/4711 and /api/v2/orders/4711 address the same order. For strict REST, header versioning is cleaner: Accept: application/vnd.mironsoft.v2+json or a custom header API-Version: 2.

In practice, URL versioning dominates, because it is visible in browser dev tools, cURL and documentation without configuration. The decisive REST pattern is not the format of versioning but the commit discipline behind it: minor changes (new optional fields, new endpoints) are backward compatible and do not bump the version. Breaking changes (field renames, changed status codes, removed fields) require a new major version. Both versions run in parallel for a defined deprecation period while clients migrate. This period and the EOL date must be communicated in the Sunset header and in the API documentation.


# API versioning patterns

# URL-Path versioning (most common)
GET /api/v1/products/123
GET /api/v2/products/123

# Header versioning (strict REST)
GET /api/products/123
Accept: application/vnd.mironsoft.v2+json

# Sunset header for deprecation notice
HTTP/1.1 200 OK
Deprecation: true
Sunset: Sat, 01 Aug 2026 00:00:00 GMT
Link: <https://mironsoft.de/api/v2/products/123>; rel="successor-version"

# Content negotiation for response format
GET /api/v1/orders/4711
Accept: application/json          # default JSON
Accept: application/ld+json       # JSON-LD
Accept: application/vnd.api+json  # JSON:API format

# Conditional requests, ETag and If-None-Match
GET /api/v1/products/123
HTTP/1.1 200 OK
ETag: "a3f4c8b92d"
Cache-Control: max-age=60, private

GET /api/v1/products/123
If-None-Match: "a3f4c8b92d"
HTTP/1.1 304 Not Modified

7. OpenAPI specification: schema first instead of code first

The most important process shift in modern API design is the move from code-first to schema-first: the OpenAPI specification is created first, and server stubs, client SDKs and documentation are generated from it. The REST pattern for team workflows: the openapi.yaml is the contract between backend and frontend. Changes to the API are first submitted as an OpenAPI pull request, reviewed and merged, before implementation begins. This prevents API implementations that drift from frontend expectations and enables parallel development through a mock server.

The OpenAPI specification should define reusable schemas in components/schemas and reference them via $ref. The REST pattern for consistent schemas: all timestamps as ISO 8601 strings (format: date-time), all IDs as strings (even if internally a UUID or integer), all monetary amounts as integers in the smallest currency unit (cents, not euros). nullable: false as the default, fields that can be null are marked explicitly. additionalProperties: false in request schemas to reject unknown fields. These conventions in the schema make the generated SDK self-explanatory and reduce integration errors.

8. Idempotency, safe methods and retry logic

Idempotency means that repeated identical requests produce the same state as a single request. This is not the same as identical responses: DELETE /orders/4711 returns 204 No Content on the first call and 404 Not Found on the second, yet it is still idempotent because the resulting state is the same afterward. The REST pattern for retry safety: safe methods (GET, HEAD, OPTIONS) are always idempotent. PUT and DELETE are idempotent by HTTP specification. POST is not, here you need an idempotency-key pattern.

The REST pattern for idempotent POST requests is a client-generated Idempotency-Key header (a UUID). The server stores the key and the response for a defined period (typically 24 hours). If the same request arrives again with the same key, the server returns the cached response without executing the operation again. This is indispensable for payment APIs, order creation and every operation where a network interruption leaves the client uncertain about the outcome. Without this pattern, retries lead to duplicate orders, duplicate charges or database inconsistencies.

9. REST patterns in direct comparison

Many everyday API design decisions can be made in different ways, with substantial differences in consistency, cacheability and client compatibility. Choosing the right REST pattern is not a matter of style, it has a direct impact on the maintainability of the API across versions.

Design decision Antipattern Recommended REST pattern Benefit
Reporting errors 200 {"error":true} 422 + RFC 9457 Body Clients can branch on status code
Executing an action POST /cancelOrder POST /orders/{id}/cancellation Verbs in the path avoided
Pagination ?page=3&size=20 ?cursor=base64&limit=20 Stable under insertions/deletions
POST idempotency No safeguard Idempotency-Key: UUID Retries without side effects
Timestamps Unix timestamp integer ISO 8601 string Self-explanatory, timezone-safe

Antipatterns rarely arise from ignorance, but from time pressure and missing team conventions. A living style guide that exists directly in the API specification as an x-mironsoft-guidelines extension and is checked by a linter ensures that new endpoints follow the same REST patterns, automatically, without manual code reviews for every design question.

Mironsoft

API design, OpenAPI specification and REST architecture

REST APIs that stay consistent in production?

We analyze existing APIs, identify inconsistencies and build OpenAPI specifications that serve as a contract between backend, frontend and QA, with complete error formats, pagination and a versioning strategy.

API review

Analysis of existing APIs for inconsistencies, antipatterns and missing error formats

OpenAPI creation

Schema-first OpenAPI 3.1 specifications with reusable schemas and validation

Versioning strategy

Deprecation processes, sunset headers and breaking-change strategies for stable APIs

10. Summary

The most important REST and OpenAPI patterns for production-grade APIs always solve the same underlying problem: APIs without clear conventions accumulate inconsistencies that complicate integrations and produce breaking changes. Nouns instead of verbs in URLs make the API self-explanatory. Precise HTTP status codes enable differentiated client error handling. RFC 9457 error formats standardize error bodies without custom documentation. Cursor pagination survives data changes reliably. Idempotency keys make POST requests retry-safe.

The biggest lever lies in the schema-first approach: an OpenAPI specification as a team contract from which code is generated prevents divergence between backend implementation and frontend expectations from the outset. Combined with an API linter that catches REST pattern violations in the CI pipeline, no new inconsistencies arise, not even under time pressure or with frequently changing teams.

REST and OpenAPI patterns, the essentials at a glance

Resource design

Nouns, plural, hierarchies via path. Actions as sub-resources (POST /orders/{id}/cancellation), never verbs in the path.

Status codes & errors

Precise status codes instead of generic 200/500. Errors per RFC 9457 with application/problem+json and a violations array.

Pagination & idempotency

Cursor pagination for stable navigation. Idempotency-Key header for retry-safe POST requests.

OpenAPI & versioning

Schema first, $ref-based reuse. URL path versioning with a Sunset header for deprecation.

11. FAQ: REST and OpenAPI patterns for production-grade APIs

1What is the difference between PUT and PATCH?
PUT replaces completely, PATCH partially. For PATCH, application/merge-patch+json (RFC 7396) is the recommended content type, only the fields sent are updated.
2Why no verbs in REST URLs?
HTTP methods already carry the action semantics. Verbs duplicate this inconsistently. POST /orders/{id}/cancellation is unambiguous, POST /cancelOrder is not.
3401 vs. 403, what is the difference?
401: not authenticated (sign in again). 403: authenticated, but not authorized (missing access rights). The name "401 Unauthorized" is historically misleading.
4What is RFC 9457?
Standardized JSON error format: type, title, status, detail, instance. Content-Type: application/problem+json. Reduces custom error documentation and is machine-readably distinguishable.
5Cursor vs. offset pagination?
Offset shifts under insertions/deletions. Cursor stays stable. For real-time feeds and large datasets, always choose cursor.
6When do I need an idempotency key?
For every POST request where a network error could trigger a retry: payments, orders, newsletter sign-ups. Client sends a UUID, server caches the response.
7Schema-first vs. code-first?
Schema-first: the specification is the contract, code is generated. Parallel development, a mock server and API review before implementation become possible. Code-first only mirrors the implementation.
8How do I communicate deprecation?
Deprecation: true header plus Sunset: <date> header plus a link to the successor version. At least 6 months lead time. Link the changelog and migration guide.
9Which content types are standard?
application/json for responses, application/problem+json for errors, application/merge-patch+json for PATCH. Always specify charset=UTF-8 and Content-Type in the response.
10How do I test REST patterns automatically?
Contract tests with Schemathesis or Dredd against the OpenAPI specification. Spectral as an API linter checks the specification itself for pattern violations. Pact for consumer-driven contract tests.