API Request, Response, and Error Shapes Clients Can Trust

Design consistent API request and response envelopes, validation errors, and problem details so client developers can handle success and failure reliably.

published: reading time: 8 min read author: GeekWorkBench
Quick Summary

Stable API contracts tell clients what to send and how to handle success or failure. This guide covers request validation, Problem Details, field errors, batch outcomes, gateway responses, and safe logging, with trade-offs between standard and custom envelopes. Readers can use these patterns to build clients that handle errors predictably without exposing sensitive details.

API Request, Response, and Error Shapes Clients Can Trust

Introduction

An API response is part of its contract: clients need to know which fields to send and how to distinguish a validation problem from a server failure. Consistent shapes make that behavior easier to document and handle, while keeping internal details out of public responses.

This guide uses HTTP Problem Details to model failures, then covers request validation, success responses, partial outcomes, gateway errors, and logging. It also weighs when a shared envelope helps and when it obscures useful HTTP semantics.

A practical error shape

The Problem Details format (application/problem+json) provides standard fields such as type, title, status, detail, and instance. APIs can add an errors extension for field-level validation while keeping a consistent envelope:

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

{
  "type": "https://api.example.com/problems/validation",
  "title": "Request validation failed",
  "status": 422,
  "detail": "Correct the marked fields and try again.",
  "instance": "/api/orders",
  "code": "validation_failed",
  "errors": [{"field":"quantity","code":"must_be_positive"}]
}

The body should not expose SQL, filesystem paths, stack traces, or secrets. Keep validation errors specific enough to fix, but avoid confirming sensitive account existence when that would create an enumeration risk.

Model the response path

sequenceDiagram
    participant Client
    participant API
    participant Validator
    participant Service
    Client->>API: Request with contract fields
    API->>Validator: Parse and validate
    alt Invalid input
        Validator-->>API: Field violations
        API-->>Client: 4xx problem response
    else Valid input
        Validator-->>API: Typed input
        API->>Service: Apply business operation
        Service-->>API: Result
        API-->>Client: 2xx representation
    end

This boundary helps keep HTTP concerns out of domain code. The validator produces a known input shape; the service returns a domain outcome; the API layer maps outcomes to status codes and response formats.

When to use and when not to use

Use a consistent response contract for APIs consumed by multiple clients, especially when teams deploy clients independently from servers. Use field-level errors for forms and structured clients that can point to specific inputs. A tiny internal endpoint may not need a complex envelope; keep it simple while preserving a stable status and content type. Avoid a success-shaped body for failure, because intermediaries and generic client libraries rely on HTTP status semantics.

Trade-Off Table

Design Strength Cost
Problem Details Standard fields and clear media type Extensions still need documentation
Custom stable error code Straightforward client branching Requires governance and code catalog
Generic {data, error} envelope Uniform JSON shape Can obscure status semantics and add wrappers
Raw framework error Cheap during development Leaks details and changes across versions

Implementation snippet

Map known errors centrally, then keep unexpected failures generic:

function toProblem(error: unknown, requestId: string): Response {
  if (error instanceof ValidationError) {
    return Response.json(
      {
        type: "about:blank",
        title: "Request validation failed",
        status: 422,
        code: "validation_failed",
        errors: error.fields,
      },
      {
        status: 422,
        headers: {
          "Content-Type": "application/problem+json",
          "X-Request-Id": requestId,
        },
      },
    );
  }
  return Response.json(
    {
      title: "Internal server error",
      status: 500,
      code: "internal_error",
      requestId,
    },
    { status: 500 },
  );
}

The production handler should log the internal exception with the request ID, while returning only safe details to the caller.

Production failure scenarios and mitigations

A frontend depends on an error message string and breaks after copy editing. Give clients stable codes and treat detail as display text. A gateway replaces a JSON error with an HTML timeout page; document gateway behavior and let clients handle invalid response bodies defensively. A batch endpoint returns 200 when individual items failed, but provides no per-item status; define partial success explicitly with per-item outcomes or make the whole request atomic. A deployment changes field names without a migration window; use additive changes and contract checks.

