Response Schema

Standards Security Notes Jan 9, 2026 JSON
schema validation response api-design api-contract

Definition

A response schema is a formal specification that defines what data an API endpoint returns to clients. It describes the structure of response bodies for different HTTP status codes (200, 201, 400, 404, 500), including data types, required fields, nested structures, and possible variations. Unlike request schemas that validate incoming data, response schemas serve as a contract guaranteeing clients receive data in a predictable format.

Response schemas are crucial for API consumers who generate type-safe clients. When a TypeScript or Java SDK is auto-generated from an OpenAPI spec, the response schemas become compile-time guarantees - developers get autocomplete, type checking, and documentation directly in their IDE. Changes to response structure are immediately visible as breaking changes.

While request validation prevents bad data from entering your system, response validation catches backend bugs. If your code accidentally returns a string where the schema promises a number, validation catches it before the response reaches clients. This is especially valuable in microservice architectures where services evolve independently.

Example

User Profile API: A GET /users/{id} endpoint defines response schemas for multiple status codes: 200 returns a User object (id, email, name, createdAt), 404 returns an Error object (code, message), 500 returns a ServerError (requestId, message). Client SDKs use these schemas to deserialize JSON into strongly-typed objects.

Search Results: A product search API defines a response schema with pagination metadata: results (array of Product objects), totalCount (integer), page (integer), pageSize (integer), nextPageToken (string or null). The schema guarantees clients can always safely access these fields without null checks on the wrapper object.

Polymorphic Responses: A notification API returns different schemas based on notification type using a discriminator. All notifications share common fields (id, timestamp, userId), but email notifications include emailAddress and subject, while push notifications include deviceId and payload. The schema uses oneOf to model this.

Partial Updates: A PATCH /users/{id} endpoint returns the complete User object in the 200 response, even though only specific fields were updated. The response schema ensures clients always receive the full current state, not just changed fields.

Error Responses: An API defines a consistent Error schema for all 4xx and 5xx responses with required fields (code, message, timestamp) and optional fields (details array, documentation URL). This ensures all error responses across every endpoint follow the same format.

Analogy

The Product Label: Just as food products have mandatory nutrition labels with a standard format (calories, fat, carbs, protein), response schemas define the standard format for API data. Consumers rely on this consistency - they don’t need to check if calories are in grams or ounces because the label format guarantees it.

The Receipt Template: When you buy something, the receipt always has the same structure: items purchased, prices, subtotal, tax, total, payment method. You can write expense-tracking software knowing receipts follow this format. Response schemas are like receipt templates - they guarantee structure even as the specific data changes.

The Weather Report Format: Weather services always report temperature, humidity, wind speed, forecast in predictable formats. Developers building weather apps rely on this structure. If a service suddenly returned temperature in Kelvin instead of Celsius without updating the schema, apps would break. Response schemas prevent these surprises.

Code Example


# [OpenAPI 3](https://reference.apios.info/terms/openapi-3/).x Response [Schema](https://reference.apios.info/terms/schema/) Definition
paths:
  /api/users/{userId}:
    get:
      summary: Get user by ID
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: User found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
              example:
                id: "123e4567-e89b-12d3-a456-426614174000"
                email: "[email protected]"
                username: "alice_smith"
                role: "user"
                createdAt: "2024-01-15T10:30:00Z"
                profile:
                  firstName: "Alice"
                  lastName: "Smith"
                  bio: "Software engineer"
        '404':
          description: User not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                code: "USER_NOT_FOUND"
                message: "No user exists with the provided ID"
                timestamp: "2024-01-20T14:23:00Z"
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ServerError'

components:
  schemas:
    User:
      type: object
      required:
        - id
        - email
        - username
        - role
        - createdAt
      properties:
        id:
          type: string
          format: uuid
        email:
          type: string
          format: email
        username:
          type: string
        role:
          type: string
          enum: [admin, moderator, user]
        createdAt:
          type: string
          format: date-time
        profile:
          type: object
          properties:
            firstName:
              type: string
            lastName:
              type: string
            bio:
              type: string
              maxLength: 500

    Error:
      type: object
      required:
        - code
        - message
        - timestamp
      properties:
        code:
          type: string
          description: Machine-readable error code
        message:
          type: string
          description: Human-readable error message
        timestamp:
          type: string
          format: date-time
        details:
          type: array
          items:
            type: string

    ServerError:
      allOf:
        - $ref: '#/components/schemas/Error'
        - type: object
          required:
            - requestId
          properties:
            requestId:
              type: string
              format: uuid
              description: Unique ID for debugging this request

Validating responses in tests (TypeScript):


import Ajv from "ajv";
import addFormats from "ajv-formats";

const ajv = new Ajv();
addFormats(ajv);

