Nothing kills developer experience faster than bad error responses. You know the feeling — you hit an endpoint, get a 500 Internal Server Error with zero context, and spend 30 minutes guessing what went wrong.
Here are 7 mistakes API developers keep making, and how to fix each one.
1. Returning HTML Error Pages from APIs
The classic. Your API returns a text/html 404 page from Nginx instead of JSON.
// ❌ Bad: HTML default error page
<html><body><h1>404 Not Found</h1></body></html>
// ✅ Good: Structured JSON error
{
"error": {
"code": "NOT_FOUND",
"message": "The resource /api/users/999 was not found"
}
}
Fix: Always set a catch-all JSON error handler in your framework.
2. Using 200 OK for Everything
Some APIs wrap every response in 200 OK with a success: false field. This breaks HTTP clients, caching, and monitoring tools.
// ❌ Bad
HTTP 200 OK
{ "success": false, "error": "Invalid email" }
// ✅ Good
HTTP 422 Unprocessable Entity
{ "error": { "code": "VALIDATION_ERROR", "message": "Invalid email format", "field": "email" } }
Fix: Use proper HTTP status codes. 4xx for client errors, 5xx for server errors.
3. Inconsistent Error Shapes
One endpoint returns { "error": "msg" }, another returns { "message": "msg", "status": 400 }, a third returns { "errors": ["msg"] }. Consumers can't write a single error handler.
// ✅ Pick ONE format and stick to it everywhere
const errorResponse = (res, status, code, message, details = null) => {
const body = {
error: {
code, // Machine-readable: "VALIDATION_ERROR"
message, // Human-readable: "Email is required"
}
};
if (details) body.error.details = details;
return res.status(status).json(body);
};
4. Exposing Stack Traces in Production
Leaking internal errors is both a security risk and unhelpful to API consumers.
// ❌ Bad: Stack trace in production
{
"error": "TypeError: Cannot read property 'id' of undefined",
"stack": "at UserService.getUser (/app/src/services/user.js:42:15)..."
}
// ✅ Good: Clean message + internal logging
{
"error": {
"code": "INTERNAL_ERROR",
"message": "An unexpected error occurred",
"request_id": "req_abc123"
}
}
Fix: Include a request_id so developers can reference it in support tickets while you check your logs.
5. Missing Validation Details
Telling developers what failed without telling them where or why.
// ❌ Bad
{ "error": "Validation failed" }
// ✅ Good: Field-level detail
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{ "field": "email", "message": "Must be a valid email address" },
{ "field": "age", "message": "Must be between 1 and 150" }
]
}
}
6. No Rate Limit Information in Headers
Returning 429 Too Many Requests without telling the developer when they can retry.
// ✅ Always include these headers
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1708300800
Retry-After: 30
{
"error": {
"code": "RATE_LIMITED",
"message": "Rate limit exceeded. Retry after 30 seconds.",
"retry_after": 30
}
}
7. Not Documenting Error Codes
You have 15 possible error codes but they only appear when things break. Developers discover them one at a time in production.
Fix: Add an error reference to your API docs:
| Code | HTTP Status | Description |
|---|---|---|
VALIDATION_ERROR |
422 | Request body failed validation |
NOT_FOUND |
404 | Resource does not exist |
RATE_LIMITED |
429 | Too many requests |
UNAUTHORIZED |
401 | Missing or invalid API key |
FORBIDDEN |
403 | Valid key but insufficient permissions |
INTERNAL_ERROR |
500 | Something broke on our side |
Quick Checklist
Before shipping your API, verify:
- [ ] All error responses return JSON (not HTML)
- [ ] HTTP status codes match the actual error type
- [ ] Every error has the same shape
- [ ] No stack traces leak to production
- [ ] Validation errors include field-level details
- [ ] Rate limit responses include Retry-After
- [ ] Error codes are documented
Good error responses are the difference between a 5-minute integration and a 5-hour one. Your API consumers will thank you.
Originally published at 1xapi.com/blog
Top comments (0)