Menu

Topic 5 of 7

Error Handling Standards

Learn Error Handling Standards for free with explanations, exercises, and a quick test (for API Engineer).

Published: January 21, 2026 | Updated: January 21, 2026

What you’ll learn

  • How to choose the right HTTP status codes for client vs server errors
  • How to design a consistent error response body (Problem Details pattern)
  • How to handle validation errors, conflicts, rate limits, timeouts, and upstream failures
  • How to expose safe, traceable errors while hiding sensitive data
  • How to make errors actionable with IDs, codes, and retry guidance

Why this matters

Real API Engineer tasks depend on reliable error handling:

  • Debugging production incidents quickly using correlation IDs and clear error messages
  • Helping client teams recover from failures using retry hints and stable error codes
  • Reducing support tickets with predictable, documented error formats
  • Protecting security and privacy by avoiding sensitive data leaks in errors

Concept explained simply

Errors are part of the API contract. If success responses are roads, error responses are guardrails. They prevent confusion and help clients safely recover.

Mental model

Think of error handling as a map:

  • Status code: which road you’re on (client mistake vs server issue)
  • Error code: exact location of the issue (e.g., USER_EMAIL_INVALID)
  • Message: readable sign explaining what happened
  • Details: extra context (which field, what limit)
  • Trace/correlation ID: coordinates to find logs
  • Retry guidance: when to try again (or not)

Standards to follow

  • Use the right class: 4xx = client mistakes; 5xx = server faults
  • Return a consistent body. A practical Problem Details shape:
    {
      "type": "https://errors.your-api/validation",
      "title": "Validation error",
      "status": 422,
      "detail": "One or more fields are invalid.",
      "errorCode": "VALIDATION_FAILED",
      "traceId": "d7d9b93f9f6c",
      "errors": { "fieldName": ["Message 1", "Message 2"] }
    }
    
  • Always include: status, title, detail (safe), errorCode (stable), traceId
  • Never expose secrets, stack traces, SQL, paths, or internal service names
  • Prefer stable errorCode values over parsing human messages
  • Document retry rules: provide Retry-After for 429/503 when applicable
  • For idempotent endpoints, ensure safe retries (e.g., idempotency keys)
Quick status code mapping
  • 400 Bad Request: malformed or incompatible input
  • 401 Unauthorized: missing/invalid credentials (use WWW-Authenticate when relevant)
  • 403 Forbidden: authenticated but not allowed
  • 404 Not Found: resource doesn’t exist or not visible
  • 409 Conflict: state conflict (version, duplicate unique field)
  • 410 Gone: resource intentionally removed
  • 422 Unprocessable Content: syntactically valid, semantically invalid (field-level issues)
  • 429 Too Many Requests: rate limit exceeded (use Retry-After)
  • 500 Internal Server Error: unexpected server failure
  • 502 Bad Gateway: upstream service failed
  • 503 Service Unavailable: temporary outage or maintenance (use Retry-After)
  • 504 Gateway Timeout: upstream timed out
400 vs 422

Use 400 when the request is malformed or fails basic parsing. Use 422 when the request is well-formed JSON but fails business/field validation (e.g., email format, min length).

Worked examples

A) Validation error (POST /users)

HTTP/1.1 422 Unprocessable Content
Content-Type: application/json

{
  "type": "https://errors.your-api/validation",
  "title": "Validation error",
  "status": 422,
  "detail": "One or more fields are invalid.",
  "errorCode": "VALIDATION_FAILED",
  "traceId": "c5a7d1b8",
  "errors": {
    "email": ["Must be a valid email address."],
    "password": ["Must be at least 12 characters.", "Must include a number."]
  }
}

Why: Request is syntactically valid JSON, but fields fail validation.

B) Conflict (duplicate resource)

HTTP/1.1 409 Conflict
Content-Type: application/json

{
  "type": "https://errors.your-api/conflict",
  "title": "Conflict",
  "status": 409,
  "detail": "A user with this email already exists.",
  "errorCode": "USER_EMAIL_TAKEN",
  "traceId": "9f12a3cd"
}

Why: State conflict due to unique constraint.

C) Rate limit exceeded

HTTP/1.1 429 Too Many Requests
Retry-After: 60
Content-Type: application/json

{
  "type": "https://errors.your-api/rate-limit",
  "title": "Too many requests",
  "status": 429,
  "detail": "Rate limit exceeded. Try again in 60 seconds.",
  "errorCode": "RATE_LIMIT_EXCEEDED",
  "traceId": "44bb29f1"
}

Why: Client can retry; header gives timing.

D) Upstream timeout via gateway

HTTP/1.1 504 Gateway Timeout
Content-Type: application/json

