REST API design in 2026 is no longer a matter of taste — much of what used to be argued in style guides now has a published standard, a production reference implementation, or both. The hard part is that the consensus moved while a lot of shipped code stayed put. This reference pulls the current state of the art into one place.
Three things are true at once. The patterns that matter — structured errors, cursor pagination, idempotency keys, header-based versioning, machine-readable deprecation — are well-specified and battle-tested at companies like Stripe, GitHub, and Google. Most real-world APIs still don’t use them. And the gap between the two is exactly where developer experience, reliability, and integration cost quietly leak away.
What follows is organised the way an engineering team actually makes decisions: resource modelling, error format, pagination, idempotency, versioning, rate limiting, deprecation, and the OpenAPI-first workflow that ties it together. Every fact below is sourced from the relevant IETF RFC or the public engineering documentation of the provider it describes.
- 01RFC 9457 is the standard for API errors — and it’s 2023.Problem Details for HTTP APIs defines a machine-readable JSON error format (media type application/problem+json) with five base fields. It superseded RFC 7807 in July 2023, yet many APIs still return ad-hoc strings.
- 02Offset pagination collapses at scale; cursor doesn’t.OFFSET 100,000 forces the database to scan and discard the first 100,000 rows on every request. Cursor pagination keys off a stable identifier instead — the trade-off is losing random-access page jumps.
- 03Idempotency keys make retries safe — with one trap.Stripe’s spec uses an Idempotency-Key header (V4 UUID recommended). Results are cached only after endpoint execution begins, so a failed validation is NOT served from cache — a detail that breaks naive retry logic.
- 04Versioning is about isolation, not version numbers.Stripe pins date-based versions by default and transforms responses backwards; GitHub uses an X-GitHub-Api-Version header. The mechanism matters more than whether you write v1 or a date.
- 05The spec is the contract — write it first.OpenAPI 3.1 aligns fully with JSON Schema 2020-12; 3.2 (Sept 2025) adds structured tag navigation and streaming media types. Generating clients and stubs from the spec closes the drift gap that wrecks DX.
01 — Resources & NamingModel resources, not your database.
The first design decision is also the one teams most often get backwards: a REST resource is a noun in your domain model, not a row in your schema. Microsoft’s Azure REST API design guidance is explicit on this — use plural nouns for collection URIs, keep URI depth to a collection/item/collection maximum, and avoid APIs that mirror the internal database structure. Leaking your table names into the URL couples consumers to your storage layer and widens the attack surface for no benefit.
Google’s AIP-190 naming standard adds the conventions that keep a large API surface coherent: method names in VerbNoun UpperCamelCase, correct American English spelling, and the same term used for the same concept everywhere. It warns against overly generic words — instance, info, service — that create ambiguity. Google-style APIs also use a colon separator for custom actions on collections, making the action-versus-resource boundary explicit: POST /v1/videos:process rather than /processVideo. The standard methods stay List, Get, Create, Update, and Delete.
Plural nouns
Microsoft’s guide recommends plural nouns for collections and capping URI depth at collection/item/collection. Don’t mirror your database tables in the path.
Colon separator
Google-style REST keeps action-resource boundaries explicit with a colon — not a verb baked into the path like /processVideo. Standard methods stay List/Get/Create/Update/Delete.
Method semantics
Per Microsoft’s guide, GET, PUT, and DELETE must be idempotent; POST and PATCH are not guaranteed to be. The same PUT submitted repeatedly must always produce the same result.
02 — Error BodiesRFC 9457: the error standard almost nobody uses.
Here is the adoption gap in one line: the IETF has had a machine-readable error standard since 2023, and most APIs still return {"error": "something went wrong"}. RFC 9457, “Problem Details for HTTP APIs,” was published in July 2023 and supersedes RFC 7807. It defines a standardised JSON (and XML) error format served as application/problem+json, built from five base fields: type, title, status, detail, and instance.
RFC 9457 added three improvements over 7807: a registry for commonly-used problem type URIs to enable interoperability, clarified handling of multiple simultaneous problems, and expanded guidance for non-dereferenceable type URIs. One subtlety catches teams off guard — the detail field is explicitly for helping the client resolve the issue, not for server debugging output. Extension members beyond the five standard fields are allowed, and consumers must ignore unrecognised extensions so the format stays forward-compatible.
| Field | Type | Required | Purpose |
|---|---|---|---|
type | URI reference | Recommended | Identifies the problem type |
title | string | Optional | Human-readable summary of the problem type |
status | integer | Optional | HTTP status code (useful in logging contexts) |
detail | string | Optional | Occurrence-specific explanation for the client |
instance | URI reference | Optional | Identifies this specific occurrence |
| Extension members | any | Optional | Problem-specific data; unknown members must be ignored |
The practical migration is small and high-leverage. Swap a bare {"error": "not found"} for a problem+json body with a stable type URI and a client-actionable detail, set the Content-Type to application/problem+json, and your consumers get errors they can branch on programmatically instead of string-matching prose. It is one of the cheapest developer-experience upgrades an existing API can make.
03 — PaginationThe OFFSET lie at scale.
Offset pagination feels free because it is, until your dataset isn’t small. The mechanism is the problem: offset-based pagination forces the database to scan and discard the first N rows on every query, so OFFSET 100,000 requires the engine to read and throw away 100,000 rows before returning a single result. At scale that cost grows roughly with how deep into the dataset the request reaches.
Cursor-based pagination avoids the problem by keying off a stable identifier — a timestamp or an opaque token — so the database can seek directly to the slice it needs. The trade-off is real and worth stating plainly: API consumers can no longer jump to an arbitrary page, because random access is lost, and cursors can be invalidated if records are deleted, which means fresh cursors with each response. GitHub’s production answer is to expose pagination through Link headers — containing next, last, first, and prev references — rather than baking URLs into the JSON body, and to have clients follow those links rather than construct query strings by hand.
You should not try to manually construct pagination queries, but instead use the Link headers to determine what pages of results you can request.— GitHub Docs, REST API Best Practices
Offset is fine
Admin tables, settings lists, anything where the total row count stays modest and users genuinely want to jump to page 7. Offset’s scan cost is negligible when N is small.
Cursor by default
Activity feeds, event logs, anything paginated deeply or under heavy load. Keying off a stable identifier avoids the read-and-discard cost that makes deep OFFSET exponentially expensive.
Link headers
Follow GitHub’s pattern: return next/prev/first/last in Link headers and instruct clients to follow them. Decouples your URL structure from consumer code so you can change it later.
04 — IdempotencyIdempotency keys and the cache-timing trap.
Networks fail mid-request, and a client that doesn’t know whether its POST succeeded will retry. Idempotency keys make that retry safe. Stripe’s widely-copied spec uses an Idempotency-Key request header, recommends a V4 UUID, caps key length at 255 characters (vendor-stated), and retains keys for at least 24 hours (vendor-stated). It applies to all POST requests. Stripe saves the resulting status code and body of the first request regardless of whether it succeeded or failed — including 500 errors — and if the parameters on a retry don’t match the original request, the idempotency layer returns an error, preventing accidental reuse of a key for a different payload.
The reason this matters is fundamental to distributed systems, and Stripe’s own engineering writing puts it well — when a client sees a failure it can converge its state with the server’s by retrying until it verifiably succeeds, but only if the server makes that retry safe. Idempotency keys are how you keep the promise. If you’re building event-driven systems on top of this, the same discipline extends to idempotency patterns for webhooks and retries, where at-least-once delivery makes the property non-negotiable.
When a client sees a failure, it can ensure convergence of its own state with the server's by retrying, and continue to retry until it verifiably succeeds.— Stripe Engineering, Designing robust and predictable APIs
05 — VersioningVersioning is about isolation, not numbers.
The version-numbering debate misses the real engineering question: how do you isolate a breaking change from the clients that aren’t ready for it? Stripe’s answer is date-based API versioning — values like 2017-05-25 rather than integer v1/v2 — and the company reports having shipped approximately 100 backwards-incompatible versions since 2011. New accounts are pinned to the latest version automatically at their first API call. The architecture underneath is a transformation layer: a core that only speaks the current version, plus version-change modules that convert responses backwards for older clients, which eliminates the version-check conditionals that would otherwise sprawl across business logic.
GitHub takes the header route, using an X-GitHub-Api-Version request header rather than URL-path versioning. Both keep the base URL stable, which is what makes them “soft” isolation. The comparison below puts the common strategies side by side on the dimensions that actually drive the decision.
| Strategy | Isolation | Client effort | Used by |
|---|---|---|---|
| URL path/v1/users → /v2/users | Hard — separate namespace | Manual URL swap | Many public APIs |
| HeaderX-GitHub-Api-Version | Soft — same base URL | Header update | GitHub REST API |
| Date-basedStripe-Version: 2024-06-20 | Soft + pinned by default | Dashboard upgrade | Stripe |
| Media typeAccept: …; version=2 | Soft — content negotiation | Accept header | Some hypermedia APIs |
| Query param?version=2 | Soft — cacheability risk | Param update | Less common / legacy |
06 — Rate Limits429 done right: backoff, jitter, and headers.
A 429 Too Many Requests response is only half a design. RFC 9110 (Section 10.2.3) and RFC 6585 both recommend including a Retry-After header with the 429, where the value can be either an HTTP date or a delay in seconds. Without it, every client has to guess when to come back. On the client side, exponential backoff with jitter is the standard for recovery — and the jitter is not optional. Without it, multiple clients that hit the limit at the same moment will retry simultaneously, re-synchronise their traffic, and destabilise the server they were trying to be gentle with.
Good APIs let clients avoid the 429 entirely. Most providers expose X-RateLimit-Remaining and X-RateLimit-Reset headers so a proactive client can self-throttle before it trips the limit, rather than reacting after the fact. GitHub adds a nice twist for conditional requests: it returns ETag and Last-Modified headers, and a conditional GET using if-none-match or if-modified-since that returns 304 Not Modified does not consume primary rate-limit quota. On the implementation side, Redis is the standard backend for real-time rate limiting thanks to its atomic increments and TTL support.
The four pieces of a complete rate-limit response
Source: RFC 9110, RFC 6585, GitHub & Zuplo documentationAuthentication sits alongside rate limiting as the other transport concern that shapes the whole API. OAuth 2.0 with PKCE (Proof Key for Code Exchange) is the standard auth flow for modern public clients; bearer tokens should expire in roughly 15 to 60 minutes with refresh token rotation, and every authentication pattern is exposed without HTTPS/TLS at the transport layer. For the algorithm-level view of how the limits themselves are computed, see our companion reference on rate limiting strategies for REST APIs.
07 — DeprecationSunsetting endpoints, machine-readably.
Retiring an endpoint is a contract event, and the IETF has given it two machine-readable signals. RFC 8594 defines the Sunset HTTP response header, indicating the date and time after which a URI is expected to become unresponsive. Its companion, RFC 9745 (“The Deprecation HTTP Response Header Field”), defines the Deprecation header, which signals that an endpoint is deprecated. Used together, they let consumers detect and monitor deprecations automatically rather than discovering them when something breaks.
Standards describe the mechanism; policy is what makes deprecation humane. The widely-referenced open-source Zalando RESTful API Guidelines specify that deprecated endpoints must carry both the Deprecation and Sunset headers, and that teams owe consumers six to twelve months of notice plus a migration guide before decommissioning. The headers tell a machine; the notice period and the guide tell the engineers on the other end.
When it goes away
RFC 8594 defines the Sunset HTTP response header — the date/time after which a URI is expected to become unresponsive. Lets clients schedule their migration against a real date.
That it’s deprecated
RFC 9745 defines the Deprecation response header signalling an endpoint is deprecated. Pairs with Sunset for automated, machine-readable monitoring.
Plus a migration guide
The Zalando RESTful API Guidelines call for both headers on deprecated endpoints, and 6–12 months of notice plus a migration guide before decommissioning. Policy, not just protocol.
08 — OpenAPI-FirstThe spec is the contract.
Every pattern above is only as trustworthy as the document that describes it — and a spec written after the code drifts away from reality the moment the code changes. The design-first answer is to make the OpenAPI specification the contract, not the implementation. OpenAPI 3.1.0 aligns fully with JSON Schema Draft 2020-12, added webhooks as a top-level element, made path items optional for reusable component libraries, and allowed descriptions alongside $ref usage. The current 3.1-line schema is 3.1.2 (schema updated 2025-11-23).
OpenAPI 3.2.0, released in September 2025, added structured tag navigation, streaming-friendly media types, and new OAuth flows while remaining fully backwards-compatible with 3.1. With the spec as the source of truth, code generation closes the drift gap: the open-source OpenAPI Generator produces client SDKs and server stubs across 50+ languages from a single OpenAPI document and integrates into CI/CD pipelines so generated code stays in sync with spec changes. When the spec is the contract, the generated client is correct by construction.
One more design lever belongs in the same conversation. HATEOAS — Hypermedia as the Engine of Application State — enriches responses with discoverable links to reduce client-server coupling, commonly serialised as HAL or JSON:API. In 2025–2026 it earns its complexity more for intricate client-server ecosystems such as workflow APIs and multi-step state machines than for simple CRUD. If you’re weaving APIs together across services, our reference on API-first development and microservices architecture picks up where this leaves off, as does our guide to serverless deployment for API backends.
In 2025, HATEOAS isn't fashionable like GraphQL — yet it solves real problems for complex client-server ecosystems.— Pradeep Loganathan
09 — What To AvoidThe anti-patterns that quietly cost you.
Most API problems aren’t exotic; they’re the same handful of anti-patterns repeated across teams. Each one trades a small upfront convenience for a recurring tax on the consumers and on your future self. The grid below collects the ones worth auditing for first.
Verbs in the path
A verb in the URI duplicates what the HTTP method already says and forfeits method semantics. Using POST for everything is the same mistake at scale — it throws away GET/PUT/DELETE meaning entirely.
200 OK on failure
Hiding a failure behind a 200 status, or returning a generic error string, forces clients to parse prose. RFC 9457 exists precisely to replace this — a stable type URI beats a free-text message.
Deep nesting & leaked schema
URI nesting beyond ~3 levels is fragile, and using table names as resource names couples consumers to your storage and widens the attack surface. Keep depth shallow; model the domain, not the database.
Silent breaking changes
Integer bumps with no deprecation headers, no sunset date, and no migration path break consumers without warning. Offset pagination on large datasets is the same class of bug — an invisible cost.
10 — ConclusionClose the adoption gap.
The patterns are standardised. The opportunity is in actually using them.
The striking thing about REST API design in 2026 is how little of it is still genuinely contested. Errors have a standard in RFC 9457. Pagination has a clear mechanism-driven answer. Idempotency, versioning isolation, machine-readable deprecation, and spec-first workflows all have well-documented production references. The disagreement has largely moved from what is correct to why so few APIs do it.
That gap is the opportunity. An existing API can adopt problem+json errors in an afternoon, add Retry-After and X-RateLimit-* headers in a sprint, and earn outsized developer-experience gains for the effort. The harder, more valuable moves — cursor pagination on hot paths, a transformation-layer versioning strategy, an OpenAPI document that is the contract rather than an afterthought — are the ones that compound as the API and its consumer base grow.
Looking forward, the direction of travel is clear: machine-readable everything. Errors a client can branch on, deprecations a tool can monitor, contracts a generator can turn into correct clients. The teams that treat the specification as the source of truth — and lean on the IETF and vendor patterns rather than re-litigating settled questions — will spend their design energy on the parts of their API that are genuinely unique to their domain. That is where it belongs.