Skip to main content

REST API Design That Survives: Versioning, Auth, Errors, and the Parts Nobody Teaches

Anyone can ship an endpoint. The APIs that survive five years of consumers, migrations, and 3 a.m. incidents are designed around the boring parts: versioning you can live with, auth that fails closed, errors machines can parse, and pagination that doesn't lie.

REST API Design That Survives: Versioning, Auth, Errors, and the Parts Nobody Teaches

Every tutorial teaches the same REST API: five routes, a happy path, JSON in, JSON out, done in an afternoon. Then the API meets reality — a mobile client that can’t update for three weeks, a partner integration written against a typo you now can’t fix, a security review, a pagination bug that double-charged nobody but double-showed everybody — and you discover that the tutorial covered the easy 20%. The other 80% is what this post is about.

These are the decisions I’ve had to defend (or regret) across years of production APIs, in the order they usually bite. They’re also, not coincidentally, the exact topics the system design interview probes when the interviewer says “now design the API for it.”

How do you version an API you can’t break?

Version from day one, in the URL, and treat every published field as a contract you’ll honor for years. The moment one consumer you don’t control ships against your API, “we’ll clean it up later” becomes fiction: renaming a field is a breaking change, removing one is a breaking change, and changing a type is an outage with your name on it. /v1/ in the path is unglamorous and slightly inelegant — and it’s the approach whose failure modes are boring, which is the highest compliment in API design.

Before going decision by decision, here is the whole post in one table — the tutorial answer, the answer that survives five years of consumers, and what the shortcut actually costs when it comes due:

DecisionThe tutorial answerThe answer that survivesWhat the shortcut costs
VersioningAdd and rename fields freely/v1/ from day one; additive changes only after publishEvery rename is an outage inside somebody else’s application
Auth defaultAdd auth to the endpoints that need itDeny by default; public routes are explicitly markedThe one endpoint someone forgot, found by a crawler before you
AuthorizationA valid token means the caller is allowedVerify ownership on every resource fetchYour customer’s data served to a different customer
ErrorsHTTP status code plus a prose messageMachine code, human message, correlation IDIntegrators guess; their bug report becomes your 3 a.m. page
Pagination?page=3An opaque cursor meaning “continue after this row”Items duplicate and vanish under writes; deep pages scan and discard
Documentation audienceHumans who will skim itThe most literal consumer you will ever haveAn agent hallucinates the parameter you left vague

The real discipline isn’t the URL scheme; it’s the additive-change habit. New capability? New optional field, new endpoint — never a changed meaning for an existing one. Teams that internalize “published means permanent” design fields more carefully before publishing, which is the actual benefit: versioning discipline is mostly a forcing function for thinking twice.

What does “auth that fails closed” look like?

Every endpoint requires authentication unless explicitly, deliberately marked public — the deny-by-default middleware pattern, where forgetting to configure a route means it’s closed, not open. The industry’s recurring API disaster isn’t broken crypto; it’s the endpoint someone forgot to protect, discovered by a crawler. Fail-closed design makes that mistake structurally impossible: the lazy path and the safe path are the same path.

Authorization deserves the same paranoia one level deeper: authentication says who you are, authorization says what’s yours, and the gap between them is the classic breach. GET /orders/12345 with a valid token must still verify that order 12345 belongs to this token’s user — the missing-ownership-check bug (IDOR, in security parlance) remains the most common serious API vulnerability in code review, and generated code reproduces it enthusiastically because the happy-path version looks identical.

Why do error responses deserve design time?

Because your errors are an API too — the one consumed at 3 a.m. by a stressed integrator deciding whether the bug is theirs or yours. A survivable error response carries three things: a machine-readable code (insufficient_funds, not just HTTP 400), a human-readable message that names the offending field, and a correlation ID that lets your logs and their bug report find each other. HTTP status alone is too coarse — a 400 that means five different things forces every consumer to parse your prose.

