# How to serve your OpenAPI examples as mock responses

> Wire the examples in your OpenAPI description to what the mock server returns, so the docs, the tests and the mock all show one payload.

Source: https://voxgig.com/howto/serve-openapi-examples-as-mock-responses

- Audience: api-producer
- Level: beginner
- Verified: 2026-09-14
- Published: 2026-09-14
- Updated: 2026-09-24

## Short answer

Put the payload in the description under `examples`, give each one a name, and let the mock serve it. A client picks a named example with a `Prefer: example=<name>` request header, which is the convention Prism established. Validate every example against its own schema in CI, because an example that drifts from the schema still renders in the docs and still ships.

---
## You will need

An OpenAPI 3 description with at least one response example, and Node 22 or later. The `example`
and `examples` keywords are defined in the
[Media Type Object](https://spec.openapis.org/oas/v3.1.0.html#media-type-object), and the request
header comes from [RFC 7240](https://www.rfc-editor.org/rfc/rfc7240.html).[^1]

## Approaches compared

| Approach | When it fits | What it costs you | When to pick something else |
| --- | --- | --- | --- |
| [A mock built from the description](https://spec.openapis.org/oas/v3.1.0.html#example-object) | You want the mock in your own test process with no extra service | Code to maintain, and only the behavior you thought to write | A hosted mock already covers what you need |
| [Hand-written JSON fixtures](https://nodejs.org/api/test.html) | A handful of cases, edited freely without touching the description | Three sources of truth, which drift within a release or two | The description already carries the payloads |
| [Mockoon route responses](https://mockoon.com/docs/latest/about/) | You want rules, delays and a desktop tool a tester can drive | A second definition of the API, kept in the tool rather than in the repository | The description is the artifact your team already reviews |
| [Prism static mode](https://docs.stoplight.io/docs/prism/83dbbd75532cf-http-mocking) | Any team that already publishes a description and wants a mock for free | A service to run, and behavior fixed by what the description says | You need stateful behavior across requests |

The choice is really about how many places a payload lives. A description that carries its examples
has one, and the mock, the docs and the contract tests all read it. Fixtures give you freedom to
write a case the description does not describe, which is useful right up to the moment the two
disagree and nobody notices. A hosted mock sits in between: one source, one more service to run.

## Name every example

Named examples cost nothing at the point of writing and buy a great deal at the point of testing.

```json title="openapi.json"
"examples": {
  "installed": {
    "summary": "A meter in service",
    "value": { "id": "mtr_8f2", "serial": "SN-40199", "state": "installed" }
  },
  "retired": {
    "summary": "A meter taken out of service",
    "value": { "id": "mtr_31a", "serial": "SN-40200", "state": "retired" }
  },
  "stale": {
    "summary": "Written before state became required",
    "value": { "id": "mtr_0b4", "serial": "SN-40201" }
  }
}
```

The names become the vocabulary a test uses. A test that asks for `retired` reads as a sentence,
and the payload it gets is the one the docs render beside the endpoint. The `summary` field is what
a docs renderer shows in the example picker, so write it for a reader rather than for yourself.

Three examples is usually the right number for an endpoint: the ordinary case, the interesting
case, and the empty one. More than that and the picker in the docs becomes a menu nobody reads.
Fewer, and the reader has to guess what an absent optional field means.

## Let the header pick the example

One header does the selection, and an unknown name is an error rather than a fallback.[^2]

```js title="mock.mjs"
const wanted = /example=([\w-]+)/.exec(req.headers.prefer ?? '')?.[1]
const names = Object.keys(media.examples)
const name = wanted ?? names[0]
if (!media.examples[name]) {
  return send(res, 400, { title: `No example named ${name}`, detail: `have: ${names.join(', ')}` })
}
res.setHeader('preference-applied', `example=${name}`)
```

Refusing an unknown name matters more than it looks. A mock that falls back to the first example
turns a typo in a test into a passing test asserting the wrong payload. The failure then shows up much
later, against the real API, in a pull request that changed nothing related.

Echo `Preference-Applied` so a caller can tell which example it actually received. Without it, a
test cannot separate an honored request from a request the mock ignored while happening to return
the right thing.

## Check it worked

Validate the examples, then ask the mock for each of them.

```bash
node demo.mjs
```

```text output
-- every example checked against the schema it claims
installed  ok
retired    ok
stale      $.state: required property is missing
-- the mock answers with the example you asked for
example=installed  200 {"id":"mtr_8f2","serial":"SN-40199","state":"installed"}
example=retired    200 {"id":"mtr_31a","serial":"SN-40200","state":"retired"}
example=nope       400 {"title":"No example named nope","detail":"have: installed, retired, stale"}
```

The third line is the one that earns the check. That example was written before `state` became
required, it still renders in the docs, and a client generated from those docs would expect a field
the API always sends. Nothing about reading the description reveals it. Only running the example
through its own schema does. Put that check in the same job that lints the description, so it runs
on every change rather than on the days somebody remembers.

```bash
node --test mock.test.mjs
```

```text output
1..6
# tests 6
# suites 0
# pass 6
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 298.44952
```

## When it goes wrong

Every paged request comes back as a 404 against a path that is clearly documented. The router is
matching `req.url`, which carries the query string, against an OpenAPI path, which never does. Parse
the URL and match only its path. It is the first thing a client hits and the last thing anyone
suspects, because the path in the error message looks right.

The mock returns a generated payload rather than your example. The example sits on the schema
instead of on the media type, or it uses `example` where the tool reads `examples`. Both keywords
exist and they are not interchangeable.[^3]

Every request gets the same body. The selection header is being dropped by a proxy, or the mock
reads a query parameter you did not send. Assert on `Preference-Applied` rather than on the body
alone.

An example renders correctly and fails validation. It is stale. Run the validation in CI, and fail
the build rather than opening an issue nobody will pick up.

A test passes against the mock and fails against the real API. The example describes a payload the
service never produces. Generate examples from a recorded response once, then keep them under the
same review as the schema. A recorded payload with the identifiers replaced is worth more than an
invented one, because it carries the fields nobody remembered to document.

## When not to do this

Do not mock a workflow that depends on state. A description holds payloads, not transitions, so a
create followed by a read returns the example rather than what you created. Use a real service for
those paths, or a mock built to hold state.

Do not let the examples become the test data for everything. An example exists to show a reader
what a response looks like, and one stretched to cover every edge case stops doing that job. Keep
the examples readable, and put the awkward cases in tests where they can carry a comment.

Do not skip validating them because the schema is right. The schema and the example drift in
opposite directions during a rename, and the example is the half nobody regenerates.

## Related how-tos

- [Stub outbound HTTP calls in Node tests with nock](/howto/stub-outbound-node-http-calls-with-nock)

- [Validate responses against the OpenAPI document at runtime](/howto/validate-responses-against-the-openapi-document)

## Last verified

Verified 2026-09-14 against Node 22.22.2. Both output blocks are what the preceding command
printed.

[^1]: [RFC 7240](https://www.rfc-editor.org/rfc/rfc7240) is by J. Snell, dated June 2014, and
it introduces `Prefer` as a header the server is allowed to ignore. A server that does not
recognize a preference must ignore the token and carry on rather than fail, which is what makes
`Preference-Applied` worth echoing. The
[IANA registry](https://www.iana.org/assignments/http-parameters/http-parameters.xhtml#preferences)
the RFC set up holds thirteen preferences, six of them prefixed `odata.`, and `example` is not
among them. Prism's convention is a convention, and the registry is the place it is not.

[^2]: The mock's refusal carries `title` and `detail`, two of the five members
[RFC 9457](https://www.rfc-editor.org/rfc/rfc9457#section-3.1) defines for a problem details
body. That document is dated July 2023 and obsoletes
[RFC 7807](https://www.rfc-editor.org/rfc/rfc7807), which had said much the same in March 2016
under two of the same three names.
A body with no `type` member is taken to be of type `about:blank`, the one problem type the RFC
registers itself, with a recommended status code given as not applicable.

[^3]: [OAS 3.1.0](https://spec.openapis.org/oas/v3.1.0.html#media-type-object) says of the Media
Type Object that `example` is mutually exclusive of `examples`. One row down it says that
`examples` is mutually exclusive of `example`, in case the first sentence had left a doubt. The
[Schema Object](https://spec.openapis.org/oas/v3.1.0.html#schema-object) has an `example` of
its own, marked deprecated in favor of the `examples` keyword of JSON Schema.
[Section 9.5](https://json-schema.org/draft/2020-12/json-schema-validation) of that
specification defines it as an array, so its entries have no names. A payload can be written three
ways under two spellings, and only one of the three lets a header pick it.