An API that's boringly consistent — the same patterns for every resource, the same error shape everywhere — is easier to work with than one that's individually elegant in each endpoint but inconsistent between them. Consistency is what lets a developer guess correctly without reading the docs every time.
GET /users # list users
GET /users/42 # get one user
POST /users # create a user
PATCH /users/42 # update a user
DELETE /users/42 # delete a user
Plural nouns for collections, the HTTP verb carrying the action, not the URL (avoid /getUser or /deleteUser) — this convention is old, well understood, and worth following even when you could invent something "better."
GET /v1/users/42
Adding a version prefix before you have any consumers costs nothing. Adding it after you have consumers who depend on unversioned responses means every future breaking change is now a painful, coordinated migration instead of a simple /v2 rollout alongside the existing one.
{
"error": {
"code": "VALIDATION_FAILED",
"message": "Email is required",
"field": "email"
}
}
Every error response using the same structure means client code can handle errors generically, rather than writing custom parsing logic per endpoint. A machine-readable code alongside the human-readable message lets clients branch on logic without string-matching the message text, which can change wording without warning.
GET /users?cursor=eyJpZCI6NDJ9&limit=50
Offset-based pagination (?page=3) seems simpler but degrades badly on large, frequently-changing datasets — items shift between pages as data changes underneath the request. Cursor-based pagination stays correct regardless of concurrent writes, at the cost of not supporting "jump to page 10" directly.
Good API docs specify required vs. optional fields, exact types, what happens on invalid input, and rate limits — the things a consumer actually needs to build against confidently, not just a list of URLs with a one-line description each.
Comments