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.

How Prism Answers a Request Flow from an incoming request through validation against the specification to a generated response. How Prism Answers a Request request arrives POST /v1/… validate request against spec pick response example or schema validate response then return
With --errors, contract violations become local 422s instead of silent passes.

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

  1. 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
  1. Fix the client to match the contract rather than loosening the mock. The 422 lists exactly which fields to change.

  2. Select specific examples for specific tests. If the specification defines named examples, a test chooses one with the Prefer header:

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 .
  1. 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.

  2. 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.

Static Examples vs Dynamic Generation Comparison of Prism's static example mode and dynamic generation mode. Static Examples vs Dynamic Generation static examples dynamic mode (-d) same response every call new values each call exact values in asserts schema-shaped only Prefer selects example random but valid needs examples in spec works from schema alone
Static examples suit assertions on exact values; dynamic mode suits exploratory UI work.

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

  1. Lint the specification in CI with Spectral so an invalid document never reaches the mock.

  2. 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.

  3. 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.

Prism Modes and When to Use Them Table of Prism modes with what they return and the typical use case. Prism Modes and When to Use Them Mode Returns Use for mock examples tests with exact asserts mock -d generated data UI development mock --errors 422 on violations client contract checks proxy --errors real upstream spec drift detection
Mock mode for daily development; proxy mode on a schedule to validate the specification itself.

Platform caveats

Apple Silicon (ARM64): stoplight/prism images 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:4010 through the published port; containers use payments-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.