{
  "type": "https://errors.your-api/gateway-timeout",
  "title": "Gateway timeout",
  "status": 504,
  "detail": "Dependency did not respond in time.",
  "errorCode": "UPSTREAM_TIMEOUT",
  "traceId": "e7820a11"
}

Why: Indicates dependency timeout without exposing internal hostnames.

Design patterns that help

  • Correlation ID: include a traceId; pass it through services
  • Error catalog: maintain stable errorCode values and short descriptions
  • Localization: keep message user-friendly but avoid critical reliance on its text; clients should use errorCode for logic
  • Partial failures: return multi-error details when appropriate (e.g., bulk operations)

Exercises

These mirror the exercises below. Try first, then open the solutions.

  1. ex1 — Design a validation error. Endpoint: POST /users. Body has invalid email and weak password. Return the best status code, headers, and error body with field-level details.
    Hints
    • Request is parseable JSON
    • Use errors per field
    • Include a stable errorCode
  2. ex2 — Design a rate limit error. A client exceeded per-minute request limits. Return response with retry guidance and a consistent error body.
    Hints
    • Which status code signals throttling?
    • How do you tell the client when to retry?
    • Keep sensitive data out
  • [Checklist] Status code matches the scenario
  • [Checklist] errorCode is stable and machine-friendly
  • [Checklist] Human detail explains what to fix
  • [Checklist] Includes traceId
  • [Checklist] Retry-After present when applicable

Common mistakes

  • Using 500 for client errors. Fix: map validation/authz issues to 4xx
  • Inconsistent error shapes across endpoints. Fix: a single error schema
  • Leaking stack traces or SQL. Fix: log internally; return safe detail
  • Missing retry hints on 429/503. Fix: add Retry-After when possible
  • Unstable messages used for client logic. Fix: rely on errorCode, not text
  • Ambiguous 400 vs 422. Fix: parse errors = 400, field/business validation = 422
Self-check
  • Can you point each error in your API spec to a status code and errorCode?
  • Do you include traceId on every error path?
  • Can a client programmatically act using only status + errorCode?
  • Do you have tests that assert error shape remains stable?

Practical projects

  1. Retrofit one existing endpoint to return a consistent error schema; add tests for at least 5 error scenarios
  2. Implement a middleware that sets traceId and injects it into all error responses
  3. Add rate limiting and return 429 with Retry-After; include integration tests
  4. Create an error catalog document mapping errorCode → description → httpStatus

Learning path

  1. Define your API-wide error schema (fields, naming, examples)
  2. Map common scenarios to status codes (auth, validation, conflict, limits, upstream)
  3. Implement middleware for uniform error responses and trace IDs
  4. Write contract tests for error shapes and headers
  5. Document error codes in your API reference

Who this is for

  • API Engineers and Backend Developers who publish HTTP/JSON APIs
  • Team leads establishing cross-service standards
  • SREs improving operability and incident response

Prerequisites

  • Basic HTTP knowledge (methods, headers, status codes)
  • JSON serialization and request/response handling in your runtime
  • Logging and correlation ID basics

Mini challenge

Your PATCH /orders/{id} can return: missing auth token, invalid field value for quantity, and stale version (ETag mismatch). Propose the three responses (status + minimal body fields) using your chosen error schema.

One possible approach
// Missing auth token
401 Unauthorized
{
  "type": "https://errors.your-api/auth",
  "title": "Unauthorized",
  "status": 401,
  "detail": "Missing or invalid token.",
  "errorCode": "AUTH_REQUIRED",
  "traceId": "..."
}

// Invalid quantity
422 Unprocessable Content
{
  "type": "https://errors.your-api/validation",
  "title": "Validation error",
  "status": 422,
  "detail": "One or more fields are invalid.",
  "errorCode": "VALIDATION_FAILED",
  "traceId": "...",
  "errors": { "quantity": ["Must be a positive integer."] }
}

// Stale version (ETag mismatch)
409 Conflict
{
  "type": "https://errors.your-api/conflict",
  "title": "Conflict",
  "status": 409,
  "detail": "Resource version conflict.",
  "errorCode": "VERSION_CONFLICT",
  "traceId": "..."
}

Next steps

  • Adopt a single error schema across your services
  • Create a shared library or middleware to enforce it
  • Add tests that pin error shapes and headers

Quick Test

Available to everyone. Note: only logged-in users will have their progress saved.

Practice Exercises

2 exercises to complete

Instructions

The client sends JSON with an invalid email and a weak password. Provide the full HTTP response: status, essential headers, and a JSON body using a consistent error schema. Include field-level errors.

Expected Output
HTTP 422 with a JSON body containing type, title, status, detail, errorCode, traceId, and an errors object listing email and password issues.

Error Handling Standards — Quick Test

Test your knowledge with 10 questions. Pass with 70% or higher.

10 questions70% to pass

Have questions about Error Handling Standards?

AI Assistant

Ask questions about this tool