Skip to content

Instantly share code, notes, and snippets.

@bsitruk
Last active June 19, 2025 09:38
Show Gist options
  • Save bsitruk/765b4a9ee390a8a32d7b15d6474e872b to your computer and use it in GitHub Desktop.
Save bsitruk/765b4a9ee390a8a32d7b15d6474e872b to your computer and use it in GitHub Desktop.

REST API - OpenAPI Error Response

#api#best-practice

 When writing the OpenAPI specs of an API, which error case should I document in the "response" section ? 

In OpenAPI you only need to describe the responses that the client can realistically expect and act on for a given operation. The spec itself clarifies that the document

“is not necessarily expected to cover all HTTP response codes … however, it is expected to cover a successful operation response and any known errors.” (swagger.io)

What counts as a “known” error?

  • Deterministic validation or business-rule failures
    400 Bad Request or 422 Unprocessable Entity when the input is syntactically or semantically wrong.
  • Authentication / authorisation issues
    401 Unauthorized, 403 Forbidden.
  • Resource state errors
    404 Not Found, 409 Conflict (e.g., optimistic-lock conflict, duplicate data).
  • Rate limiting / quota
    429 Too Many Requests (often includes a Retry-After header).
  • Operation-specific conditions (e.g., 410 Gone, 412 Precondition Failed) that your service purposefully returns.

These are the cases where the caller can retry differently, show a message, or log something specific; therefore you should list them explicitly in the responses section with both the status code and the error schema.

How to handle the unexpected 5xx errors?

Unhandled exceptions, time-outs, or infrastructure failures are not predictable from the API contract. Best practice is to add one generic entry instead of enumerating every 5xx code:

responses:
  '200':
    description: Successful response
    content:
      application/json:
        schema: { $ref: '#/components/schemas/MyData' }

  '4XX':          # or individual 4xx codes as above
    $ref: '#/components/responses/ClientError'

  '5XX':          # ← catches 500, 502, 503, 504 …
    description: Unexpected server error
    content:
      application/problem+json:
        schema: { $ref: '#/components/schemas/ProblemDetails' }

Using the wildcard (4XX, 5XX) or the special default response keeps the spec concise and still tells integrators that “something went wrong on the server” without promising a specific error payload. This mirrors the guidance echoed in community discussions: document the success path and the errors you can foresee, but you don’t have to list every hypothetical 5xx response individually. (stackoverflow.com)

Choosing an error schema

Adopt a single structured format—most teams now use “Problem Details for HTTP APIs” (RFC 9457, successor to RFC 7807). It gives you a standard object with fields like type, title, status, detail, instance, and is easily extended with code, errors, etc. (swagger.io) Define it once under components/schemas and $ref it from every error response.


TL;DR
Document:

  1. Success (2xx) responses.
  2. All client-actionable errors (specific 4xx and any business-domain errors).
  3. One generic server error using default, 5XX, or a single 500, tied to your standard error object.

Everything else can safely be left unspecified—clients already know they must handle unexpected HTTP codes defensively.

Can you give me an example of an error schema ?

Below is a compact, copy-paste-ready scaffold that teams commonly put in components/schemas and components/responses. It follows RFC 9457 “Problem Details for HTTP APIs”, then shows how you might extend it with an application-specific code and an array of per-field errors (useful for validation failures).

components:
  schemas:
    ProblemDetails:        # the base error object
      type: object
      required: [type, title, status]
      description: Standard problem-details envelope (RFC 9457)
      properties:
        type:
          type: string
          format: uri
          example: https://api.example.com/problems/out-of-credit
          description: URI that identifies the problem “class”.
        title:
          type: string
          example: Out of credit
          description: Short, human-readable summary (usually from an error catalogue).
        status:
          type: integer
          format: int32
          example: 403
          description: HTTP status code for this occurrence.
        detail:
          type: string
          example: Your account balance is 0. Top-up required before purchase.
          description: Human-readable explanation for *this* occurrence.
        instance:
          type: string
          format: uri
          example: urn:uuid:4b602e26-d2e2-4fc2-8b31-cc04a086fae2
          description: URI that identifies this specific error record (for support logs).
        # --- OPTIONAL EXTENSIONS ---
        code:
          type: string
          example: ACC_001
          description: Internal/business error code that never changes language.
        errors:
          type: array
          description: List of granular validation problems (only used on 4 xx).
          items:
            $ref: '#/components/schemas/FieldError'

    FieldError:
      type: object
      required: [field, message]
      description: Fine-grained validation error for a single input element.
      properties:
        field:
          type: string
          example: email
        message:
          type: string
          example: must be a valid email address

  responses:
    ClientError:           # can be reused for 4xx codes you expose
      description: Generic client error
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/ProblemDetails'

    ServerError:           # catch-all 5xx (or use `default`)
      description: Unexpected server failure
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/ProblemDetails'

Sample response payload

{
  "type": "https://api.example.com/problems/invalid-parameter",
  "title": "Invalid parameter",
  "status": 400,
  "detail": "Query parameter 'sort' must be one of [asc, desc].",
  "instance": "urn:uuid:4b602e26-d2e2-4fc2-8b31-cc04a086fae2",
  "code": "VAL_002",
  "errors": [
    {
      "field": "sort",
      "message": "must be 'asc' or 'desc'"
    }
  ]
}

Why this shape?

  • type, title, status, detail, instance are the five base members defined by RFC 9457; they let generic tooling parse the error while still giving humans context. (rfc-editor.org)
  • code (string) and errors (array) are extension members—allowed by the RFC as long as clients ignore fields they don’t understand. They help map the error to internal logs and convey multiple validation issues in one go. (swagger.io)

Use this schema as a single source of truth: every operation that can fail simply $refs the appropriate response (ClientError, ServerError, or something more specific like ValidationError). That keeps your OpenAPI tidy while giving consumers a predictable, machine-readable error format.


https://www.rfc-editor.org/rfc/rfc9457.html

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment