Quick answer
Good REST API error handling gives clients a clear HTTP status code, a stable machine-readable error code, a human-readable message, and enough context to fix or report the problem.
The goal is not to expose internal details. The goal is to make failures predictable for users, client developers, logs, alerts, and support workflows.
A useful error shape
A practical JSON error response looks like this:
{
"error": "validation_failed",
"message": "One or more fields are invalid.",
"fields": {
"email": "Enter a valid email address."
},
"requestId": "req_7f3a"
}
The error field should be stable for machines. The message field should be readable for humans. The requestId helps connect the client error to logs and traces.
Use status codes consistently
HTTP status codes are the first signal clients receive.
Common choices:
| Status | Use |
|---|---|
400 Bad Request | Malformed JSON or invalid request shape. |
401 Unauthorized | Authentication is missing or invalid. |
403 Forbidden | The caller is authenticated but not allowed. |
404 Not Found | Resource does not exist or should not be revealed. |
409 Conflict | Request conflicts with current state. |
422 Unprocessable Entity | Field-level semantic validation failed. |
429 Too Many Requests | Client exceeded a rate limit. |
500 Internal Server Error | Unexpected server failure. |
503 Service Unavailable | Temporary overload or dependency outage. |
Read REST API Status Codes Explained for a deeper guide.
Stable application error codes
Do not make clients parse English messages.
Bad:
{
"message": "This email already exists, please try another one."
}
Better:
{
"error": "email_already_registered",
"message": "An account already exists for this email address."
}
The message can change. The error code should remain stable unless the API version changes.
Field errors
For validation failures, include field-level details:
{
"error": "validation_failed",
"message": "One or more fields are invalid.",
"fields": {
"email": "Enter a valid email address.",
"password": "Password must be at least 12 characters."
}
}
This lets clients highlight the right form fields without guessing.
If the API supports nested objects, use a consistent field path convention:
{
"fields": {
"billingAddress.postalCode": "Postal code is required."
}
}
Do not leak internals
Error responses should not expose:
- Stack traces.
- SQL queries.
- Database hostnames.
- Secrets or tokens.
- Internal service URLs.
- Unexpected raw exception messages.
Log detailed errors internally, but return safe messages to clients.
Request IDs
Every error response should include a request ID when possible:
{
"error": "internal_error",
"message": "Something went wrong.",
"requestId": "req_abc123"
}
Support teams and developers can use that ID to find logs, traces, and metrics.
Use the UUID Generator for local examples of request IDs or correlation IDs.
Retryable errors
Some errors should tell clients whether retrying makes sense.
For rate limits:
HTTP/1.1 429 Too Many Requests
Retry-After: 30
For temporary outages:
{
"error": "service_unavailable",
"message": "The service is temporarily unavailable. Try again later."
}
Read Retry Patterns Explained before asking clients to retry write operations.
Error code taxonomy
Keep application error codes boring and consistent. A good taxonomy might group errors by authentication, authorization, validation, conflicts, rate limits, and dependency failures.
For example, validation_failed, email_already_registered, rate_limit_exceeded, and dependency_unavailable are more useful than one generic bad_request code everywhere. Client developers can branch on stable codes while still showing friendly messages to users.
Documentation and OpenAPI
Error handling should be documented like any other part of the API contract. Clients need to know which status codes can appear, which error codes are stable, and what fields are safe to show to end users.
In OpenAPI, define reusable error schemas instead of writing a different shape on every endpoint. For example, one ErrorResponse schema can include error, message, requestId, and optional fields. Endpoints can then reference that schema for 400, 401, 403, 409, 422, 429, and 5xx responses.
Document examples for common failures. A single validation example is not enough if the API also has rate limits, conflicts, dependency outages, and authorization failures. Good examples help SDK authors and frontend teams handle failure without reverse engineering production behavior.
Logging and support workflow
A useful error response connects to operations. The requestId should appear in application logs, gateway logs, and tracing metadata. If support receives a screenshot with a request ID, engineers should be able to locate the failure quickly.
Avoid logging full request bodies by default. Error logs often contain emails, tokens, addresses, or business data. Redact secrets and keep structured fields such as status code, error code, route, caller, tenant, and dependency name. The public error response and internal log entry should tell the same story at different detail levels.
Common mistakes
The first mistake is returning 200 OK for errors and putting failure information only in the JSON body. That weakens logs, clients, and monitoring.
The second mistake is changing error codes without versioning or migration notes.
The third mistake is exposing internal exception messages to users.
The fourth mistake is returning a different error shape from every endpoint.
Related reading
Read REST API Design Checklist for endpoint review, API Authentication Best Practices for 401 and 403 behavior, REST API Status Codes Explained for status code decisions, OpenAPI Explained for Backend Developers for shared error schemas, and JSON Schema vs OpenAPI Schema for schema boundaries.
For failure modes that need careful responses, read Idempotency in APIs Explained, API Abuse Prevention Explained, and Security Event Logging Explained. Use the HTTP Status Code Lookup as a quick reference while designing error responses. For the full sequence, browse the API Design Learning Path and the Backend Security Learning Path.