Skip to content

17 Error Responses

DodaTech 3 min read

title: Error Responses in REST API Design — Complete Guide weight: 27 date: 2026-06-28 lastmod: 2026-06-28 description: Design consistent REST API error responses with structured JSON envelopes, meaningful error codes, validation details, and proper HTTP status code mapping. tags: [api-development, rest]


Consistent error responses in REST APIs use a structured JSON envelope with error code, message, details, and trace ID, helping clients handle failures programmatically without parsing error text.

```mermaid
flowchart TD
  A[Request] --> B{Validation}
  B -->|Invalid| C[400 Bad Request]
  B -->|Valid| D{Process}
  D -->|Success| E[200 OK]
  D -->|Server Error| F[500 Error]
  C --> G[Error Envelope]
  F --> G
  G --> H[code, message, details, trace_id]
  style A fill:#e1f5fe
  style G fill:#ffcdd2
  style H fill:#fff9c4

A good error response includes a unique error code for programmatic handling, a human-readable message, validation details showing which field failed and why, and a trace ID for debugging. The HTTP status code reflects the error category.

Think of error responses like an airplane's check engine light. The light (status code) tells you something is wrong. The diagnostic code (error code) tells you exactly which system failed. The mechanic's notes (message) explain what happened in human terms.

Example: Standard Error Envelope

import requests

response = requests.post(
    "https://api.example.com/users",
    json={"email": "not-an-email"}
)
error = response.json()
print(f"Status: {response.status_code}")
print(f"Code: {error['error']['code']}")
print(f"Message: {error['error']['message']}")
print(f"Trace ID: {error['error']['trace_id']}")

Expected output:

Status: 400
Code: VALIDATION_ERROR
Message: The request contains invalid fields
Trace ID: 550e8400-e29b-41d4-a716-446655440000

Example: Validation Error with Field Details

import requests

response = requests.post(
    "https://api.example.com/users",
    json={
        "name": "",
        "email": "invalid",
        "age": -1
    }
)
error = response.json()
print(f"Status: {response.status_code}")
for detail in error['error']['details']:
    print(f"  {detail['field']}: {detail['issue']}")

Expected output:

Status: 422
  name: must not be empty
  email: must be a valid email address
  age: must be a positive integer

Example: Consistent Error Handling

import requests

def safe_api_call(url, method="GET", **kwargs):
    try:
        response = requests.request(method, url, **kwargs)
        response.raise_for_status()
        return response.json()
    except requests.exceptions.HTTPError as e:
        error = e.response.json().get("error", {})
        return {
            "error": True,
            "status": e.response.status_code,
            "code": error.get("code", "UNKNOWN"),
            "message": error.get("message", str(e)),
            "details": error.get("details", [])
        }

result = safe_api_call(
    "https://api.example.com/users/99999"
)
if result.get("error"):
    print(f"[{result['code']}] {result['message']}")
else:
    print(result)

Expected output:

[NOT_FOUND] User with id 99999 was not found

Common Mistakes

  1. Returning unstructured error messages — Returning just "Invalid input" without details forces clients to implement fragile string parsing.
  2. Exposing stack traces — Returning a 500 error with a Python traceback or SQL query exposes internal implementation details and security vulnerabilities.
  3. Inconsistent error structure across endpoints — If /users returns {"error": "..."} but /orders returns {"message": "..."}, clients cannot use a generic error handler.
  4. Using 200 OK with an error flag in the body — This breaks HTTP semantics. Use the appropriate 4xx or 5xx status code.
  5. Not including a correlation ID — Without a trace ID, clients and support teams cannot correlate errors with server logs for debugging.

Practice Questions

  1. What fields should a consistent error envelope include?
  2. Why should you return error details for validation failures?
  3. What status code should you use for a malformed request body?
  4. Why is it dangerous to expose stack traces in error responses?
  5. Challenge: Design an error response system for a payment API that handles insufficient funds, declined cards, expired cards, invalid CVV, and duplicate transactions. Each error should have a unique code and actionable message.

FAQ

What is the difference between 400 and 422?

400 Bad Request means the request is malformed (bad JSON syntax). 422 Unprocessable Entity means the syntax is valid but the content is invalid (missing field, wrong type).

Should I include a message in every error response?

Yes, but keep messages concise and user-friendly. Detailed debugging information should go in the details array or server logs.

How do I handle validation errors with multiple field failures?

Return all field errors in a details array. Each element should include the field name, the issue, and optionally the rejected value.

What is the retry-after header for error responses?

Use Retry-After with 429 Too Many Requests and 503 Service Unavailable to tell clients when they can retry.

Should error messages be localized?

Ideally yes, but start with a single language (usually English). Use error codes for programmatic handling and localized messages for display.

Mini Project

Build a Python error-handling utility class that generates consistent error responses. Support validation errors with field details, authentication errors, not-found errors, and server errors. Each response should include a trace ID, error code, message, and optional details.

What's Next

Now learn about authentication design in REST API Design.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro