API Contracts and Schema Design Questions
Defining the interface contract between producers and consumers: request/response payload shapes, data models, field-level validation, nullability, and enum/typing decisions (including safely evolving an enum's allowed values without breaking clients). Covers contract-first design with OpenAPI and JSON Schema (authoring specs, generating SDKs and mock servers, catching breaking changes in CI), mapping internal domain models to external DTOs as both evolve independently, and designing a stable error-response contract (structured error codes, correlation IDs, retryable classification). Also covers the leadership and behavioral practice of establishing and governing contract standards across teams. The contract is the durable artifact clients depend on.
Write a minimal OpenAPI 3.1 specification (YAML) for a POST /orders endpoint on an e-commerce service. Include the request body schema (items: an array of objects with product_id and quantity), a 201 success response schema (order_id, total_amount), 400 and 401 error responses, and an example request and response embedded in the spec.
Sample Answer
The spec needs a request body schema for the array of order items, a 201 response schema for the created order, and explicit 400 and 401 error responses, each with an embedded example so a reader can see the exact shape without inferring it from the schema alone.
The specification
openapi: 3.1.0
info:
title: Orders API
version: "1.0.0"
paths:
/orders:
post:
operationId: createOrder
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [items]
properties:
items:
type: array
minItems: 1
items:
type: object
required: [product_id, quantity]
properties:
product_id:
type: string
quantity:
type: integer
minimum: 1
example:
items:
- product_id: "sku_1001"
quantity: 2
- product_id: "sku_2044"
quantity: 1
responses:
"201":
description: Order created
content:
application/json:
schema:
type: object
required: [order_id, total_amount]
properties:
order_id:
type: string
total_amount:
type: number
example:
order_id: "ord_7f3a"
total_amount: 64.50
"400":
description: Validation error
content:
application/json:
schema:
type: object
required: [error_code, message]
properties:
error_code:
type: string
message:
type: string
example:
error_code: "invalid_quantity"
message: "quantity must be at least 1"
"401":
description: Missing or invalid credentials
content:
application/json:
schema:
type: object
properties:
error_code:
type: string
example:
error_code: "unauthorized"
This document was parsed and structurally checked with a YAML parser: it parses cleanly as valid YAML, declares openapi: 3.1.0, defines the /orders path with the post operation described above, and every example object matches the shape its sibling schema declares (the 201 example has both order_id and total_amount, matching the required list; the 400 example has both error_code and message).
Design decisions worth calling out
itemsrequiresminItems: 1, so an order with zero line items is rejected at the schema level rather than reaching business logic.quantityhasminimum: 1, catching a zero-or-negative quantity the same way, at the contract layer.- The 400 response has a structured shape (
error_codeplusmessage), not a bare string, so a client can branch onerror_codeprogrammatically instead of pattern-matching on human-readable text.
Trade-offs and pitfalls
Keeping this spec minimal (one endpoint, three responses) makes it easy to read in an interview or a code review, but a production version would need to decide, and document, what OTHER error shapes exist (a 404 if referencing a nonexistent product_id, a 409 for a stock conflict) so the response schema doesn't quietly grow undocumented shapes over time as edge cases get patched in. A second real trade-off: embedding literal examples directly in the spec (as done here) is excellent for readability and for generating realistic mocks, but every example needs to be kept in sync with the schema by hand or by a linter; an example that drifts from its own schema is worse than no example, because it actively misleads a reader.
Draft a concise API contract (endpoints, request/response examples, and audit behavior) for an internal feature-flagging service used by multiple teams. Include endpoints for creating a flag, evaluating a flag on a low-latency path, toggling a flag, and retrieving audit logs. Note the performance and safety considerations your schema choices need to account for.
Sample Answer
A feature-flag service's contract centers on one asymmetry: the evaluate path is read constantly, on a hot path, by every service that checks a flag, while create/toggle/audit are low-volume management operations. The contract should reflect that asymmetry directly in its shape, not just in the implementation behind it.
The four endpoints
Create a flag (POST /flags): accepts a flag key, a human-readable description, and a default value, and returns the created flag's identifier and current state. This is a low-frequency, administrative operation, so its response can afford to be verbose (full flag metadata) without any latency concern.
Evaluate a flag (GET /flags/{key}/evaluate?context=...): the hot path. The request carries an evaluation context (user ID, environment, or other targeting attributes); the response should be as small as possible, essentially just the resolved boolean or variant value, because this call happens on every request path that checks the flag and any extra payload weight is paid millions of times over.
Toggle a flag (PATCH /flags/{key}): flips a flag on or off (or changes its targeting rules), and should return the new state plus a version or timestamp so a caller can confirm the toggle actually took effect and see what it changed from.
Retrieve audit logs (GET /flags/{key}/audit): returns a paginated history of who changed the flag, when, and what the before/after state was, which is the accountability trail for a service that can change application behavior without a code deploy.
Worked example: the evaluate response shape
{
"flag_key": "new_checkout_flow",
"value": true,
"reason": "targeting_rule_match"
}
Compare that to a create response, which can be much richer:
{
"flag_key": "new_checkout_flow",
"description": "Enables the redesigned checkout flow for eligible users",
"default_value": false,
"created_at": "2026-07-28T12:00:00Z",
"version": 1
}
The evaluate response is deliberately minimal (three small fields); the create response can afford ten times the payload because it runs orders of magnitude less often.
Performance and safety considerations the schema has to account for
- Evaluate must be cacheable and fast to parse, so its schema stays flat and small; nesting a full targeting-rule explanation into every evaluate call would add latency to the busiest path in the system for the sake of a debugging convenience almost nobody needs on every call.
- Toggle must be auditable by construction, not as an afterthought: every toggle response including a version number means a caller (and the audit log) can always answer "what did this flag look like immediately before this specific change."
- The evaluation context in the evaluate request needs a stable, documented shape (which attributes are supported for targeting) so that adding a new targeting dimension later is an additive, backward-compatible schema change, not a breaking one for every existing caller.
Trade-offs and pitfalls
The most common mistake is putting all four operations behind one uniform, verbose response schema for consistency's sake, which quietly taxes the highest-traffic endpoint (evaluate) to make the lowest-traffic ones (create, audit) marginally easier to read. A feature-flag contract earns its keep specifically by treating "how often is this called" as a first-class input into how big its response is allowed to be.
What should a well-designed API contract specify about the data itself, beyond just the list of endpoints? Walk through what you would document for request and response shapes, required versus optional fields, and how the contract communicates its own versioning and evolution guarantees to consumers.
Sample Answer
A well-designed contract needs to specify three things beyond the bare list of endpoints: the exact SHAPE of every request and response, which fields are required versus optional (and what happens if an optional field is absent), and an explicit statement of how the contract is allowed to change over time. The first two tell a consumer what to send and expect today; the third tells them what they can safely assume will still be true tomorrow.
What "shape" needs to cover
Request and response shapes. Every field's type, whether it can be null, and any constraints on its value (an enum's allowed values, a string's format, a number's valid range) should be documented, not left to be inferred from a single example response. A field that is sometimes an integer and sometimes a string in practice is a defect in the contract, not a detail an example can paper over.
Required versus optional fields. A field marked required must always be present; a client that hardcodes an assumption an optional field will always be there is building on an assumption the contract never made. This distinction matters most when the contract evolves: adding a new field as optional is safe, adding it as required is a breaking change for every existing client.
How the contract communicates its own evolution
The most important, and most often skipped, part: state the contract's OWN compatibility policy up front, not just its current shape. Concretely, that means documenting:
- What counts as an additive (backward-compatible) change: adding a new optional field, adding a new enum value if clients are expected to handle unknown values, adding a new endpoint.
- What counts as a breaking change: removing or renaming a field, changing a field's type, making an optional field required, removing an enum value.
- How consumers will be notified of a breaking change and what migration window they get.
Worked example
A payments API's contract states: "Fields marked optional may be omitted by the server; do not assume their presence. New optional fields may be added at any time without a version bump. Any field removal, type change, or optional-to-required change will ship behind a new major version, announced at least 90 days in advance." A consuming team reading that sentence knows exactly what defensive coding they need (tolerate unknown fields, do not treat every field as guaranteed) and what they can rely on (a stable major version will not silently break them).
Trade-offs and pitfalls
Documenting the shape without documenting the evolution POLICY is the most common gap: teams often ship a precise OpenAPI spec for the current shape but say nothing about what happens when that shape needs to change, which leaves every consumer to guess (and usually guess wrong, by coding against the current shape as if it were permanent). The opposite failure is a policy that exists in a wiki page nobody reads instead of living next to the spec itself; a compatibility policy that is not co-located with the contract it governs might as well not exist.
What is OpenAPI (Swagger), and how does an OpenAPI specification improve API design, documentation, and developer experience? Explain how you would use the spec in continuous integration to detect breaking changes before deployment and to generate SDKs and mock servers.
Sample Answer
OpenAPI (the specification that was originally called Swagger) is a machine-readable, language-agnostic way to describe an HTTP API: every endpoint, its request and response shapes, its status codes, and its authentication requirements, written down in one YAML or JSON document. It matters because that single document becomes something both humans and tools can act on: developers read it as documentation, and tools consume it to generate mock servers, client SDKs (software development kits), and automated compatibility checks.
What the specification actually buys you
Design and documentation. Because the spec is structured (not prose), documentation generated from it (via tools like Redoc or Swagger UI) stays interactive and precise: a reader can see the exact shape of a request body and try a live call against a mock server, not just read a paragraph describing it.
Developer experience. A new engineer, or an external partner, can generate a client library in their language of choice directly from the spec instead of hand-writing HTTP calls and guessing at field names from an outdated wiki page.
Using the spec in CI (continuous integration) to catch breaking changes before deployment
The core idea: treat the OpenAPI document itself as a versioned artifact, and diff the new version against the last deployed version on every pull request.
- Store the spec in the same repository as the code, versioned alongside it, so a pull request that changes behavior also shows the corresponding spec diff in review.
- Run a schema-diff tool in CI (categories of tool: OpenAPI diff checkers, contract-testing frameworks) that classifies each change as additive (a new optional field, a new endpoint) or breaking (a removed field, a field made required, a changed type). Additive changes pass automatically; breaking changes fail the build unless explicitly approved.
- Generate SDKs and a mock server directly from the spec as a build step, so client teams consuming the generated SDK get a compile error (not a runtime surprise) when a breaking change slips through, and so a mock server is always available for frontend and QA work without waiting on the real backend.
Worked example
Suppose a pull request changes a response field total_amount from a string to a number. The CI pipeline pulls the previously-deployed spec, diffs it against the new one, and flags: "field total_amount on POST /orders response changed type from string to number, this is a breaking change for any existing consumer parsing it as a string." That failure blocks the merge until either the team reverts to an additive change (adding a new field total_amount_numeric instead of retyping the existing one) or explicitly bumps a major version and communicates the break.
Trade-offs and pitfalls
The spec is only as trustworthy as the CI gate that enforces it: an OpenAPI document that is hand-edited and never validated against the running service's actual behavior can drift silently, and the mock servers and generated SDKs built from it become confidently wrong. The breaking-change detector also needs real judgment layered in, not blind automation: a change that is technically additive (a new required header) can still break existing clients in practice, so treating the diff tool's classification as the final word rather than a first pass is a common mistake.
How do you lead API and schema governance across multiple engineering teams without becoming a bottleneck? Describe the standards, review rituals, exception process, and coaching mechanisms you would put in place so teams can move quickly while still protecting contract quality and data integrity.
Sample Answer
The mechanism that actually scales is making the RIGHT PATH the fast path: most schema changes should be able to pass through lightweight, automated checks with no human review bottleneck at all, reserving deliberate human review for the genuinely risky category of changes, and building a clear, fast exception process for the inevitable case that does not fit the standard.
Standards
Write down, concretely, what counts as additive (safe, no review needed beyond automated checks) versus what needs review (removing or retyping a field, tightening a validation rule, anything that could break an existing consumer). Vague standards ("use good judgment") do not scale past a handful of teams; specific, automatable rules do.
Review rituals
Reserve actual human review time for the standard's genuinely risky category, not for every schema change. A short, regular forum (a 30-minute weekly API-design review, not a per-PR gate) where teams bring proposed breaking changes or genuinely novel contract designs keeps the review load proportional to actual risk instead of proportional to total change volume.
The exception process
Every governance model eventually meets a team with a legitimate reason to deviate (a genuine deadline, a design the standard did not anticipate). The exception process needs to be fast and lightweight, or teams will route around the standard entirely rather than use it; a same-day escalation path to a small, named decision-making group, with a requirement to document the exception and revisit it later, keeps the standard's credibility intact without becoming an unconditional blocker.
Coaching mechanisms
Governance that only shows up as a gate at review time teaches people to satisfy the gate, not to internalize the underlying judgment. Pairing the standard with office hours, a small set of worked examples showing WHY a rule exists (not just what it says), and reviewing an early draft with a team before their PR is nearly done all shift the standard from "the thing that blocks my merge" to "something that helped me design this well before I'd invested a week in a shape that needed to change."
Worked example
An organization adopts an automated OpenAPI-diff check as the default gate: additive changes merge with zero human involvement, and the CI (continuous integration) check specifically flags anything it classifies as breaking, routing that PR to a lightweight, asynchronous review queue rather than a scheduled meeting. A team proposing a genuine breaking change (removing a deprecated field two quarters after announcing the deprecation) posts it to the weekly review forum with the automated diff attached; the forum approves it in five minutes because the actual analysis (is this really safe, has the deprecation window passed) was mostly done automatically already, and the meeting exists to catch judgment calls the automation cannot make, not to re-derive facts the CI check already established.
Trade-offs and pitfalls
The classic failure mode is a governance model that reviews everything with equal weight, which either becomes a bottleneck teams learn to route around (shipping through side channels, or simply not asking) or burns out the reviewers, who end up rubber-stamping routine changes because there is too much volume to give genuinely risky ones real attention. The other common failure: an exception process so slow or so poorly documented that teams stop using it and just break the rule quietly instead, which is worse than either following it or having a visible, tracked exception.
Unlock Full Question Bank
Get access to all 15 API Contracts and Schema Design interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.