Observability checklist

  • Track status, stable error code, route template, request ID, and latency.
  • Measure validation failures by field code without recording submitted private values.
  • Separate expected client errors from unexpected server errors in alerts.
  • Monitor response content type and body size, including gateway-generated errors.

Security and Compliance Notes

Error bodies are public output. Redact credentials and personal data, sanitize reflected input, and avoid stack traces. Return the same auth error where revealing whether an account exists would be unsafe. Apply the same care to logs: restrict access, define retention, and avoid storing full request bodies when they may contain personal or regulated data. If a response includes user supplied text, encode it for its eventual display context so an error message cannot become an injection path.

Common Pitfalls / Anti-Patterns

  • Returning a different error schema from each route forces clients to maintain endpoint-specific parsers.
  • Changing or reusing error codes makes client handling unreliable; treat documented codes as part of the API contract.
  • Using 200 OK for a failed operation breaks generic HTTP handling and hides failures from caches and monitoring.
  • Returning an empty 500 without a request identifier leaves operators with no useful way to find the protected server-side exception.
  • Logging complete request bodies can expose credentials and personal data. Log safe fields and a correlation ID instead.

Quick Recap Checklist

  • Document request fields, success payloads, and error payloads together.
  • Return meaningful HTTP status codes and stable machine-readable error codes.
  • Keep public error details safe while recording useful diagnostics in protected logs.
  • Include a request identifier that connects a client response to server-side traces or logs.

Interview Questions

1. Why should clients branch on an error code instead of the detail string?
Human-readable text can be edited or localized. A documented code such as validation_failed provides a stable machine-readable condition.
2. What does a Problem Details response add to a plain error message?
It gives errors a standard media type and fields for the problem type, title, HTTP status, instance, and detail. An API can add documented extensions for cases such as field-level errors.
3. How can an API return useful diagnostics without leaking internals?
Return a safe error code and request identifier, log the full exception in a protected system, and use that identifier to correlate the two during debugging.
4. How should a batch API communicate partial success?
Document whether the operation is atomic. If items can succeed independently, return a clear outcome for each item so clients can identify which ones failed; do not imply that the entire batch succeeded with a bare 200 response.
5. What information belongs in a field-level validation error?
Include a stable field identifier and machine-readable reason, with a safe message that helps the caller correct the input. Do not include submitted secrets or unrestricted attacker-controlled values.
6. Why should an API client handle a non-JSON error body?
A proxy, gateway, or web server may return HTML or plain text for failures before the API handler runs. Clients should preserve the HTTP status and handle an unparseable body without crashing.
7. Why should the HTTP status and status field in a Problem Details body agree?
Clients and intermediaries use the HTTP status line, while some application code also reads the body. A mismatch creates conflicting signals and makes retries, monitoring, and support harder to reason about.
8. Why can returning different login errors reveal account information?
If the response distinguishes an unknown account from a wrong password, an attacker can test which accounts exist. Use consistent public error behavior where enumeration would create a risk.
9. When should a generic response envelope be used?
Use a wrapper when it provides shared metadata or another documented capability clients need. Avoid adding one solely for visual uniformity if it obscures HTTP status semantics or forces every client to unwrap unused data.

Further Reading

Conclusion

A stable request-response contract reduces defensive code in every client. Keep the envelope small, use HTTP status codes honestly, and separate public diagnostics from internal exceptions.

Category

Related Posts

API Resource Names, Relationships, and Collections

Model API URLs around stable resources and relationships, with clear collection behavior that clients can navigate without learning server internals.

#api-design #rest #resource-modeling

HTTP Methods, Status Codes, and Headers in API Design

Choose HTTP methods that match the operation, return status codes clients can act on, and use headers for metadata, caching, and safe retries.

#api-design #http #rest

API Examples, Schemas, and Useful Error Documentation

Write API examples, schemas, and error docs that help developers send valid requests, handle failures, and understand exactly what a response means.

#api-documentation #schemas #error-handling