What You'll Learn
- Which status code to return for each error scenario
- How to structure error response bodies
- The difference between 400, 401, 403, 404, 409, 422, 429
- The RFC 7807 "Problem Details" standard
Why This Matters
Bad error messages waste hours. A response of 404 Not Found with no body leaves the developer wondering: wrong URL? wrong ID? missing auth? Good error responses tell the developer exactly what went wrong and how to fix it. This lesson shows you how.
Status Codes for Common Scenarios
| Scenario | Code | Name |
|---|---|---|
| Resource created | 201 | Created |
| Successful read/update | 200 | OK |
| Successful delete | 204 | No Content |
| Async job accepted | 202 | Accepted |
| Malformed JSON | 400 | Bad Request |
| Missing auth token | 401 | Unauthorized |
| Valid token, no permission | 403 | Forbidden |
| Resource does not exist | 404 | Not Found |
| Method not allowed on this URL | 405 | Method Not Allowed |
| Conflict (duplicate email) | 409 | Conflict |
| Wrong Content-Type | 415 | Unsupported Media Type |
| Valid JSON, validation failed | 422 | Unprocessable Entity |
| Rate limit hit | 429 | Too Many Requests |
| Server crash | 500 | Internal Server Error |
| Database down | 503 | Service Unavailable |
| Upstream timeout | 504 | Gateway Timeout |
The Anatomy of a Good Error Response
Just returning a status code is not enough. The body should explain what went wrong and how to fix it. A good error response has:
- A stable error code or slug (e.g.,
VALIDATION_FAILED) - A human-readable message
- Optional details (which field failed, what was wrong)
- Optional documentation URL
- Optional request ID for support
Example — Validation error
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
{
"error": {
"code": "VALIDATION_FAILED",
"message": "The request body failed validation.",
"details": [
{
"field": "email",
"issue": "must be a valid email address",
"value": "anita@"
},
{
"field": "age",
"issue": "must be greater than 0",
"value": -5
}
],
"documentation_url": "https://docs.example.com/api/errors/validation",
"request_id": "req_abc123"
}
}
Example — Authentication error
HTTP/1.1 401 Unauthorized
Content-Type: application/json
WWW-Authenticate: Bearer
{
"error": {
"code": "MISSING_TOKEN",
"message": "Authentication required. Send a Bearer token in the Authorization header.",
"documentation_url": "https://docs.example.com/api/auth"
}
}
Example — Conflict (duplicate)
HTTP/1.1 409 Conflict
Content-Type: application/json
{
"error": {
"code": "DUPLICATE_EMAIL",
"message": "A user with this email already exists.",
"resource": "User",
"conflicting_field": "email",
"value": "anita@example.com"
}
}
Example — Rate limited
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 60
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1692820860
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests. Try again in 60 seconds."
}
}
The RFC 7807 "Problem Details" Standard
RFC 7807 defines a standard JSON format for HTTP errors. It uses these fields:
type— a URL to documentation about the errortitle— a short human-readable descriptionstatus— the HTTP status codedetail— a longer explanationinstance— a URL identifying this specific occurrence
Example
HTTP/1.1 400 Bad Request
Content-Type: application/problem+json
{
"type": "https://docs.example.com/errors/validation",
"title": "Invalid request",
"status": 400,
"detail": "The 'email' field is required.",
"instance": "/users/failed-validation-req-abc"
}
Using RFC 7807 gives your API a consistent error format that tools and clients can recognize.
What to Put in the Body for 5xx Errors
For 5xx (server errors), do not leak stack traces or internal details. Return a generic message and a request ID so support can find the issue in logs:
HTTP/1.1 500 Internal Server Error
Content-Type: application/json
{
"error": {
"code": "INTERNAL_ERROR",
"message": "Something went wrong on our end. Please try again.",
"request_id": "req_abc123",
"support_url": "https://support.example.com"
}
}
Common Mistakes
- Returning 200 with an error in the body. Some APIs do this — it breaks HTTP clients that check the status code. Always return a proper 4xx or 5xx.
- Using 401 for permission errors. 401 = "I don't know who you are". 403 = "I know you, but you can't do this".
- Using 404 to hide authorization failures. Some APIs return 404 instead of 403 to avoid leaking that a resource exists. This is a deliberate security choice but should be documented.
- Generic error messages. "Bad request" with no detail is useless. Tell the developer which field failed and why.
- Leaking stack traces in 500 errors. Internal details should never reach the client. Log them server-side; return a generic message with a request ID.
- Inconsistent error formats. One endpoint returns
{"error": "..."}, another returns{"message": "..."}. Pick one format and use it everywhere.
Practical Exercise (10 minutes)
For each scenario, write the correct status code and a sample error response body:
- User sends a POST with
{"email": "not-an-email"}. - User sends a request without an auth token.
- User sends a valid token but tries to access another user's data.
- User requests
/users/999999but no such user exists. - User sends a POST with
{"email": "existing@example.com"}and that email is already registered. - User hits the API 1000 times in 10 seconds.
- Database crashes mid-request.
Mini Challenge
Design the error response format for a new API. Decide: do you use RFC 7807 or a custom format? What fields does every error have? What does the documentation URL look like? Write three example error responses covering a 400, a 401, and a 500.
Key Takeaways
- Use the right status code: 201 created, 204 deleted, 400 malformed, 401 missing auth, 403 forbidden, 404 not found, 409 conflict, 422 validation, 429 rate limited, 500 server error.
- Every error response should have: a stable error code, a message, optional field-level details, and a documentation URL.
- RFC 7807 "Problem Details" is a standardized error format worth adopting.
- Never return 200 with an error body. Never leak stack traces. Never use 401 for permission errors.
- Be consistent: one error format across the entire API.
Previously: Lesson 20 covered versioning and idempotency.
Today: You learned how to design error responses that help developers fix problems.
Next: Module 3 is complete. Module 4 begins with lesson 22 — How to Call an API from the Browser.
FAQ
Should I use 400 or 422 for validation errors?
Use 400 when the JSON itself is malformed (missing brace, wrong quotes). Use 422 when the JSON is valid but the values fail business rules (invalid email, age negative). Some APIs use 400 for both, but 422 is more precise.
What should I do for "not found" when the user is not authenticated?
Return 401, not 404. The user needs to authenticate before you can tell them whether the resource exists. After authentication, return 404 if it does not exist or 403 if it exists but they cannot access it.
Comments
Comments
Post a Comment