The Best API Design Happens Before the API Exists
API design often starts too late. By the time teams are debating endpoint names, field casing, or pagination style, the most important decisions may already be buried: who owns the resource, what lifecycle it follows, which failures users can recover from, and what compatibility promises the API must carry.
The visible API is the last artifact. The real design happens earlier.
The thesis
Good API design starts with lifecycle, authority, failure, and ownership. Endpoint shape should express those decisions, not substitute for them.
An API that looks clean but hides confused ownership will become difficult to operate. An API that exposes a clear model can survive imperfect naming.
The production pattern
A team needs a new interface. The first draft mirrors a database table or a screen. It has create, update, list, and delete operations. It passes a style review. Then edge cases arrive.
Who is allowed to change state after submission? Is deletion real deletion or archival? Can two callers update the same resource? Are updates patches, commands, or replacement documents? What happens if a caller retries after a timeout? Are IDs caller-generated or server-generated? Can the resource move between accounts, regions, or owners? Which fields are historical facts and which are current views?
These questions are not polish. They define the contract.
The model
Before reviewing endpoint shape, I ask for an API pre-design with six sections.
Resource identity: What is the durable thing? What is its identifier? Can identity change? Is it globally unique, scoped, or composite? If the answer depends on the caller, the resource model is not done.
Lifecycle: What states can the resource occupy? Which transitions are allowed? Which transitions are irreversible? Which actor or system initiates each transition? APIs without lifecycle discipline often leak internal states or allow invalid combinations.
Authority: Which system is the source of truth for each field and transition? If two systems believe they own the same fact, the API will eventually arbitrate a disagreement it was not designed to handle.
Failure semantics: What can fail before, during, and after side effects? What does the caller know after a timeout? Is retry safe? How are partial successes represented? The error model is part of the product.
Compatibility: Which consumers must keep working while the API evolves? Are readers tolerant of unknown fields? Can the API add states? How will deprecation happen? A beautiful v1 that cannot evolve is not beautiful.
Operational ownership: Who monitors this API? What are the service-level promises? What dashboards answer whether it is healthy? Who handles abusive callers, runaway usage, and data repair?
Only after these sections are clear do I care deeply about endpoint names.
Where this goes wrong
The counterpoint is that some APIs are deliberately disposable. A prototype, an internal script endpoint, or a narrow admin-only control may not deserve a heavy design process. A principal engineer should not force ceremony where the blast radius is tiny and reversibility is high.
The trick is being honest about disposability. Many "temporary" APIs become permanent because other systems integrate with them. If the API crosses team boundaries, stores durable data, triggers irreversible effects, or becomes part of a workflow people depend on, it is no longer casual.
Another failure mode is abstract purity. Teams can spend too long inventing a perfect resource model and miss the product need. API design should clarify decisions, not become an academic exercise.
What I do now
I ask teams to write example calls from the point of view of the caller, but I pair that with state diagrams and failure cases. A happy-path sample request is useful. A retry-after-timeout example is more revealing. A forbidden transition tells me more about the model than a successful create.
I also look for verbs that indicate hidden commands. Sometimes update is too vague. The caller is not updating a row; they are approving, canceling, submitting, restoring, transferring, or acknowledging. Naming the command can expose authorization, audit, and idempotency requirements.
For long-lived APIs, I prefer explicit idempotency keys on side-effecting operations, stable error categories, additive response evolution, and documented lifecycle transitions. I want enough structure that consumers can integrate without reading server code or asking the owning team how the system really behaves.
I also ask what the API will make easy to do accidentally. A permissive endpoint may look flexible, but it can allow callers to skip validation, create impossible states, or encode workflow assumptions outside the owning service. A restrictive endpoint may look less elegant, but it can protect invariants that the business depends on. The right answer depends on whether the API is a toolkit, a command surface, or the only safe entrance to a state machine.
This is where principal-engineer judgment matters. API design is not a taste contest about REST, RPC, resources, or events. It is a choice about where authority lives and how much freedom downstream owners should have without creating repair work for everyone else.
Closing takeaway
Design the API before the endpoints: name the resource, lifecycle, authority, failure semantics, compatibility promise, and owner, then let the surface shape follow.