← All posts
7 min read

Status Codes and Error Responses: Making Failure Predictable and Actionable

#technology#http#api-errors#status-codes
📑 On this page

An API error is not merely a message for a developer to read.

It is a contract that helps the caller decide what to do next.

HTTP status codes communicate a broad outcome; a structured error body supplies stable, safe, actionable details.

A good error lets a client correct input, ask for authentication, resolve a conflict, wait before retrying, or stop without guessing from human prose.

A concrete example: invalid input

Request:

POST /customers
 
{
  "email": "not-an-email",
  "birthDate": "2050-01-01"
}

Response:

422 Unprocessable Content
 
{
  "type": "https://example.com/problems/validation",
  "code": "VALIDATION_FAILED",
  "message": "Some fields need correction.",
  "errors": [
    { "field": "email", "code": "INVALID_FORMAT" },
    { "field": "birthDate", "code": "DATE_IN_FUTURE" }
  ],
  "requestId": "req-7f2"
}

The client can attach feedback to the correct fields without parsing sentences.

Status-code families

HTTP status codes are grouped:

  • 1xx: Informational
  • 2xx: Successful
  • 3xx: Redirection or cache-related
  • 4xx: The request cannot succeed as sent or authorized
  • 5xx: The server failed to fulfill a valid request

The family helps infrastructure and clients classify outcomes.

Do not return 200 OK for every response and place success inside a custom Boolean. That weakens browser, cache, gateway, monitoring, and retry semantics.

Success codes

Common success codes:

  • 200 OK: Successful response with a body
  • 201 Created: A resource was created
  • 202 Accepted: Work was accepted but not completed
  • 204 No Content: Success with no response body

For 201, include the created representation or a Location header when useful.

202 should provide a job or status resource. Acceptance is not completion.

Use 204 only when the client genuinely needs no updated data.

400 Bad Request

400 describes malformed or invalid request syntax and is also broadly used for invalid input.

Examples:

  • Invalid JSON
  • Missing required top-level input
  • Unsupported parameter form

Some APIs distinguish semantically invalid but parseable input with 422.

Consistency matters more than arguing over one ambiguous boundary.

Document the chosen rule and keep field-level error details stable.

401 and 403

401 Unauthorized means authentication is missing, invalid, or insufficiently established. Despite the name, it is mainly an authentication response.

403 Forbidden means the caller is authenticated but not permitted.

Security may justify returning 404 instead of 403 when revealing that a resource exists would leak information.

Clients should not repeatedly retry a 401 with the same invalid credential. They may refresh, reauthenticate, or stop according to protocol.

404 Not Found

404 can mean:

  • No resource at this URL
  • Resource not visible to this caller
  • Endpoint does not exist

Avoid exposing internal details such as:

Order exists but belongs to tenant 17.

For APIs with eventual creation or asynchronous replication, document whether a temporary not-found result is possible.

Do not make clients infer deletion versus permission solely from secret-sensitive details.

409 Conflict

409 Conflict describes a request that conflicts with current resource state.

Examples:

  • Email already registered
  • Order already shipped and cannot be cancelled
  • Version mismatch
  • Idempotency key reused with different input

The response should explain the conflict and possible resolution.

For optimistic concurrency, include current version or instructions to retrieve it.

Conflicts are expected domain outcomes, not necessarily server defects.

429 Too Many Requests

429 indicates a rate or quota limit.

Useful response information includes:

  • Limit reason
  • Retry time
  • Remaining quota when safe
  • Relevant scope
Retry-After: 30

Clients should back off rather than immediately retry in a loop.

Do not expose sensitive anti-abuse thresholds when that would help attackers. Provide enough guidance for legitimate clients.

500 and 503

500 Internal Server Error indicates an unexpected server failure.

503 Service Unavailable communicates temporary inability, such as overload or maintenance, and may include Retry-After.

Do not expose:

  • Stack traces
  • SQL
  • File paths
  • Secrets
  • Internal hostnames

Return a request identifier and log detailed diagnostics internally with protected access.

The user-safe response and operator diagnosis serve different audiences.

Retryability must be explicit

Status code alone does not always determine whether retry is safe.

Consider:

  • 503 on a GET: often retryable with backoff.
  • Timeout on order creation: outcome unknown.
  • 409 due to temporary version conflict: retry after refresh.
  • 400 invalid field: correct before retry.

Document retry policy and operation idempotency.

Clients should use bounded exponential backoff with jitter for transient failures.

Retries without limits can amplify an outage.

Stable machine-readable codes

Human messages may change for clarity or localization.

Machine behavior should use stable codes:

{
  "code": "ORDER_ALREADY_SHIPPED",
  "message": "This order can no longer be cancelled."
}

Avoid clients branching on:

if (message.includes("shipped"))

Define code namespaces, meanings, and compatibility.

Do not reuse an existing code for a new unrelated condition.

Problem details

Standardized problem formats can include:

  • type
  • title
  • status
  • detail
  • instance
  • Extension fields

The type can link to documentation.

Standards reduce custom parsing across APIs.

Application-specific codes and field errors can extend the base format.

Use one consistent envelope rather than invent a different error shape for every endpoint.

Validation errors

Validation responses should identify:

  • Field or path
  • Stable code
  • Constraint
  • Safe context

Do not echo raw secrets or entire submitted documents.

For nested arrays, use a clear path:

items[2].quantity

Client validation improves immediacy, but server validation remains authoritative.

Avoid revealing whether an email is registered when that enables account enumeration unless the workflow and risk permit it.

Request identifiers

A request ID connects:

  • Client report
  • Gateway log
  • Service trace
  • Database operation
  • Dependency call

Return it in a header or body:

X-Request-ID: req-7f2

Do not use sequential IDs that expose traffic volume or internal records.

Preserve caller-provided IDs only if validated; otherwise generate a trusted internal correlation ID.

Localization

APIs used directly by user interfaces may provide localized display messages.

Even then, keep:

  • Stable code
  • Field path
  • Structured parameters

The client may localize messages itself for consistency and offline use.

Do not put business-critical values only inside prose.

Separating code from presentation allows wording to improve without breaking clients.

Partial failures

Batch operations can have mixed outcomes.

Options include:

  • Fail the entire batch atomically.
  • Return one result per item.
  • Accept a job and expose progress.

Example:

{
  "results": [
    { "id": "1", "status": "updated" },
    { "id": "2", "status": "failed", "code": "NOT_FOUND" }
  ]
}

Define whether successful items were committed and how retry avoids repeating them.

A single top-level 500 can hide useful item-level outcomes.

Monitoring errors

Measure:

  • Error rate by operation and code
  • User-visible failure
  • Retry volume
  • Validation patterns
  • Dependency contribution
  • Latency before failure

Not every 4xx is harmless. A sudden authentication-failure surge may indicate attack or configuration failure.

Not every 5xx has equal impact. Segment by user journey and service objective.

Error contracts support operations as well as client code.

Knowledge check

  1. Why should callers not parse human error messages?
  2. How do 401 and 403 differ?
  3. When is 409 Conflict useful?
  4. Why can a timeout on resource creation require idempotency rather than blind retry?
  5. What should a server return instead of an external stack trace?

The one idea to remember

An API error should tell the caller what class of outcome occurred and what action is safe next. Use HTTP status semantics plus stable structured codes, protected detail, retry guidance, and correlation identifiers.