const userSchema = {
  type: "object",
  required: ["id", "email", "username", "role", "createdAt"],
  properties: {
    id: { type: "string", format: "uuid" },
    email: { type: "string", format: "email" },
    username: { type: "string" },
    role: { type: "string", enum: ["admin", "moderator", "user"] },
    createdAt: { type: "string", format: "date-time" },
    profile: {
      type: "object",
      properties: {
        firstName: { type: "string" },
        lastName: { type: "string" },
        bio: { type: "string", maxLength: 500 }
      }
    }
  }
};

const validate = ajv.compile(userSchema);

// Contract testing - validate actual API response
async function testGetUser() {
  const response = await fetch('https://api.example.com/users/123');
  const data = await response.json();

  const valid = validate(data);

  if (!valid) {
    console.error("Response schema validation failed!");
    console.error(validate.errors);
    throw new Error("API response does not match schema");
  }

  console.log("✓ Response matches schema");
  return data;
}

// Usage in integration tests
describe('GET /users/:id', () => {
  it('returns user matching response schema', async () => {
    const user = await testGetUser();
    expect(validate(user)).toBe(true);
  });

  it('handles 404 with error schema', async () => {
    const response = await fetch('https://api.example.com/users/nonexistent');
    expect(response.status).toBe(404);

    const error = await response.json();
    const errorSchema = {
      type: "object",
      required: ["code", "message", "timestamp"],
      properties: {
        code: { type: "string" },
        message: { type: "string" },
        timestamp: { type: "string", format: "date-time" }
      }
    };

    const validateError = ajv.compile(errorSchema);
    expect(validateError(error)).toBe(true);
  });
});

Diagram

sequenceDiagram
    participant Client
    participant API
    participant Handler
    participant Validator
    participant Monitor

    Client->>API: GET /users/123
    API->>Handler: Route request
    Handler->>Handler: Fetch user data
    Handler->>Validator: Validate response against schema

    alt Schema Valid
        Validator-->>Handler: Pass
        Handler->>API: Return response
        API-->>Client: 200 OK + User object
    else Schema Invalid
        Validator-->>Handler: Schema violation
        Handler->>Monitor: Alert - Response schema mismatch
        Handler->>API: Return generic error
        API-->>Client: 500 Internal Server Error
    end

    Note over Monitor: Logs schema violations
for debugging

Security Notes

SECURITY NOTES

CRITICAL: Response schemas document expected output. Validate responses match schema.

Response Definition:

  • Success responses: Document what successful response contains
  • Error responses: Document error formats
  • Pagination: Document pagination fields
  • Metadata: Document response metadata

Client Validation:

  • Type validation: Validate response types
  • Required fields: Check for required fields
  • Extra fields: Handle extra fields gracefully
  • Error handling: Handle error responses

Benefits:

  • API contract: Clear contract between client and server
  • Auto-generation: Generate documentation/types
  • Testing: Generate test cases
  • Compatibility: Detect breaking changes

Content Negotiation:

  • Multiple formats: Support JSON, XML, etc.
  • Accept header: Use Accept header for format selection
  • Version-specific: Responses may vary by API version
  • Custom types: Support custom MIME types

Versioning:

  • Backward compatible: Maintain compatibility with old responses
  • Deprecate fields: Mark fields for deprecation
  • Migrate: Provide migration path for clients

Best Practices

  1. Define responses for all status codes - Include 200, 201, 400, 401, 404, 500 at minimum
  2. Use consistent error schema - All endpoints should return errors in the same format
  3. Provide realistic examples - Show actual response data, not abstract schemas
  4. Validate in tests - Contract tests should validate responses against schemas
  5. Version response schemas - Track breaking changes when response structure evolves
  6. Document required vs optional - Be explicit about which response fields are guaranteed
  7. Use discriminators for variants - Model polymorphic responses with oneOf and discriminators
  8. Include pagination metadata - For list endpoints, define totalCount, page, pageSize in schema

Common Mistakes

Missing error schemas: Only defining success responses (200, 201) and forgetting to document error response structures. Clients need to know error formats too.

Inconsistent error responses: Different endpoints returning errors in different formats. This breaks client error-handling logic.

Overpromising with required fields: Marking fields as required in the schema but sometimes not returning them. This breaks type-safe clients.

Not validating responses: Only validating requests while letting backend bugs produce invalid responses. Response validation catches backend issues early.

Exposing too much in dev: Using the same response schema for development and production, accidentally leaking stack traces or debug info to production clients.

Ignoring additionalProperties: Not specifying whether extra fields might appear. Clients don’t know if they should ignore unknown fields or treat them as errors.

No schema for 500 errors: Assuming internal errors are unpredictable. Even 500 responses should follow a schema for consistent logging and alerting.

Standards & RFCs