Generating Mock Servers From OpenAPI With Prism
The frontend team is blocked because the payments service it integrates with is still being built, and the hand-written stubs they made last sprint no longer match the agreed API — amount became amount_cents, and the client silently shows NaN. Or Prism is running but every request returns 422 Unprocessable Entity with Request body must have required property 'currency', and nobody is sure whether the client or the mock is wrong. Prism generates a mock server directly from an OpenAPI document, so the mock can only return what the contract allows and can reject requests the contract forbids. This page sets it up and reads its errors, as part of mocking external APIs and third-party services.
A mock generated from the contract removes one source of drift entirely: nobody writes responses by hand, so they cannot diverge from the specification.
Diagnostic
Start Prism against the specification and send the request the client makes, with verbose errors:
#!/usr/bin/env bash
set -euo pipefail
npx --yes @stoplight/[email protected] lint specs/payments.yaml --fail-severity=error
docker run --rm -d --name prism -p 4010:4010 -v "$PWD/specs:/specs:ro" stoplight/prism:5.12.0 mock -h 0.0.0.0 --errors /specs/payments.yaml
sleep 2
curl -s -X POST http://localhost:4010/v1/payment_intents -H 'content-type: application/json' -d '{"amount": 1299}' | jq .
docker logs prism 2>&1 | grep -E 'violation|Request' | tail -4
docker rm -f prism >/dev/null
Expected bad output:
{
"type": "https://stoplight.io/prism/errors#UNPROCESSABLE_ENTITY",
"title": "Invalid request",
"status": 422,
"validation": [
{ "location": ["body"], "severity": "Error", "message": "Request body must have required property 'amount_cents'" },
{ "location": ["body"], "severity": "Error", "message": "Request body must have required property 'currency'" }
]
}
The mock is right: the client sends the old field name and omits a required field. Without --errors, Prism would have returned a plausible 200 and hidden the bug.
Root cause
A mock that returns fixed JSON for any request cannot tell the client it is wrong. Prism reads the OpenAPI document, finds the operation that matches the method and path, validates the request's parameters and body against the operation's schema, and generates a response from the operation's examples or, if none exist, from the response schema. With --errors, validation failures become 422 responses with a precise list of violations; without it, Prism logs them and still returns a success. Most "Prism returns 422 for everything" reports are therefore real contract mismatches — renamed fields, missing required properties, wrong content types — surfaced for the first time. The remainder are specification problems: a spec that marks a field required which the real API treats as optional, or an invalid document that Prism interprets differently from the author's intent, which is why the diagnostic lints the spec first.
Resolution
- Run Prism as a Compose service with the specification pinned in the repository:
services:
payments-mock:
image: stoplight/prism:5.12.0
command: ["mock", "-h", "0.0.0.0", "-p", "4010", "--errors", "/specs/payments.yaml"]
volumes:
- ./specs:/specs:ro
ports:
- "127.0.0.1:4010:4010"
web:
environment:
PAYMENTS_BASE_URL: http://payments-mock:4010
Fix the client to match the contract rather than loosening the mock. The 422 lists exactly which fields to change.
Select specific examples for specific tests. If the specification defines named examples, a test chooses one with the
Preferheader:
responses:
'200':
content:
application/json:
examples:
succeeded:
value: { id: pi_123, status: succeeded, amount_cents: 1299, currency: eur }
requires_action:
value: { id: pi_456, status: requires_action, amount_cents: 1299, currency: eur }
'402':
content:
application/json:
examples:
card_declined:
value: { error: { code: card_declined, message: Your card was declined. } }
#!/usr/bin/env bash
set -euo pipefail
curl -s -X POST http://localhost:4010/v1/payment_intents -H 'content-type: application/json' \
-H 'Prefer: code=402, example=card_declined' -d '{"amount_cents": 1299, "currency": "eur"}' | jq -c .
Use dynamic mode for varied data when tests need different values on each call (
mock -d), which generates responses from the schema with realistic random values instead of fixed examples.Proxy to the real sandbox to catch drift between the specification and the real API. In proxy mode, Prism forwards requests to the upstream and validates both requests and responses against the document:
#!/usr/bin/env bash
set -euo pipefail
docker run --rm -d --name prism-proxy -p 4011:4010 -v "$PWD/specs:/specs:ro" \
stoplight/prism:5.12.0 proxy -h 0.0.0.0 --errors /specs/payments.yaml https://api.stripe.com
PAYMENTS_BASE_URL=http://localhost:4011 STRIPE_API_KEY="${STRIPE_TEST_KEY:?set a test-mode key}" npm run test:smoke
docker logs prism-proxy 2>&1 | grep -iE 'violation' || echo "no contract violations"
docker rm -f prism-proxy
Prism passes the client's own Authorization header through to the upstream, so the smoke tests authenticate with a test-mode key exactly as they would without the proxy. Run the suite through the proxy on a schedule; any response violation means the specification pinned in the repository no longer describes the real API.
Expected output
$ curl -s -X POST http://localhost:4010/v1/payment_intents -H 'content-type: application/json' -d '{"amount_cents": 1299, "currency": "eur"}' | jq -c .
{"id":"pi_123","status":"succeeded","amount_cents":1299,"currency":"eur"}
$ curl -s -X POST http://localhost:4010/v1/payment_intents -H 'content-type: application/json' -H 'Prefer: code=402, example=card_declined' -d '{"amount_cents": 1299, "currency": "eur"}' | jq -c .
{"error":{"code":"card_declined","message":"Your card was declined."}}
Valid requests get contract-conforming responses, specific scenarios are selectable per test, and invalid requests fail loudly with the violated rule.
Because the mock is generated, updating it is a matter of updating the pinned specification. When the providing team publishes a new version, drop it into specs/, restart the service and run the client's tests: any breaking change shows up immediately as 422s or failed assertions, days before the real service is deployed. That is the main reason frontend and backend teams adopt contract-generated mocks — they can build in parallel against the same document and discover disagreements early.
Prevention
Lint the specification in CI with Spectral so an invalid document never reaches the mock.
Pin the specification version in the repository and update it through pull requests, so a provider change becomes a reviewable diff and CI shows which client code breaks.
Add examples for every error the client handles. A declined card, a rate limit and a validation error each need a named example so tests can select them.
Platform caveats
Apple Silicon (ARM64):
stoplight/prismimages are multi-arch. The npm package (@stoplight/prism-cli) also runs natively if Docker is not wanted for this.
macOS (Docker Desktop): mounting a large specification directory is fine; Prism reads the document once at startup. Restart the container after editing the specification.
WSL2: host-side tests reach Prism at
localhost:4010through the published port; containers usepayments-mock:4010.
Rollback
Remove the service and point the base URL at the sandbox or back at hand-written stubs:
#!/usr/bin/env bash
set -euo pipefail
docker compose rm -sf payments-mock
sed -i.bak 's|^PAYMENTS_BASE_URL=.*|PAYMENTS_BASE_URL=http://localhost:8089|' .env
Frequently Asked Questions
Why does Prism return 422 for my requests?
Because they violate the OpenAPI document — a missing required field, a wrong type or a renamed property. The response body lists each violation. Fix the client, or fix the specification if it is wrong, rather than removing --errors.
How do I get a specific error response from Prism?
Define a named example for that status in the specification and send Prefer: code=402, example=card_declined. Without examples, Prefer: code=402 returns a response generated from the 402 schema.
Can Prism keep state between requests?
No. Prism is stateless: creating a resource and then fetching it returns unrelated data. For flows that need state, use WireMock scenarios or a lightweight fake service.
Where do we get the provider's OpenAPI document?
Many providers publish one (Stripe, GitHub, Twilio). For internal services, the providing team should publish it as a build artefact. Pin a copy in the consuming repository and update it deliberately.