Most API advice gets repeated long after the reason for it stopped applying, and some of it was never true — it just sounded authoritative in a post everyone cited. The beliefs below survive code review at plenty of companies. Each has a cost that shows up months later as a broken client, a cache serving errors, or a pagination bug nobody can reproduce. Each section states the claim, then what the specifications and the failure modes actually say.
REST is an architectural style described in Roy Fielding's 2000 dissertation, and it says nothing about JSON, which had no RFC until 2006. What it asks for is resource identification through URIs, self-descriptive messages and hypermedia driving state. JSON is the convention that won, not a requirement: an API serving text/csv for a report export is still REST. Practically, honour the Accept header rather than hardcoding one serialiser, and return 406 when you cannot satisfy it.
Three options, three different bills. URL versioning (/v2/orders) is trivially cacheable and readable in a log, but it claims the resource changed identity, which is false, and forces every client to rewrite every path on upgrade. Header versioning (API-Version: 2026-03-01) keeps URLs stable and lets you version per endpoint, but it is invisible in a browser and easy to forget in a curl reproduction. Media-type versioning (Accept: application/vnd.acme.order+json;v=2) is the most correct and the least used, because tooling support is poor. Date-based versions pinned per API key cost the most up front and the least in support later.
Half right. PUT must be idempotent and replaces the resource at the target URI, so any field you omit is genuinely removed — sending {"name":"x"} to a user with an email address wipes the email. PATCH has no idempotency requirement at all; a PATCH that appends to an array is legal and repeating it appends twice. PATCH also has no default body format. JSON Merge Patch (RFC 7386) is simple but uses null to mean removal, so you can never patch a field to null, and it cannot delete an array element. JSON Patch (RFC 6902) is an explicit op list that can, at the cost of verbosity. Pick one and send the matching content type.
It moves the cost. Field selection does stop the mobile app downloading 40KB it discards, but each field is a resolver, and a list of 50 orders each resolving a customer is 51 database round trips unless you batch with a DataLoader-style pattern. GraphQL also removes HTTP caching, since everything is a POST to one endpoint, and it lets a stranger request a deeply nested query. You need depth limits, a per-field complexity budget and persisted queries in production — work a REST endpoint with a ?fields= parameter does not require.
A 200 is cacheable, so a proxy will happily store your error body and serve it to the next caller. Retry logic in every mainstream HTTP client keys off status, so a transient failure dressed as 200 never gets retried, and generated SDKs and gateways decide success the same way. Use the codes: 201 with a Location header on create, 202 when you accepted work you have not finished, 409 for a genuine state conflict, 422 for a body that parsed but failed validation, 429 with Retry-After. Put the machine-readable detail in a Problem Details body (RFC 9457) rather than inventing another error envelope.
Offset breaks under writes. Insert a row while a client sits between page 2 and page 3 and every later row shifts, so the client silently skips one; a delete makes it see the same row twice. Offset also slows as it grows, because the database still walks the skipped rows. Keyset pagination sorts on an indexed, unique, monotonic column and asks for rows after the last one seen.
SELECT * FROM orders
WHERE (created_at, id) < ('2026-03-01T09:12:00Z', 84213)
ORDER BY created_at DESC, id DESC
LIMIT 50;
Encode that tuple as an opaque cursor string so you can change the sort later without breaking clients. Keep offset only for admin screens that need to jump to page 40.
A client that times out on a POST cannot know whether the charge went through. Give it an Idempotency-Key header: store the key with its response for 24 hours and return the original result on a repeat rather than creating a second order. Scope keys per endpoint and per account, and return 409 if the same key arrives with a different body — that is a client bug, not a retry.
Free tools, guides, and resources across the SPUNK13 network.
Visit spunk.bet400+ Free Tools