API Anti-Patterns Field Guide
Everything-POST, verb explosion, raw DB models as contracts, unbounded lists, 200-for-everything, frontend-only authorization, versioning nothing or versioning everything. Recognizing the pattern is faster than rediscovering the pain.
Frame the contract
API design starts with a consumer, a design question and a guarantee — never with a URL.
Contract-shape anti-patterns
The first family distorts the shape of the contract itself. Each one is locally defensible — that is why they survive review — and each has a specific tax that arrives later, paid by consumers.
- Everything is POST — every operation tunnels through
POST /doThing. Loses method semantics: nothing is safely retryable or cacheable by contract, and every intermediary must assume the worst (see HTTP Methods Are Promises). - Verb explosion —
/createUser,/updateUser,/deactivateUser,/reallyDeleteUser… Operations multiply without a model; consumers memorize a phrasebook instead of learning a grammar (see From Domain to Resources). - Raw DB model as contract — the table serialized as JSON. Every schema migration is now a breaking change; internal bookkeeping fields are public promises (see Response Contracts Are Not Database Rows).
- Unbounded list endpoints —
GET /ordersreturns *all* orders. Works in staging, dies at the customer with 2M orders; pagination retrofits are breaking changes (see Unbounded Collections: The Anti-Pattern With a Fuse). - Giant payloads — kitchen-sink responses with embedded everything (see API Granularity and the Chatty API).
- Internal leakage — enum values named after internal services, ids exposing storage layout, errors quoting SQL. Each leaked detail is an accidental contract clause.
Behavioral anti-patterns
The second family lies about behavior: the contract says one thing, the wire does another, and consumers program against the lie.
The most expensive member is 200-for-everything: errors returned as 200 OK with {"success": false} in the body. Every piece of generic HTTP machinery — retries, caches, monitoring, load-balancer health logic — now believes failures are successes. Error rates read as zero while customers see failures; caches store error bodies and serve them as truth (see The Error Model: Structure Over Apology and Status Codes Clients Can Branch On).
- 200 OK for all failures — breaks every consumer and intermediary that trusts status codes; makes error-rate monitoring structurally blind.
- Hidden state transitions — an order moves
created → shippedthrough side effects no API exposes; consumers poll and diff to reverse-engineer the machine (see Resources Have State Machines). - No idempotency on retryable money paths — the client retried, the customer paid twice; the network guaranteed the retry would eventually happen (see Idempotency).
- Breaking enum changes — a new enum value ships; old clients hit
default: throw. Additive for you, breaking for them (see Enum Evolution: The New Value That Broke Old Clients). - Unstable errors — clients branch on message strings; a copyedit becomes an outage (see An Error Taxonomy Clients Can Branch On).
- Authorization only in the frontend — the API trusts that the button was hidden. The API *is* the security boundary; the UI is a convenience (see Authorization Design in the Contract).
Change-management anti-patterns, and reading the guide honestly
The third family fails at evolution, in both directions. Versioning every tiny change mints /v7/ for additive field changes — consumers face migration demands for no benefit, and version count becomes noise that hides the one real break. Never versioning despite breaking changes ships renames in place and calls the outage a "misunderstanding". Both come from the same gap: no definition of which changes are breaking (see Backward Compatibility: The Real Rules and Versioning: What a Version Even Promises).
Two honesty rules keep the guide useful. First, an anti-pattern is a *default to depart from with reasons*, not a law: an internal admin API with two consumers may legitimately skip pagination on a ten-row collection — the review question is "did you decide that, or did it happen?". Second, the inverse failure exists for nearly every entry: REST purity is the mirror of everything-POST; versioning-everything mirrors versioning-nothing. The guide is a map of ditches on both sides of the road, not a wall on one side.
| Anti-pattern | The tax, when it arrives | Unpacked in |
|---|---|---|
| Everything POST | No safe retries or caching; arrives with the first network blip | HTTP Methods Are Promises |
| Raw DB model exposed | Every migration is a breaking change; arrives with the first refactor | Response Contracts Are Not Database Rows |
| Unbounded lists | Timeouts and OOMs; arrives with the first big customer | Unbounded Collections: The Anti-Pattern With a Fuse |
| 200 for failures | Blind monitoring, poisoned caches; arrives silently, discovered late | The Error Model: Structure Over Apology |
| No idempotency on payments | Double charges; arrives with the first retried timeout | Idempotency |
| Frontend-only authorization | Data breach via curl; arrives when someone reads the network tab | Authorization Design in the Contract |
| Version everything / version nothing | Migration fatigue / surprise outages | Versioning: What a Version Even Promises |
Key points
- Anti-patterns are locally defensible decisions with taxes that arrive later, paid by consumers — that is why review needs the catalog.
- The shape family (everything-POST, raw models, unbounded lists) freezes evolution; the behavior family (200-for-everything, hidden transitions) makes consumers program against lies.
- 200-for-everything is uniquely corrosive because it blinds every generic intermediary and monitor at once.
- Most anti-patterns have a mirror-image failure; the guide maps ditches on both sides of the road.
- Departing from a default is allowed — as a decision with a reason, recorded where the next reviewer will find it.
Follow the failure
How the contract fails or gets misused, hop by hop — and what it costs when it completes.
- 1Team → v1: ships several anti-patterns; each was the fastest local choice and none failed in staging.
- 2Consumers → v1: integrate against the accidental shapes; the taxes are now shared liabilities.
- 3Scale → API: the first big customer hits the unbounded list; the first network blip double-charges via the non-idempotent POST.
- 4Team → firefighting: each incident is treated as novel because the org has no vocabulary linking failure to shape.
- 5API → freeze: enough consumers depend on the broken shapes that fixing any of them is itself a breaking change.
- Consumers absorb the taxes: retries they cannot trust, lists that time out, errors they cannot branch on.
- Monitoring credibility: 200-for-everything makes dashboards lie exactly when they matter.
- The provider's roadmap: anti-patterns compound into a contract too load-bearing to repair, forcing a v2 program.
Design, observe, evolve
A contract decision is incomplete until you know how you would notice it failing and how it changes later.
- • Review new APIs against the named catalog explicitly — a ten-minute checklist pass catches most entries before they ship.
- • Encode the mechanical ones (pagination present, error envelope, status-code usage, auth on every route) into a spec linter in CI.
- • For every deliberate deviation, record the reason in the design doc; undocumented deviations are findings, not choices.
- • Prioritize by tax schedule: fix the ones whose tax arrives with scale (unbounded lists, idempotency) before the aesthetic ones.
- • Latency and payload percentiles per endpoint surface unbounded lists and giant payloads before customers do.
- • A 5xx/4xx rate of exactly zero on a busy API is a 200-for-everything alarm, not a compliment.
- • Duplicate side effects (two charges, two emails) in reconciliation are the idempotency anti-pattern announcing itself.
- • Anti-patterns are repaired by parallel-shape migration: ship the correct shape alongside, move consumers with telemetry, deprecate the old (see [[api-migration]]).
- • The catalog itself evolves: every incident review that finds a new repeatable shape adds an entry, turning outages into review questions.
- • Checklist review adds friction to every API launch and will occasionally flag legitimate designs — the documented-deviation path must be cheap.
- • A linter can ossify: rules that made sense once need owners and revisiting, or the guide becomes the dogma it warned about.