Requestsnamingconsistencyconventionsstyle guideidstimestamps

One Vocabulary: Naming and Consistency

A consumer who has learned one endpoint should be able to predict every other. Casing, id formats, timestamps, money, envelopes, error shapes and header names are decided once, written in a style guide and enforced by a linter — because consistency is the cheapest documentation an API will ever have.

Follow the failure

Frame the contract

API design starts with a consumer, a design question and a guarantee — never with a URL.

Design question
If a consumer learns one operation of this API, how much of the rest can they guess correctly — and what enforces that the guess keeps being right as ten teams add endpoints?
Consumers
Developers integrating for the first time, who read one endpoint and extrapolate; SDK generators that turn every inconsistency into a language-level oddity; and the internal teams adding endpoints who will copy whatever the nearest example does.
The promise
A consistent API uses one casing, one id format, one timestamp format, one money representation, one list envelope, one error shape and one set of header conventions across every operation — documented in a style guide and enforced mechanically, so the vocabulary is learned once.
RequirementConsumersResource ModelStyleContractValidationAuthorizationErrorsIdempotencyPaginationVersioningObservabilityEvolutionTrade-offs

Inconsistency is a tax on every consumer, paid forever

The same concept spelled three ways — userId in one response, user_id in another, owner in a third; created as epoch seconds here, createdAt as an ISO string there, creation_date as YYYY-MM-DD somewhere else — is not a cosmetic problem. Every consumer writes a mapping layer to normalize it, every SDK generator produces three accessor styles, and every new integrator has to read every endpoint because none can be predicted from the last. The tax is paid on every integration, and it never goes away because fixing it later is a breaking change (Backward Compatibility: The Real Rules).

It arrives without anyone deciding. Team A ships the first endpoints in camelCase because their framework defaults to it; Team B copies a snake_case example from a partner API; a contractor adds epoch timestamps because the database stored them that way. No decision was wrong in isolation; the absence of a shared decision produced the mess. Consistency is therefore an organizational artifact — a written vocabulary and a mechanism that enforces it — not a matter of individual care (API Ownership and the Catalog decides who holds the pen).

The payoff of consistency is that the API becomes learnable by induction. A consumer who knows that lists return { data, next_cursor }, errors return { code, message, request_id, errors[] }, ids look like type_xxxx, and timestamps are RFC 3339 UTC, can write correct code against an endpoint they have never read. That is the sense in which consistency is documentation: it lets the docs say less, and it makes the Documentation Is Part of the Contract that remains describe *what* each operation does rather than re-explaining *how* every field is spelled.

The decisions a style guide should settle — once — with a defensible default for each
DecisionDefault worth adoptingWhyWhat inconsistency costs
Field casingOne of snake_case or camelCase, everywhere, including query paramsPredictability; SDK generators map one ruleTwo accessor styles in every SDK; mapping layers in every client
Resource idsOpaque strings with a type prefix: usr_7f9cSelf-describing in logs; no enumeration; not tied to a tableInteger ids that leak, collide across shards and cannot move
TimestampsRFC 3339 in UTC with Z, suffixed _atUnambiguous; sorts lexically; every language parses itEpoch-vs-ISO bugs, off-by-timezone incidents
Money{ "amount": 1999, "currency": "EUR" } in minor unitsNo floats; currency travels with the numberRounding drift; a 19.99 that becomes 19.989999
Booleans and enumsis_/has_ prefixes; lowercase snake_case enum valuesReadable; stable across languagesMixed active/isActive/ACTIVE in one payload
CollectionsPlural nouns; envelope { data, next_cursor }Room for pagination and metadataBare arrays that can never grow a cursor
ErrorsOne shape from The Error Model: Structure Over Apology on every operationClients branch on code oncePer-endpoint error formats; string matching
HeadersIdempotency-Key, Request-Id, Retry-After, RateLimit-* — the same names everywhereMiddleware and SDKs handle them onceThree spellings of the request id header
Nulls and absenceFields always present; null for no value; absent means unchanged on PATCHDeterministic shape (Request Contracts: Required, Optional, Null and Absent)Clients checking three conditions per field

