Seeding Realistic Mock Data with OpenAPI Examples
Problem: the mock server returns {"id": "string", "total": 0, "created_at": "2020-01-01T00:00:00Z"} for every order. The frontend builds a layout around it, the first real response has a forty-character customer name and an empty line-item list, and the layout breaks. This guide is part of Mock Server Strategies and covers making the mock’s data as useful as its shape.
Root Cause: A Schema Describes Types, Not Situations
Given only a schema, a mock generates a value of the right type. It cannot know that an order may have no line items, that a customer name may be long enough to wrap, or that a total may be zero after a full discount. Those are situations, and situations are exactly what a consumer’s UI and error handling have to survive.
Step 1: Enumerate the Cases Worth Showing
Four cases cover most endpoints, and each answers a question a consumer will otherwise answer by guessing:
- Empty — a collection with nothing in it, a resource with every optional field absent. Answers “what does the zero state look like?”
- Typical — the shape ninety per cent of responses take.
- Maximal — every optional field populated, collections at their documented maximum, the longest strings the schema allows. Answers “will the layout hold?”
- Awkward — non-Latin script, a name with an apostrophe, a zero total, a negative adjustment. Answers “what did we forget to escape?”
Step 2: Add a Named Example Per Case
Examples live in the media type object’s examples map. Each key is a name a consumer can select.
paths:
/orders/{id}:
get:
operationId: getOrder
responses:
'200':
description: The order.
content:
application/json:
schema: { $ref: '#/components/schemas/Order' }
examples:
typical:
summary: A paid order with three line items
value:
id: ord_0123456789abcdef
customer_name: Aoife Ní Bhraonáin
status: paid
items:
- { sku: SKU-COFFEE-1KG, quantity: 2, unit_price_minor: 1850 }
- { sku: SKU-FILTER-100, quantity: 1, unit_price_minor: 450 }
- { sku: SKU-MUG-STD, quantity: 4, unit_price_minor: 900 }
discount_minor: 500
total_minor: 7250
currency: EUR
created_at: "2026-07-29T10:00:00.000Z"
empty:
summary: A draft order with no items yet
value:
id: ord_fedcba9876543210
customer_name: Jo Ng
status: draft
items: []
discount_minor: 0
total_minor: 0
currency: EUR
created_at: "2026-07-29T09:58:12.400Z"
maximal:
summary: Every optional field populated, long values throughout
value:
id: ord_ffffffffffffffff
customer_name: Bartholomew Featherstonehaugh-Wollstonecraft III
status: partially_refunded
items: [] # abbreviated here; the real example carries 50
discount_minor: 999999
total_minor: 12500000
currency: JPY # a zero-exponent currency, deliberately
created_at: "2026-07-29T10:00:00.000Z"
settled_at: "2026-07-29T10:04:31.117Z"
notes: "Delivery: leave with the neighbour at no. 14 — bell doesn't work"
Three deliberate choices in those values. The typical example uses a name with a diacritic and a currency with two decimals; the maximal example uses JPY, which has none, so a consumer hard-coding a division by 100 fails against the mock rather than in production; and every identifier is obviously fictional while still matching the schema’s pattern.
Use the plural examples map rather than the singular example field. Only the map supports names, and when both are present tooling behaviour varies.
Step 3: Document How to Select Them
An example nobody can request is documentation, not a fixture. Publish the selection convention alongside the mock’s base URL.
info:
description: |
## Mock server
Base URL: `https://mock.example.com/v2`
Select a named example with the `Prefer` header:
```
Prefer: example=empty
Prefer: example=maximal
```
Select a documented error response by status:
```
Prefer: code=404
Prefer: code=422
```
With no `Prefer` header the mock returns the `typical` example.
# The three situations, from one endpoint.
curl -s -H 'Prefer: example=empty' https://mock.example.com/v2/orders/ord_1 | jq -c '.items'
# []
curl -s -H 'Prefer: example=maximal' https://mock.example.com/v2/orders/ord_1 | jq -r '.customer_name'
# Bartholomew Featherstonehaugh-Wollstonecraft III
curl -s -H 'Prefer: code=404' -o /dev/null -w '%{http_code}\n' https://mock.example.com/v2/orders/ord_1
# 404
The selection mechanics differ by tool — the Prism guide covers its fallback order, and Microcks dispatches on the request instead of on a header.
Step 4: Validate the Examples in CI
An example that drifts from its schema is worse than no example: a consumer builds against it and the real endpoint rejects the same shape.
# .redocly.yaml
rules:
no-invalid-media-type-examples:
severity: error
allowAdditionalProperties: false
no-invalid-schema-examples:
severity: error
$ npx @redocly/cli@1.25 lint api/openapi.yaml
[1] api/openapi.yaml:412:19 at #/paths/~1orders~1{id}/get/responses/200/content/application~1json/examples/maximal/value
Example value must conform to the schema: `currency` must match pattern "^[A-Z]{3}$".
Error was generated by the no-invalid-media-type-examples rule.
Run it in the same job as the rest of the specification checks so a drifting example blocks the merge.
Step 5: Reuse the Examples as Test Fixtures
The examples are already realistic, already validated and already agreed. Extract them rather than maintaining a second set:
// test/fixtures.ts — one source of truth for mock data and unit tests
import { parse } from "yaml";
import { readFileSync } from "node:fs";
const spec = parse(readFileSync("dist/openapi.yaml", "utf8"));
const examples =
spec.paths["/orders/{id}"].get.responses["200"].content["application/json"].examples;
export const orderFixtures = {
typical: examples.typical.value,
empty: examples.empty.value,
maximal: examples.maximal.value,
};
test.each(Object.entries(orderFixtures))("renders the %s order without overflow", (_name, order) => {
const { container } = render(<OrderSummary order={order} />);
expect(container.scrollWidth).toBeLessThanOrEqual(container.clientWidth);
});
Verification
# Every example validates against its schema.
npx @redocly/cli@1.25 lint api/openapi.yaml
# ✓ no errors
# The mock serves each named example.
for name in typical empty maximal; do
printf '%-8s items=%s\n' "$name" \
"$(curl -s -H "Prefer: example=$name" http://127.0.0.1:4010/orders/ord_1 | jq '.items | length')"
done
# typical items=3
# empty items=0
# maximal items=50
# The zero-exponent currency example is present, so a hard-coded /100 fails here.
curl -s -H 'Prefer: example=maximal' http://127.0.0.1:4010/orders/ord_1 | jq -r '.currency'
# JPY
Edge Cases and Caveats
- Personal data. Examples appear in public documentation, screenshots and logs. Use obviously fictional values that still exercise the awkward cases; never paste a real record.
- Examples going stale. A field added to the schema does not appear in the examples, and lint rules that check conformance will not notice an absent optional field. Review examples when a schema changes, and keep the maximal one genuinely maximal.
- Size. A fifty-item example inflates the document noticeably. Keeping large examples in separate files referenced with
externalValuekeeps the specification readable — at the cost of the linter no longer validating them, so weigh it. - Request examples too. Examples on request bodies are equally useful: they give a consumer something to paste, and they document the awkward cases a client must be able to send.
- Mock latency. Real responses take time; a mock answers instantly, so consumers build without loading states. Configure an artificial delay if your mock supports one, or the first real integration surfaces a class of missing UI.
Keeping Examples Trustworthy as the API Grows
Examples decay faster than schemas, because a schema change is validated and an example change is remembered. Three habits keep them worth reading.
Review examples when the schema changes. The lint rules catch an example that violates its schema, not one that has simply become unrepresentative — a status example still showing a state the product removed, or a maximal example carrying ten items when the limit is now fifty. A short checklist item on any schema change is enough.
Derive examples from real traffic occasionally. Take an anonymised production response, strip anything identifying, and compare it with the typical example. The differences are what your consumers see and your examples do not: fields that are always null in practice, arrays that are never empty, strings twice as long as the example suggests.
Keep the maximal example genuinely maximal. It is the one that catches layout bugs, and it is the first to fall behind when a new optional field is added. If only one example is kept current, make it this one.
Frequently Asked Questions
Why not let the mock generate values from the schema?
Because schema-generated values are type-correct and meaningless — string, 0, 2020-01-01. Consumers build layouts around them, and the first real response breaks the layout. Named examples let you show a long name, a zero total and an empty list deliberately.
Where do examples belong in the document?
In the media type object’s examples map, keyed by a descriptive name. Avoid the singular example field alongside it: when both are present, tooling behaviour varies and only one is selectable by name.
How does a consumer choose which example to receive?
With the Prefer header — Prefer: example=empty for the named example, or Prefer: code=404 for a documented error. Both are Prism conventions and are worth documenting alongside the mock’s base URL.
Should examples be validated?
Always, in CI. An example that no longer matches its schema is worse than none, because a consumer builds against it and the real endpoint rejects the same shape.
Can examples contain realistic personal data?
Use realistic shapes, never real people. Examples end up in public documentation, in logs and in screenshots, so the safest habit is obviously-fictional values that still exercise the awkward cases — long names, non-Latin scripts, missing optional fields.