7 API Design Mistakes That Will Haunt You
After building four API-first products — DocuMint, CronPing, FlagBit, and WebhookVault — we have encountered every API design mistake in the book. Some of them we made ourselves. Here are seven that s
After building four API-first products — DocuMint, CronPing, FlagBit, and WebhookVault — we have encountered every API design mistake in the book. Some of them we made ourselves. Here are seven that seem fine at first but will haunt you as your product grows.
1. Using 200 for Everything
The temptation is understandable: just return 200 and put the error in the response body. It is simpler. Your client code only checks one status code. But HTTP status codes exist for a reason — they let intermediaries (proxies, CDNs, monitoring tools, browsers) understand what happened without parsing your response body.
Use 201 for resource creation. Use 204 for successful deletion. Use 400 for client errors. Use 404 for missing resources. Use 422 for validation failures. Use 429 for rate limiting. Use 500 for server errors. Your monitoring dashboard will thank you.
2. Unbounded List Endpoints
Returning all records from a list endpoint works fine with 10 items. It falls apart at 10,000. Always paginate from day one, even if your database has five rows. Cursor-based pagination is better than offset-based for large datasets because it handles concurrent insertions and deletions gracefully.
3. Breaking Changes Without Versioning
The first time you rename a field, remove an endpoint, or change a response format, every existing integration breaks. URL-based versioning (/api/v1/) is the simplest approach that works. Header-based versioning is more "correct" but harder for developers to use. Whatever you choose, choose it before your first user, not after your hundredth.
4. Inconsistent Naming
Is it created_at or createdAt? Is it user_id or userId? Is the list endpoint /users or /user? Pick a convention and enforce it everywhere. Our convention: snake_case for JSON keys, plural nouns for collections, ISO 8601 for dates, UUIDs for identifiers. Every endpoint follows the same pattern.
5. No Idempotency Keys
Network requests fail. Clients retry. If your POST endpoint creates a new resource every time it is called with the same data, you get duplicate invoices, duplicate payments, duplicate monitors. Idempotency keys (a client-generated UUID sent in a header) let your API recognize retries and return the original response instead of creating duplicates.
6. Leaking Internal Implementation
Your API response should not expose database column names, internal IDs, SQL error messages, stack traces, or file paths. These leak implementation details that make it harder to refactor internally and create security vulnerabilities. Return meaningful error messages like "Invoice not found" instead of "SQLITE_ERROR: no such row in invoices table".
7. Ignoring Rate Limiting Until It Is Too Late
Rate limiting is not just about protecting your server. It is about providing a fair experience for all users and preventing abuse. Add rate limiting from the start, even if it is generous. Return 429 with a Retry-After header. Document your limits clearly. Users appreciate knowing the rules upfront rather than discovering them when their integration breaks.
The Pattern
Notice the pattern: every mistake on this list seems harmless with one user and five endpoints. They only become painful at scale. But by the time you feel the pain, fixing it means breaking existing integrations. The cost of getting it right on day one is almost zero. The cost of fixing it on day one hundred is enormous.
Design your API as if your hundredth user is already watching. Because by the time they arrive, it is too late to change.