Design Principles Without Commandments
Consistency, predictability, explicitness, least surprise, good defaults, bounded operations. Principles earn their place as tie-breakers and review questions — not as absolutes that override a requirement.
Frame the contract
API design starts with a consumer, a design question and a guarantee — never with a URL.
The principles, and what each one buys
Principles are compressed experience, and they compress well because most API mistakes are repeated ones. But a principle applied against a requirement is just dogma with better branding — the skill is knowing what each principle protects, so you can tell when it applies.
The test for every principle below is the same: it should win *ties*. When the requirement genuinely does not care, follow the convention. When the requirement does care, the requirement wins, and the deviation gets documented as deliberate (see What an API Contract Actually Is).
- Consistency — same concepts, same names, same shapes everywhere. Buys transfer learning: one endpoint teaches the next. Violated by
user_idhere anduserIdthere (see One Vocabulary: Naming and Consistency). - Predictability — similar operations behave similarly, including in failure. Buys correct consumer guesses under pressure.
- Explicitness — guarantees, defaults and limits are stated, not implied. Buys survivable evolution (silent behavior becomes load-bearing).
- Least surprise — the obvious reading of the contract is the true one. A
GETthat mutates is the canonical violation (see GET: The Promise of Safety). - Good defaults — the zero-config call does something safe and bounded. Buys a gentle learning curve without hiding sharp edges.
- Bounded operations — every list paginates, every payload has a limit, every call has a deadline. Buys survival at sizes you have not seen yet (see Unbounded Collections: The Anti-Pattern With a Fuse).
- Clear ownership — every surface has an accountable owner (see API Ownership and the Catalog).
Consistency is the compound-interest principle
If you keep only one principle, keep consistency, because it multiplies the value of every other decision. A mediocre pagination convention applied uniformly beats an excellent one applied to a third of endpoints: consumers write one pagination helper, one error handler, one retry wrapper, and reuse them everywhere. The API's learnability *is* the consistency of its conventions.
Consistency is also the principle most under attack, because it dies by a thousand locally-reasonable decisions — this endpoint's data "naturally" fits a different envelope; that team prefers a different timestamp format. The defense is written conventions plus review: a short style sheet (naming, envelopes, errors, pagination, timestamps, ids) and a linter or checklist that makes deviation a decision instead of an accident (this is what the review lab's linter checks).
The limit case matters too: consistency with a bad convention is still a cost. When a convention proves harmful, change it deliberately, version the change, and migrate — do not let the API fork into "old style" and "new style" endpoints with no map of which is which.
1GET /users?page=2&per_page=502→ { "data": [...], "meta": { "total": 913 } }3 4GET /orders?offset=100&limit=505→ { "orders": [...], "count": 913 }6 7GET /invoices?cursor=abc8→ { "items": [...], "next": "def" }9# three list endpoints, three pagination dialects,10# three client helpers to write and maintain1GET /users?cursor=abc&limit=502GET /orders?cursor=abc&limit=503GET /invoices?cursor=abc&limit=504→ {5 "data": [ ... ],6 "next_cursor": "def" | null7}8# one helper paginates every collection in the APINeither dialect on the left is wrong in isolation. The cost is the aggregate: every consumer pays the learning and maintenance tax for every dialect, forever.
When principles collide, requirements referee
Real designs put principles in tension. Explicitness says "make the client state the page size"; good defaults say "work without it". Least surprise says "DELETE deletes"; a compliance requirement says deletion is a 30-day reversible process. Consistency says "every list paginates the same way"; one collection is capped at ten items by domain rule and pagination there is noise.
These are not paradoxes; they are unfinished requirement questions. The compliance case is not "violating least surprise" — the surprise was in pretending deletion is instant when the requirement says otherwise; the honest contract models the process (see DELETE: What Does Gone Mean? and Resources Have State Machines). Principles frame the debate; the requirement, made explicit, resolves it. What is *not* acceptable is resolving the tension silently and differently on every endpoint.
Key points
- Principles win ties between designs that both satisfy the requirement; they never override the requirement itself.
- Consistency compounds: one convention learned is every endpoint half-learned. Guard it with a written style sheet and review.
- Explicitness and bounded operations are the principles that pay at scale — silence and unboundedness both become load-bearing.
- Principle collisions are unfinished requirement questions; make the requirement explicit and it referees.
- Deviations are allowed — as documented decisions, not accidents.
Follow the failure
How the contract fails or gets misused, hop by hop — and what it costs when it completes.
- 1Team → endpoints: each engineer ships locally reasonable choices; no shared conventions exist.
- 2API → consumers: every endpoint has its own naming, envelope and pagination dialect.
- 3Consumers → SDKs: clients accrete special cases per endpoint; the API is "documented" by its quirks.
- 4New team member → API: extends the wrong pattern (there are four to choose from); the fork deepens.
- 5Team → cleanup: a "consistency pass" is proposed and rejected — by now it would break every consumer at once.
- Learnability: every endpoint must be studied individually; integration time scales with surface size instead of flattening.
- Client code quality: consumers write N variants of pagination/error/retry handling, each with its own bugs.
- The provider's own tooling: SDK generation, linting and contract testing all assume conventions that do not hold.
Design, observe, evolve
A contract decision is incomplete until you know how you would notice it failing and how it changes later.
- • Write the style sheet before the second endpoint: naming, response envelope, error shape, pagination, timestamps, id format.
- • Enforce mechanically where possible (spec linter in CI) and by review where not; deviation requires a written reason.
- • Prefer boring convention over local optimality — accept the 90%-good uniform answer.
- • When a convention must change, change it as a versioned, mapped migration — never by letting new endpoints quietly diverge.
- • Lint the API spec continuously; convention-drift count per release is a trackable metric.
- • Watch integration-time and time-to-first-successful-call for new consumers — the practical measure of learnability.
- • Support tickets asking "how does pagination work *on this endpoint*" indicate dialect drift.
- • Conventions make additive growth safe: a new endpoint that follows the sheet inherits consumer trust and tooling on day one.
- • Convention changes are themselves versioned and migrated, with the map ("endpoints on new envelope") kept in the catalog.
- • Conventions reject some locally better designs; the uniform answer is rarely the optimal answer for any single endpoint.
- • Style sheets and linters are maintenance; unenforced ones are worse than none because they document a fiction.