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.
Tell me about a time you had to push back on an API or data-model design that seemed simpler for short-term delivery but would have created long-term pain for downstream consumers. How did you make the case, and what was the final decision?
Sample Answer
The strongest version of this story is not "I was right and they were wrong," it's showing the concrete, specific downstream cost the simpler design would have created, translated into terms the other side of the table actually cared about (a delivery deadline, a support burden, a migration cost six months out), and then describing how the actual decision got made once that cost was visible to everyone.
What a strong answer walks through
The situation: name the specific design choice under pressure (a schema shortcut, a field reused for two purposes, a response shape copy-pasted from an unrelated endpoint) and the delivery pressure driving it, honestly, without caricaturing the other side's position as simply wrong.
The case you made: the strongest version of this case is concrete, not principled in the abstract. "This will be hard to maintain" rarely moves a deadline; "this specific field reuse means every future consumer has to special-case whether this response came from path A or path B, and we already have three planned features that will need to tell them apart" is something a stakeholder can actually weigh against the deadline.
How the decision actually got made: did you get the design changed outright, negotiate a smaller fix with a follow-up ticket to do it properly, or lose the argument and later have to deal with the consequence you predicted? All three are legitimate answers; the weakest version of this story claims a clean win with no friction, which reads as either an easy problem or a polished retelling.
The outcome: what happened afterward, concretely, that validates (or complicates) the case you made at the time.
Worked example shape
"We were under a two-week deadline to ship a partner integration, and the plan was to reuse an existing internal user_id field on the response as the partner-facing identifier, saving us from adding a new field and updating a few internal services. I pushed back because I could see two features already on the roadmap that would need a distinct partner-facing identifier separate from the internal one (partner-scoped API keys, and a planned data-residency requirement that needed to know which records were partner-visible). I proposed adding a new partner_ref field instead, which took two extra days but meant we did not have to do a breaking migration eight weeks later when the partner-scoped-API-keys feature landed and genuinely needed that separation. The team agreed to the two extra days once the specific upcoming conflict was visible, not because of a general 'good practice' argument."
Trade-offs and pitfalls
A common weak point in this story is framing it as pure technical purism ("clean code" or "best practices") rather than a concrete, forecastable cost; interviewers are listening for whether you can translate a technical concern into terms a non-technical stakeholder, or a deadline-driven engineering lead, would actually act on. Equally common: telling this story as an unqualified win when the honest answer is more nuanced (you got a smaller compromise, or the team shipped the shortcut anyway and you were right six months later); either honest version is stronger than a suspiciously frictionless win.
Write a JSON Schema (draft 2020-12 or later) for a POST endpoint /v1/apps/{app_id}/events that accepts an event payload with properties: eventType (enum), timestamp (ISO 8601 string), actor (object with id and type), metadata (optional object with string values), and items (array of objects with id and quantity). Include validation rules, required fields, and example success and validation-error responses.
Sample Answer
The schema needs five things: a type constraint on every field, an enum for eventType so only known event kinds validate, a format constraint on timestamp, an object shape for actor with its own required sub-fields, and an array shape for items with per-item structure. Below is a complete, valid JSON Schema (draft 2020-12) for the event.
The schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://api.example.com/schemas/app-event.json",
"title": "AppEvent",
"type": "object",
"properties": {
"eventType": {
"type": "string",
"enum": ["item_created", "item_updated", "item_deleted"]
},
"timestamp": {
"type": "string",
"format": "date-time"
},
"actor": {
"type": "object",
"properties": {
"id": { "type": "string" },
"type": { "type": "string", "enum": ["user", "service"] }
},
"required": ["id", "type"],
"additionalProperties": false
},
"metadata": {
"type": "object",
"additionalProperties": { "type": "string" }
},
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "string" },
"quantity": { "type": "integer", "minimum": 1 }
},
"required": ["id", "quantity"],
"additionalProperties": false
},
"minItems": 1
}
},
"required": ["eventType", "timestamp", "actor", "items"],
"additionalProperties": false
}
Design decisions worth calling out
metadatais genuinely optional (absent fromrequired), and itsadditionalPropertiesconstraint restricts VALUES to strings without naming the specific keys, which keeps the schema honest about what "optional object with string values" means without over-constraining future keys.itemsrequires at least one entry (minItems: 1): an event with an empty items array is treated as invalid here, a deliberate validation rule rather than an oversight.additionalProperties: falseon the top level and on each nested object rejects unrecognized fields, which catches client typos early but is itself a compatibility decision: it means adding a genuinely new field later requires updating this schema in lockstep with any producer that starts sending it, rather than silently tolerating it.
Worked example: validating example payloads against this schema
A valid event:
{
"eventType": "item_created",
"timestamp": "2026-07-28T12:00:00Z",
"actor": { "id": "usr_123", "type": "user" },
"metadata": { "source": "mobile_app" },
"items": [{ "id": "sku_1", "quantity": 2 }]
}
This validates cleanly: every required field is present, eventType is one of the three enum values, and items has one well-formed entry.
An invalid event (empty items):
{
"eventType": "item_created",
"timestamp": "2026-07-28T12:00:00Z",
"actor": { "id": "usr_123", "type": "user" },
"items": []
}
This is rejected, specifically because items has zero entries and the schema's minItems: 1 constraint requires at least one. Both examples above were run against this exact schema with a JSON Schema validator; the valid example passed and the empty-items example failed with the minItems violation, confirming the schema behaves as written.
Example success response: {"event_id": "evt_9a1b", "accepted": true} (HTTP 201). Example validation-error response: {"error_code": "validation_error", "message": "items must contain at least 1 item"} (HTTP 400).
Trade-offs and pitfalls
additionalProperties: false is a real trade-off, not a free safety net: it makes typos in client requests fail loudly (good), but it also means the schema and every producer must be updated together the moment a genuinely new, backward-compatible field needs to be added, which is slower than a looser schema that simply ignores unknown fields. Choosing strict validation here is defensible for an internal event contract with few, coordinated producers; a public-facing schema with many independent producers might reasonably relax this to allow additive fields to pass through unvalidated.
What fields does a good JSON error response need so both a machine caller and a human developer can act on a failure? Design that shape for a REST API consumed by mobile and web clients: an error code, a user-facing message, a developer-facing trace or correlation ID, and how you would map these to HTTP status codes. Provide one example error JSON a client could parse programmatically.
Sample Answer
Four things, at minimum: a machine-parsable error CODE (a stable string a client can branch logic on), a human-readable MESSAGE (for logging and debugging, never for client branching logic), a correlation or trace ID (so a developer can find this exact request in server-side logs), and a documented mapping from error codes to HTTP status codes so the status line and the body agree on what actually happened.
Why each field earns its place
Error code, not just a status code. HTTP status codes are coarse (a 400 covers many different validation failures); a specific, stable string like insufficient_inventory lets a client write real logic ("if this specific error, show the user a restock message and offer alternatives") instead of parsing a human-readable sentence, which is fragile and can change wording without warning.
A message meant for humans, not machines. The message field exists for logs, support tickets, and developer debugging; a client should never regex-match against it to decide behavior, because message wording is exactly the kind of thing that changes without being treated as a breaking change.
A correlation ID. When a client reports "I got an error," the correlation ID is what lets an engineer find the exact request in server-side logs in seconds instead of guessing based on approximate timestamps.
A stable status-code mapping. insufficient_inventory should map to the same HTTP status every time (409 Conflict is a reasonable choice here, since it reflects a state conflict rather than malformed input), documented so both server and client code agree on the mapping rather than each side guessing.
Worked example
{
"error": {
"code": "insufficient_inventory",
"message": "Only 3 units of sku_1001 are available.",
"correlation_id": "req_8f2c9a1e",
"retryable": false
}
}
This example is 145 bytes as compact JSON. A client parses this programmatically by checking error.code === "insufficient_inventory" (stable, documented) to decide its own behavior (offer the customer 3 units instead of the requested 5), logs error.correlation_id for later debugging if the customer files a support ticket, and displays a localized version of its OWN copy for that error code to the end user rather than showing error.message directly (which is written for a developer audience, in one language, and not meant for end-user display).
Trade-offs and pitfalls
The most common mistake is putting only a human-readable message in the error body and expecting clients to parse it for meaning; the moment that message's wording changes (even a small rewording meant purely to be clearer for humans), any client parsing it silently breaks. A second common mistake: reusing the SAME error code for conceptually different failures because they happen to produce the same HTTP status, which forces clients back to string-matching the message anyway to tell them apart, defeating the entire purpose of having a code field.
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.
Explain contract-first (OpenAPI-first) versus code-first API development. Discuss how each approach impacts documentation quality, SDK generation, design reviews, iteration speed, and coordination with product and client teams in a cross-functional environment.
Sample Answer
Contract-first (also called design-first) means writing the OpenAPI specification before any implementation code exists, and treating that spec as the source of truth the rest of the process is built around. Code-first means implementing the endpoints first and generating the spec afterward, usually from code annotations. The trade-off is speed now versus alignment later: code-first ships an initial version faster, contract-first spends that time up front and gets it back many times over as the API grows.
How each choice plays out in practice
Documentation quality. Contract-first documentation is authored intentionally and reviewed as a deliverable in its own right, so it tends to describe intent, not just implementation detail. Code-first documentation is generated from whatever the code happens to do, so it is only as good as the annotations a developer remembered to write, and it can drift the moment someone edits a handler without updating the annotation.
SDK generation. Contract-first lets you generate client SDKs (software development kits), in multiple languages, before a single endpoint is implemented, because the spec is a complete, self-contained artifact. Code-first SDK generation has to wait for the code to exist, and if the spec is only produced from annotations, gaps in those annotations become gaps in the generated client (an endpoint the annotations mislabeled generates a client method with the wrong shape).
Design reviews. A written spec is something a reviewer, a frontend engineer, or an external partner can read and comment on before any implementation exists, which surfaces disagreements about the shape of the contract while they are still cheap to fix. Reviewing a code-first API usually means reviewing a pull request that already contains the implementation, so a disagreement about the contract's shape now means reworking real code, not just a document.
Iteration speed. For a small, fast-moving internal service with few consumers, code-first is genuinely faster: you skip the spec-authoring step and let the framework generate documentation as a side effect. That advantage shrinks, and can invert, once there are multiple independent consumer teams who need the contract stabilized before they start their own work.
Coordination across teams. This is where contract-first tends to win as the organization grows. A stable, reviewed spec becomes the coordination artifact multiple teams can build against in parallel: product managers get a concrete, reviewable artifact to confirm the contract actually matches the intended business requirements before a single line of implementation exists, instead of discovering a scope mismatch during a late-stage demo; frontend engineers write against mocked responses generated straight from the spec, and partner integrations get a concrete document to implement against, all before the backend team ships a single endpoint.
Worked example: the same endpoint, two orders of operations
Contract-first: write the OpenAPI document for POST /orders (request schema, response schema, error responses) -> review it with the frontend and partner teams -> generate a mock server from it so the frontend team starts building immediately -> implement the backend against the now-frozen spec -> validate the real implementation against the spec in CI.
Code-first: implement POST /orders -> annotate the handler with the framework's OpenAPI decorators -> generate the spec from those annotations -> frontend team waits for either the real endpoint or a manually-maintained mock, because no independent spec existed to generate one from earlier.
Trade-offs and pitfalls
Contract-first fails when the spec is written once and never re-validated against the implementation: without a CI (continuous integration) check that diffs the running service's actual behavior against the spec, the two drift apart just as easily as in code-first, except now with a false sense of security because "we have a spec." Code-first fails quietly: a subtle annotation mistake (a field the code treats as optional but the annotation marks required) produces a generated SDK that is wrong in a way nobody notices until a client hits it in production. Neither approach is safe without a CI gate that continuously checks the deployed behavior against the published contract, whichever one is authoritative.
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.