The style guide and the linter

A style guide is the vocabulary written down: one page, versioned, owned, with the decisions above and the reasoning behind each. The reasoning matters more than the choice — snake_case vs camelCase is a coin flip, but "because our SDK generator and our largest consumer use it" survives the next reorganization. The guide also records the exceptions and why they exist (legacy /v1/reports uses epoch timestamps; migrating in Q4), which stops the exception from being copied as a pattern.

A guide nobody enforces is a wish. Enforcement is mechanical: a linter over the OpenAPI: Describing the Contract, Not Designing It description (or whatever schema the API is described in) that fails the build on a non-conforming field name, a missing envelope, a timestamp typed as integer, an error response that is not the shared shape. Spectral-style rulesets exist for this; a custom script over the schema is a day's work. The linter runs in CI on every schema change and in the API review, and its rules *are* the style guide in executable form — this is one of the concrete arguments for Schema-First vs Code-First.

The review process closes the loop. Every new endpoint is reviewed against the guide by someone outside the authoring team; the review asks the induction question — "could a consumer of our other endpoints guess this one?" — and treats a "no" as a defect. The Review Lab's linter findings ("inconsistent id format", "list without envelope") are the same checks, run against a design before it ships.

Three endpoints, three vocabularies — each locally reasonable, jointly unlearnable
1GET /users/42
2{ "userId": 42, "createdAt": 1724580000, "active": true, "Balance": 19.99 }
3
4GET /projects/18
5{ "id": "18", "owner_id": 42, "creation_date": "2026-08-25", "is_active": "yes",
6 "members": [ … ] } # bare array, no pagination possible
7
8GET /invoices
9[ { "InvoiceID": "INV-0001", "created": "25/08/2026 10:14", "amountCents": 1999 } ]
10
11# Error from /users: { "error": "not found" }
12# Error from /projects: { "message": "Project not found", "status": 404 }
13# Error from /invoices: HTML.
One vocabulary; learn it once, predict the rest
1GET /users/usr_7f9c
2{ "id": "usr_7f9c", "created_at": "2026-08-25T09:20:00Z", "is_active": true,
3 "balance": { "amount": 1999, "currency": "EUR" } }
4
5GET /projects/proj_18a2
6{ "id": "proj_18a2", "owner": { "id": "usr_7f9c", "display_name": "A. Bee" },
7 "created_at": "2026-08-25T10:14:03Z", "is_active": true }
8
9GET /projects/proj_18a2/members?limit=20
10{ "data": [ … ], "next_cursor": "eyJ…" }
11
12# Every error, every endpoint:
13{ "code": "NOT_FOUND", "message": "Project not found", "request_id": "req_…" }

The second API needs a third of the documentation because the shape of everything is implied by the shape of anything. The first API needs a mapping layer in every consumer, and an SDK generator produces three different clients for it.

Consistency across time, not just across endpoints

The hardest consistency is temporal. The style guide exists so that the endpoint added in year three matches the one shipped in year one, when the original team is gone. That is why the guide, the linter and the review are a system: the guide holds the decisions, the linter enforces the checkable ones, and the review catches the rest. Any one of them alone decays.

When the vocabulary must change — an id format that has to grow, a timestamp precision that has to increase — the change follows API Migration: Running the Change End to End: both forms accepted on input for a window, one form emitted, telemetry on who still sends the old form, then removal. A vocabulary change is by definition API-wide, which makes it the most expensive kind of breaking change and the strongest reason to get the vocabulary right before the second endpoint ships.

Consistency is not uniformity of *semantics*. Two operations that genuinely differ should look different — a command-style POST /orders/{id}/ship next to resource-style CRUD is fine when Resource or Action? justified it, and The "REST Purity" Anti-Pattern warns against flattening real differences for the sake of visual sameness. The rule is that *the same thing* is spelled the same way; different things are allowed, and encouraged, to look different.

  • Write the vocabulary down with the reasoning, one owner, versioned; record exceptions and their expiry.
  • Lint the schema in CI: casing, id format, timestamp type, envelope, error shape, header names.
  • Review by induction: could a consumer of the other endpoints guess this one?
  • Change the vocabulary as a migration, never as a patch; it is API-wide by definition.
  • Same things spelled the same; genuinely different operations may look different.

