Skip to main content

Designing Robust Error Response Contracts

Implement a CI/CD gating pipeline that automatically validates API error payloads against a centralized JSON Schema. This workflow blocks non-compliant merges before they reach production branches.

Step 1: Define the Canonical Error Schema

Establish a strict, versioned JSON Schema to dictate the structure of all failure payloads. Align with RFC 7807 to ensure cross-service interoperability. This foundational work operates within broader Schema Design & Validation Patterns. Explicitly define required fields: type, title, status, detail, and instance.

Avoid free-form message strings. Enforce machine-readable error codes and localized display text. Set additionalProperties: false to prevent uncontrolled payload drift.

// schemas/error-response-v1.schema.json
{
 "$schema": "https://json-schema.org/draft/2020-12/schema",
 "title": "Error Response Contract",
 "type": "object",
 "required": ["type", "title", "status", "detail"],
 "properties": {
 "type": { "type": "string", "format": "uri" },
 "title": { "type": "string" },
 "status": { "type": "integer", "minimum": 400, "maximum": 599 },
 "detail": { "type": "string" },
 "instance": { "type": "string", "format": "uri-reference" },
 "error_code": { "type": "string", "pattern": "^[A-Z_]+$" }
 },
 "additionalProperties": false
}

Validation Rules

  • Reject any payload missing status or type.
  • Enforce status range strictly between 400-599.
  • Disallow arbitrary nested objects unless explicitly whitelisted.

Step 2: Configure CI Pipeline Schema Gating

Integrate automated validation into pull request workflows using ajv-cli or spectral. Build-time gating prevents regressions from reaching staging environments. Pair CI validation with Runtime Validation with Zod to mirror contract enforcement at the application layer.

The pipeline must parse all test fixtures against the canonical schema. Fail the build immediately upon schema violation. Maintain strict type checking to avoid implicit coercion.

# .github/workflows/error-contract-gate.yml
name: Error Contract Validation
on: [pull_request]
jobs:
 validate-schemas:
 runs-on: ubuntu-latest
 steps:
 - uses: actions/checkout@v4
 - name: Install AJV CLI
 run: npm install -g ajv-cli
 - name: Validate Error Fixtures
 run: ajv validate -s schemas/error-response-v1.schema.json -d 'tests/fixtures/errors/*.json' --strict=true

Validation Rules

  • Fail CI if any fixture returns false on schema validation.
  • Require 100% coverage of defined HTTP status codes in test fixtures.
  • Enforce additionalProperties: false to prevent schema drift.

Common Pitfalls

  • Using loose JSON parsers that auto-coerce types (e.g., string "404" to integer 404).
  • Allowing null values in required fields without explicit type: ["string", "null"] definitions.

Step 3: Map OpenAPI Definitions to Error Contracts

Declare error responses explicitly for every endpoint in your OpenAPI specification. Reference the canonical schema via #/components/responses to eliminate duplication. This practice directly supports Standardizing HTTP error codes in OpenAPI definitions.

Generated client SDKs will accurately reflect failure states. Run swagger-cli in CI to verify all 4xx and 5xx paths resolve correctly. Enforce application/problem+json as the mandatory content type.

# openapi.yaml
paths:
 /users/{id}:
 get:
 responses:
 '404':
 $ref: '#/components/responses/NotFound'
 '500':
 $ref: '#/components/responses/ServerError'
components:
 responses:
 NotFound:
 description: Resource not found
 content:
 application/problem+json:
 schema:
 $ref: 'schemas/error-response-v1.schema.json'
 ServerError:
 description: Internal failure
 content:
 application/problem+json:
 schema:
 $ref: 'schemas/error-response-v1.schema.json'

Validation Rules

  • Every endpoint must define at least one 4xx response.
  • All error responses must use application/problem+json content type.
  • Spectral rule: oas3-error-response-schema must pass.

Step 4: Implement Contract Testing with Mock Servers

Deploy a contract-aware mock server using Prism or WireMock. Serve only schema-compliant error payloads for frontend and QA validation. For constrained legacy codebases, adapt the validation layer using Joi and Yup for Legacy Systems to bridge older formats.

Execute nightly contract tests to detect silent violations. Verify status code alignment and content-type headers. Ensure mock routing prioritizes error examples over success defaults.

# docker-compose.mock.yml
version: '3.8'
services:
 prism-mock:
 image: stoplight/prism:4
 ports:
 - '4010:4010'
 volumes:
 - ./openapi.yaml:/api.yaml
 command: mock -h 0.0.0.0 /api.yaml

Validation Rules

  • Mock must reject requests that trigger undefined error paths.
  • Contract tests must verify Content-Type and status code alignment.
  • Pact verification must run against both mock and staging environments.

Common Pitfalls

  • Mock servers returning generic 500s without adhering to the error schema.
  • Frontend teams hardcoding error parsing logic that breaks when optional fields are omitted.

Troubleshooting

Issue Root Cause Resolution
CI fails with additional properties not allowed Test fixtures contain legacy fields (error_msg, trace_id) not defined in the schema. Add explicit property definitions to the JSON Schema or migrate fixtures to use detail and instance. Run ajv compile locally to debug exact violation paths.
OpenAPI generator produces invalid client SDKs Missing content type mapping or incorrect $ref resolution in the spec. Ensure all error responses use application/problem+json and reference the schema via #/components/schemas/ErrorContract. Validate with swagger-cli validate.
Mock server returns 200 instead of 4xx/5xx Prism/WireMock routing rules prioritize success examples over error examples. Configure mock routing to weight error examples higher, or use X-Error-Code request headers to force specific error paths in the mock.