Skip to main content

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.

Type-correct is not the same as realistic A schema-derived response supplies placeholder values of the right types. Named examples supply an empty case, a typical case and a maximal case, which are the three shapes a consumer's layout must survive. generated from the schema "customer_name": "string" "items": [ { … one item } ] "total_minor": 0 one shape, and not one you will ever see named examples empty: no items, zero total typical: three items, one discount maximal: 50 items, long unicode name the three shapes the layout must 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.

One endpoint, four situations The same mock endpoint answers with the empty, typical, maximal or error example depending on the Prefer header, so a consumer can build and test every branch without a live provider. GET /orders/{id} one mock endpoint Prefer: example=empty the zero state renders no header — typical the everyday layout Prefer: example=maximal long strings, 50 rows Prefer: code=404 every branch of the UI built before the provider ships

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:

One example set, three consumers The examples in the contract drive the mock server, the rendered documentation and the component test fixtures, so a change to an example updates all three and none of them can drift. examples in the contract validated in CI mock server selected by Prefer rendered docs what integrators copy component tests the same three fixtures
// 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 externalValue keeps 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.