Key points

  • Inconsistency is a tax every consumer pays on every integration, forever, because fixing it later is a breaking change.
  • It arrives without a decision; consistency is an organizational artifact — a written vocabulary plus mechanical enforcement.
  • A consistent API is learnable by induction, which is why it needs less documentation and produces cleaner generated SDKs.
  • Style guide, schema linter in CI, and cross-team review are one system; each alone decays over time.
  • Consistency means the same thing spelled the same way — not flattening genuinely different operations into one look.

Follow the failure

How the contract fails or gets misused, hop by hop — and what it costs when it completes.

  1. 1
    Team A → v1: ships camelCase, integer ids, epoch timestamps — the framework defaults.
  2. 2
    Team B → v1: adds endpoints in snake_case with ISO dates, copying a partner API they admire.
  3. 3
    Consumer → SDK: the generated client has userId and user_id accessors on adjacent types; the integrator writes a normalization layer.
  4. 4
    Team C → year three: copies the nearest example, which is whichever one they opened; inconsistency now has three dialects.
  5. 5
    Platform team → cleanup: discovers that unifying the vocabulary is a breaking change across 40 endpoints and 200 consumers.
What breaks
  • Every consumer builds and maintains a mapping layer; SDK generators emit inconsistent clients; onboarding time grows with every dialect.
  • Timestamp and money inconsistencies produce real bugs — timezone drift, rounding errors — not just annoyance.
  • The API cannot be fixed cheaply: vocabulary unification is the widest possible breaking change.

Design, observe, evolve

A contract decision is incomplete until you know how you would notice it failing and how it changes later.

Design the contract
  • • Adopt a style guide before the second endpoint: casing, ids, timestamps, money, enums, envelopes, errors, headers, null semantics — with reasoning.
  • • Enforce it with a schema linter in CI and in the API review; fail the build on non-conformance.
  • • Prefix ids by type, use RFC 3339 UTC timestamps suffixed `_at`, minor-unit money with currency, and the shared [[error-model]] on every operation.
  • • Record exceptions with an expiry, and review new endpoints by asking whether they could be guessed from existing ones.
  • • Treat any vocabulary change as an API-wide migration with dual acceptance, telemetry and a deadline.
Observe in production
  • • Linter findings per pull request trend toward zero when the guide is alive; a rising count means new teams have not adopted it.
  • • Support and onboarding questions of the form "why is this field named differently here?" are the consumer-side cost made visible.
  • • Field-name and format variety extracted from the schema (how many timestamp formats, how many id styles) is a measurable consistency score.
Evolve without breaking
  • • New fields and endpoints that follow the guide are additive and predictable; the guide is what makes additive growth stay learnable.
  • • Vocabulary changes are migrations: accept both forms, emit the new one, measure, deprecate, remove ([[api-migration]]).
  • • Recording the reasoning lets a future team revise a decision deliberately instead of re-litigating it per endpoint.
What it costs
  • • A guide and a linter are upfront process cost that feels bureaucratic at endpoint number two and indispensable at endpoint number forty.
  • • Strict enforcement occasionally blocks a locally better name; the consistency win usually outweighs it, and the exception mechanism exists for when it does not.
  • • Some conventions (prefixed ids, envelopes) add bytes and ceremony to tiny responses.

Misconceptions

Claim
“Naming is bikeshedding; the semantics are what matter.”
Reality
Semantics are what matter, and inconsistent naming hides them: a consumer who cannot predict a field name cannot predict its meaning either. The point is not which convention, but that there is exactly one.
Claim
“We will unify the naming in v2.”
Reality
v2 is a migration across every consumer of every endpoint — the most expensive change an API can make. The cheap moment was before the second endpoint shipped; the second cheapest is now.
Claim
“Consistency means every endpoint should look like CRUD.”
Reality
Consistency means the same concept is spelled the same way. A command-style operation next to resource-style ones is consistent if commands always look like that; forcing real differences into one shape is The "REST Purity" Anti-Pattern, not consistency.

Apply it