A bad API is one of the most expensive mistakes you can make. Every consumer has to work around your design decisions forever. Here's how I design APIs that don't make people miserable.
Be predictable
If GET /users returns a list of users, then GET /products should return a list of products in the same format. Same pagination. Same filtering syntax. Same error structure. Consistency is the single most important quality of an API.
A developer who learns one endpoint should be able to guess how every other endpoint works. If they can't, your API has a design problem.
Use the right status codes
200 means success. 201 means created. 400 means the client messed up. 401 means not authenticated. 403 means not authorized. 404 means not found. 500 means the server messed up.
That's the whole list for 90% of APIs. Don't return 200 with an error body. Don't return 500 for validation errors. Use the codes the way they were designed to be used.
Error messages are UI
When something goes wrong, the developer consuming your API needs to know: what failed, why it failed, and what they can do about it. A good error response looks like this:
- A machine-readable code —
VALIDATION_ERROR, notERR_001. Something a developer can switch on. - A human-readable message — "The email field must be a valid email address." Not "Invalid input."
- The field that failed — Point to exactly where the problem is. Don't make them guess.
Paginate everything
Every list endpoint should be paginated from day one. Not "when you have enough data." From day one. It's a trivial amount of work upfront and prevents cascading failures when your database grows.
Use cursor-based pagination, not offset-based. Offsets break when data changes between requests. Cursors don't.
Version your API
Put the version in the URL: /api/v1/users. Not in headers. Not in query params. In the URL, where it's visible and obvious. When you need to make a breaking change, create v2. Keep v1 running until everyone has migrated.
Document by example
Developers don't read API documentation top to bottom. They scan for an example that looks like what they're trying to do, copy it, and modify it. Make sure every endpoint has a complete request/response example with realistic data. Not string and number — actual values.
Swagger/OpenAPI is the bare minimum. A good API has a getting-started guide with copy-paste examples that work on the first try.
The golden rule
Before shipping an endpoint, use it yourself. Build a small client. Try to do real things with it. If it feels awkward to use, it is awkward. Fix it before anyone else has to deal with it.
The best APIs feel invisible. You don't notice them because they just work the way you expect. That's the goal.