Predictability is a feature. Surprise is a cost. The APIs people call “pleasant to integrate” are rarely clever — they do the same thing everywhere.

Consistency beats richness. One error envelope, identical shape on every endpoint, documented once. The APIs people describe as “pleasant to integrate” are rarely doing anything clever — they’re doing the same predictable thing everywhere, so the integrator’s error handling is one function instead of one per endpoint. Predictability is a feature; surprise is a cost, the same lesson durability teaches at the architecture level.

What’s wrong with page-number pagination?

Offset pagination (?page=3) lies under concurrent writes: rows inserted or deleted while a consumer walks the pages cause items to appear twice or vanish, and deep offsets punish the database with a full scan-and-discard. It survives in tutorials because it’s trivial and looks right in demos with static data. Cursor pagination — an opaque token meaning “continue after this row” — stays correct under writes and stays fast at depth, at the cost of losing “jump to page 7,” which most real consumers never needed.

The transferable rule: paginate by position in an ordering, not by count from the start, whenever data changes underneath readers. It’s a small design decision with a long tail — retrofitting cursors onto a shipped offset API is exactly the kind of published-contract migration the versioning section warned about.

Does any of this change when the consumer is an AI agent?

The principles survive; the tolerances tighten. Agents consume APIs through tool definitions, and everything above becomes machine-facing: a vague error message a human would shrug past sends an agent into a retry loop, an inconsistent envelope breaks its parsing, an under-documented parameter gets hallucinated. Designing APIs that agents can use reliably — precise schemas, exhaustive error codes, honest descriptions — is the same craft as good REST design with the sloppiness allowance removed. That’s the thesis of the MCP explainer: tool interfaces are API design, round two, with a less forgiving consumer.

Which is why the fundamentals course path still runs through building real REST services — Node.js and production APIs for the craft itself, on the backend architecture floor that decides what’s a service and what’s a handler, with TypeScript as the typing discipline that makes contracts enforceable instead of aspirational. Every one of those skills now has a second customer: the agent calling your API through a schema, exactly as literal-minded as the type checker, and considerably more expensive when confused.

The survival checklist

Version in the URL from day one; additive changes only after publish. Deny-by-default auth; ownership checks on every resource fetch. One error envelope everywhere: machine code, human message, correlation ID. Cursors, not offsets, wherever data moves. Document for the most literal consumer you’ll ever have — because as of this year, you have one. None of it is glamorous. Neither is a bridge that doesn’t fall down.

Share this article
X LinkedIn
Next step

Turn this into a real skill

A structured path from theory to production code — projects and code reviews included.

Oleksii Anzhiiak

Written by

Oleksii Anzhiiak

Software Architect, Senior .NET Engineer & Co-Founder

Oleksii Anzhiiak is a Software Architect, Senior .NET Engineer, and Co-Founder of ToyCRM.com and ProfectusLab. With over 15 years of experience, he specializes in distributed systems, cloud infrastructure, high-load backend development, and identity platforms. Oleksii designs complex architectures, builds secure authentication systems, and develops modern engineering education programs that help students achieve real career results.

LinkedIn

Recommended Watching

Hand-picked third-party videos related to this topic. Open on YouTube.

~1:56:00
Advanced Andrej Karpathy

Let’s build GPT from scratch

A rare hands-on explanation of GPT internals, connecting theory with real code. Ideal for engineers who want to truly understand how modern LLMs are built.

~27:00
Intermediate 3Blue1Brown

Transformers, the tech behind LLMs — Deep Learning Chapter 5

3Blue1Brown's signature visual treatment of the transformer architecture. The single best 30-minute primer for engineers who want the intuition before the math.

~1:00:00
Beginner Andrej Karpathy

[1hr Talk] Intro to Large Language Models

Karpathy's full hour walkthrough of how LLMs actually work — inference, training, fine-tuning, and the emerging LLM-OS picture. The clearest single-shot mental model on the internet for engineers new to the field.

Contact us