Why Your API Feels Inconsistent and How to Fix It
by Eric Hanson, Backend Developer at Clean Systems Consulting
Inconsistency is a scaling problem, not a talent problem
You don’t notice API inconsistency when one team owns everything.
You notice it when:
- multiple teams contribute to the same API surface
- services evolve at different speeds
- deadlines push local decisions over global consistency
Suddenly:
- one endpoint uses
snake_case, another usescamelCase - pagination works differently across resources
- filtering syntax changes depending on who built it
- error formats vary just enough to break generic handling
None of this happens because engineers don’t know better. It happens because there’s no system enforcing consistency.
And once it creeps in, it compounds. Every new endpoint becomes a coin flip.
The root cause: local optimization
Most inconsistencies trace back to reasonable local decisions:
- “This service uses offset pagination because it’s easier with our ORM”
- “We named it differently because this domain has slightly different terminology”
- “We returned this shape because it matched our database”
Individually, these choices are defensible.
Collectively, they create an API that feels unpredictable.
From a consumer’s perspective, the cost is real:
- more conditional logic in clients
- harder onboarding
- subtle bugs when assumptions don’t hold across endpoints
The fix is not better engineers. It’s fewer degrees of freedom.
Standardize the things that repeat
You don’t need to standardize everything. But you absolutely need to standardize the parts that show up everywhere.
Pagination
Pick one approach and stick to it.
Cursor-based (recommended for most cases):
GET /orders?cursor=eyJpZCI6MTIzfQ&limit=50
Response:
{
"data": [...],
"page": {
"next_cursor": "eyJpZCI6MTczfQ",
"has_more": true
}
}
Offset-based is simpler but breaks down at scale (large offsets, inconsistent results under writes).
The mistake is supporting both across different endpoints.
Filtering and sorting
Define a consistent query pattern:
GET /orders?status=active&created_after=2024-01-01&sort=-created_at
Rules:
- filters are simple key-value pairs
- sorting uses
-for descending - timestamps use ISO 8601
Don’t let each team invent their own syntax.
Field naming
Pick one convention:
snake_case(common in Python ecosystems)camelCase(common in JavaScript)
Then enforce it everywhere.
Mixed conventions are one of the fastest ways to make an API feel sloppy.
Error structure
Define a single format and never deviate:
{
"error": {
"code": "INVALID_STATUS",
"message": "Status must be one of: active, cancelled",
"details": {
"field": "status"
}
}
}
If one endpoint returns strings and another returns structured errors, clients can’t generalize behavior.
Consistency requires enforcement, not documentation
Most teams have API guidelines somewhere. Few teams enforce them.
That’s why inconsistency keeps creeping back.
You need automated enforcement.
OpenAPI + linting
Define your API with OpenAPI 3.x and lint it using tools like Spectral.
Example rule (pseudo-Spectral):
rules:
pagination-format:
description: "All list endpoints must use cursor-based pagination"
given: "$.paths[*][get].parameters"
then:
function: pattern
functionOptions:
match: "cursor"
This turns “guidelines” into something that blocks a PR.
Shared libraries (with caution)
Provide SDKs or internal libraries for:
- pagination helpers
- error formatting
- response envelopes
Example (Node.js/TypeScript):
export function paginatedResponse(data, nextCursor) {
return {
data,
page: {
next_cursor: nextCursor,
has_more: !!nextCursor,
},
};
}
This reduces divergence, but it can also become a bottleneck if the library is hard to evolve.
Use it to enforce patterns, not to hide everything behind abstractions.
API review as a gate
For externally exposed APIs, treat changes like schema migrations.
Have a lightweight review checklist:
- Does this follow existing patterns?
- Is naming consistent with similar resources?
- Are errors structured correctly?
This doesn’t need a committee. It needs discipline.
Be careful with backward compatibility
Fixing inconsistency in a live API is where things get tricky.
You can’t just “clean things up” if clients depend on the current behavior.
Options:
- Introduce consistency in new endpoints only
- Add new fields while keeping old ones (then deprecate)
- Version the API if changes are breaking
Example:
{
"user_id": "123",
"userId": "123" // temporary duplication during migration
}
It’s not pretty, but it avoids breaking consumers.
The key is to stop the bleeding first. Then gradually fix what already exists.
The tradeoff: consistency vs flexibility
Strict consistency has downsides:
- Slower development when patterns don’t fit perfectly
- Friction for teams with unique domain needs
- Occasional over-generalization
But inconsistency is worse:
- every integration becomes bespoke
- shared tooling becomes harder to build
- cognitive load increases across the board
Consistency is a force multiplier. It makes everything built on top of your API easier.
What to do differently this week
Pick three cross-cutting concerns in your API:
- pagination
- error handling
- naming conventions
Write down the exact rules for each—no ambiguity.
Then enforce one of them automatically (linting, shared helper, or review gate).
You don’t fix inconsistency by documenting more. You fix it by removing choice where it doesn’t add value.