Bad UI is embarrassing. A bad API is a liability that outlives the team that built it.
An API is a contract. Unlike UI code — which you can change and users adapt — an API change can silently break every downstream system that depends on it. We've learned this the hard way across 50+ integrations. These are the design decisions we stopped treating as optional.
Versioning from day one
Every API we build ships with versioning from the first endpoint. Not because we plan to break things, but because we know we'll need to evolve the API without breaking existing consumers.
We prefer URL-based versioning (/v1/resources) over header-based versioning for simplicity. It's visible in logs, easy to route, and unambiguous.
Resource design matters more than you think
The single most impactful decision in API design is how you model your resources. Get this right, and the API evolves gracefully. Get it wrong, and you're fighting the design forever.
Our approach: model resources around business concepts, not database tables. An "Order" API resource might pull data from five database tables, but the consumer doesn't need to know or care about that.
Consistent error handling
Every API we build follows the same error response format. A machine-readable error code, a human-readable message, and structured details for validation errors. Consumers should be able to write a single error handler that works across every endpoint.
Pagination, filtering, and sorting
These aren't features to add later. If an endpoint returns a list, it ships with cursor-based pagination, field-level filtering, and configurable sorting. We've seen too many systems break when a "small" dataset grows to 100K records and the unpaginated endpoint starts timing out.
Rate limiting and authentication
Every production API ships with rate limiting and proper authentication. No exceptions. Even internal APIs between our own services get authentication — because "internal" today becomes "exposed to partners" tomorrow.
Documentation as code
API documentation lives alongside the code and is generated from the source. OpenAPI specs, auto-generated client SDKs, and interactive documentation. If the docs can drift from the implementation, they will.
For interactive docs, we've largely moved away from the default Swagger UI in favour of Scalar and Stoplight Elements. The practical reasons: Scalar renders cleanly, supports multiple code sample languages out of the box, and is trivial to self-host or embed. Stoplight gives us a richer editing and mocking environment when a client's team is actively designing the API contract before implementation starts — the visual editor reduces back-and-forth with non-engineering stakeholders considerably.
Neither is a hard requirement. What matters is that the documentation is generated from the same source that produces the API — not written by hand in a wiki that nobody updates.
The principles
After building and consuming hundreds of APIs, these are the principles we don't compromise on. Be consistent in naming, structure, and behavior — an API that surprises its consumers is a broken API. Be explicit about what can change and what won't. Design for the consumer, not the implementation. Make errors helpful and actionable. Version everything from day one.
None of these are difficult ideas. The challenge is enforcing them when deadlines are tight and the temptation to ship a quick endpoint is strong. The APIs we've seen cause the most damage weren't built by bad engineers — they were built by good engineers under pressure who skipped the fundamentals just this once.

