If Your API Needs a Long Explanation It Is Probably Too Complex
by Eric Hanson, Backend Developer at Clean Systems Consulting
The explanation as a diagnostic
Before you write a paragraph of documentation for an endpoint, stop and read what you are about to write. If you are explaining why a field has different semantics depending on request context, the field is probably two different fields. If you are explaining the correct sequence of calls required to accomplish a single task, that task is probably one endpoint. If you are documenting exceptions to the general behavior, the general behavior probably needs rethinking.
Documentation should clarify, not compensate. An API endpoint that requires a tutorial is an API endpoint that has leaked its implementation into its interface.
Symptoms of accidental complexity
Context-dependent field semantics: A request field that means different things based on other fields in the same request:
{
"type": "subscription",
"amount": 100,
"interval": "monthly" // only valid if type is subscription; ignored otherwise
}
The consumer must know the rule. When they forget and set interval on a one-time charge, it is silently ignored. The fix is two separate endpoints or two separate request schemas — POST /charges and POST /subscriptions.
Side effects encoded in a single endpoint:
A POST /orders that creates the order, charges the payment method, sends a confirmation email, updates inventory, and notifies a warehouse system — all in one synchronous call. When any one of these steps fails, the client receives an error without knowing which step failed or what state things are in.
Complex side effects belong in an event-driven model where the initial response confirms acceptance, and each downstream step is a separate observable event.
Implicit state machine: An endpoint whose behavior depends on the current state of the resource being operated on, without explicitly modeling the state machine:
"You can call PATCH /subscriptions/{id} with status: 'paused' only if the subscription is currently active. If it is already paused, you get a 409. If it is cancelled, you get a 422. If it is in trial, you get a 400 unless you also provide resume_date."
The consumer must memorize this. The fix is to make the state machine explicit — model the allowed transitions, return clear errors that name the current state and the transition attempted, and document the state machine as a diagram rather than prose.
The right level of abstraction
One indicator of appropriate API design: the vocabulary in your API matches the vocabulary in your domain, not your database schema.
If your users think in terms of "subscriptions" and "plans" and "billing cycles," but your API exposes "recurring_payment_schedules" and "product_tier_assignments," the consumer has to translate between two vocabularies constantly. That translation lives in the consuming code and in every developer's head.
Name things the way your users think about them. POST /subscriptions is better than POST /recurring-payment-schedule-configurations even if the latter is more precisely accurate to the underlying data model.
The endpoint count as a design signal
More endpoints is not better. An API with 200 endpoints for a domain that should have 30 has usually modeled its internal service decomposition rather than its external use cases.
Audit which endpoints actually get called. In practice, most APIs have a long tail of endpoints that account for very little traffic. These are candidates for consolidation or deprecation.
A useful question for every endpoint: what single task does this enable? If the answer is "it depends" or requires a conjunction ("it does X and also Y"), the endpoint is probably doing too much.
When simplification requires breaking changes
Sometimes you cannot simplify without a version bump. A response field that currently serves two purposes cannot be split into two fields without clients updating their parsing logic.
In these cases:
- Design the simpler version in v2 from the start, before building v2 features.
- The migration from v1 is the opportunity to pay down the complexity debt.
- Document the complexity in v1 clearly and document the simplification in the v2 migration guide — "if you were working around X in v1, you no longer need to."
Complexity deferred to documentation is still complexity. The document is evidence that the design needs attention — use it as input to the next design iteration, not as a permanent substitute for a simpler interface.