Every API spec on Outworx Docs gets a free hosted mock URL the moment you upload it. Point your client at that URL and the responses come from the spec itself — examples, schemas, GraphQL types — so frontend work, integration tests, and SDK demos can run before the real backend exists. No CLI, no proxy, no separate "mock service" subscription.
This guide is the full reference: URL conventions, the Prefer header, stateful overrides, GraphQL support, per-version routing, the activity feed, and the diagnostic headers we ship with every response.
Prerequisites
- An Outworx Docs project with at least one OpenAPI 3, Swagger 2, or GraphQL SDL spec uploaded
- The project must be public and not password-protected — the mock URL inherits the same access gate as the rendered docs
If your project is private the mock route returns 404 to anonymous traffic. Flip Make documentation publicly accessible under Settings → General to enable mocking.
Step 1: Find your mock URL
Open any project in the dashboard and click the Mock tab. The Overview sub-tab shows the base URL for your project:
https://docs.outworx.io/mock/<your-slug>For an OpenAPI/Swagger spec, append the path you'd hit on the real API:
curl https://docs.outworx.io/mock/acme/users/42For a GraphQL spec, post to /graphql:
curl https://docs.outworx.io/mock/acme/graphql \
-H 'content-type: application/json' \
-d '{"query":"{ user(id: \"42\") { id name } }"}'GraphQL also accepts GET ?query=... for quick browser testing.
Step 2: How responses are synthesised
The mock engine walks the spec to build a response, in this priority order:
- A matching stateful override (covered in step 4).
- A named example from the operation's
examplesmap — picked byPrefer: example=<name>,random, orsequential. - The first
examplefield declared on the response or schema. - A schema walk — every property gets a sensible value from its type, format, enum, or pattern.
$ref,allOf, andoneOfare resolved. - An empty body if the response defines no schema (e.g.
204).
Every response carries an X-Mock-Synth header that tells you which path was taken: override, example:<name>, example:random, example:sequential, schema, or empty. When a request misses the spec entirely you get X-Mock-Synth: fallback with a 404.
Step 3: Steer responses with the Prefer header
RFC 7240 defines Prefer as a way for clients to nudge servers — we use it as the steering wheel for mock responses:
| Header | Effect |
|---|---|
Prefer: code=404 | Return the operation's 404 response instead of its 200. |
Prefer: example=randomized | Pick a random named example each call (see note below). |
Prefer: example=sequential | Round-robin through the examples — handy for pagination tests. |
Prefer: example=user-not-found | Return the example named user-not-found. |
Stack them with commas: Prefer: code=400, example=invalid-email.
The sequential counter is per (project, version, path, method, status) and stored in Redis with a 24-hour TTL — restart-safe across deploys without leaking state forever.
A note on random: with five examples you'd expect even distribution, but small-sample runs of three or four calls regularly bunch up. That's normal — the rolling distribution evens out across ~30 calls.
Step 4: Stateful overrides
Examples are static. Real apps need to demo states — "what does the UI look like when the user is rate-limited?" or "what happens if the order is in pending_review?" The Mock → Overrides sub-tab is where you set those up.
An override is a rule: match these requests, return this response. Each override lives at a specific endpoint (or * to match across all of them) and has:
- Status code and response body (any JSON).
- Latency in ms — useful for testing loading states.
- Priority — higher numbers win when multiple overrides match. Ties break by most-recently-updated.
- Optional matchers: query parameters, request headers, request body shape, GraphQL operation name.
- Optional version scoping — restrict an override to one version of the spec.
The matcher set is deeply partial: { "user": { "role": "admin" } } matches any body that contains user.role === "admin", regardless of what else is there. Headers and query strings work the same way. First match wins, sorted by priority desc.
The fastest way to create one is the Duplicate button on an existing override — clones every field including matchers so you can tweak one knob without re-typing the rest.
When an override fires the response includes X-Mock-Synth: override, X-Mock-Override-Id: <uuid>, and the body you defined. The activity feed (next step) tags the request with the override's name so you can audit which rule ran.
Step 5: Dynamic templating in override responses
Static overrides only get you so far. The moment you need a fresh UUID per call, the current timestamp, or to echo back the user's email from the request body, a hand-rolled mock service starts to look attractive. Outworx ships a built-in templating engine so you don't have to.
Anywhere you can put a string in an override — the response body, a response header value, the Content-Type — {{...}} placeholders get rendered just before the response goes out the wire. Syntax is Mustache-style so it lines up with what you'd write in Postman, Handlebars, or any other tool you've used.
{
"id": "{{$uuid}}",
"email": "{{request.body.email}}",
"createdAt": "{{$now}}",
"score": "{{$randomInt:0:100}}"
}Identifier and time tokens:
{{$uuid}}/{{$guid}}— fresh UUID v4 per request{{$now}}— current ISO 8601 timestamp{{$timestamp}}— current Unix seconds{{$timestampMs}}— current Unix milliseconds{{$randomDate}}/{{$randomPastDate}}/{{$randomFutureDate}}—YYYY-MM-DDwithin ±1 year
Numbers:
{{$randomInt}}— integer 0–1000{{$randomInt:1:100}}— integer in your range{{$randomFloat}}— float 0–1000 with two decimals{{$randomBool}}—true/false
Realistic fakes:
{{$randomEmail}}, {{$randomFirstName}}, {{$randomLastName}}, {{$randomFullName}}, {{$randomUserName}}, {{$randomPhone}}, {{$randomUrl}}, {{$randomIp}}, {{$randomColor}}, {{$randomCity}}, {{$randomCountry}}, {{$randomCompany}}.
Request echoing:
{{request.method}}—GET,POST, etc.{{request.path.id}}— for an override on/users/{id}, this resolves to whatever the caller put in the URL{{request.query.page}}— value of?page=…{{request.headers.authorization}}— case-insensitive header lookup{{request.body.email}}— dotted path into the parsed JSON body, including arrays ({{request.body.items.0.id}})
If a placeholder resolves to a number or boolean we strip the surrounding quotes, so "score": "{{$randomInt}}" renders as "score": 42 (a real number) not "42" (a string). JSON stays valid.
Unknown tokens, missing request fields, and non-JSON request bodies all degrade gracefully — the placeholder is left in the output untouched, so a typo shows up as a literal {{$mystery}} in the response and you can fix it without re-running anything.
The override editor in the dashboard has an Insert template button next to the Response body label. Click it and you get every available token grouped by category with a one-line description — pick one, it's appended to your body.
Step 6: Strict request validation
Mocks are usually permissive — anything goes in, you get a sensible response back. That's great for prototyping and bad for catching contract bugs. Outworx Docs lets you flip a switch and turn the mock strict.
Toggle Strict request validation on the Mock Server Overview tab and every incoming request gets validated against the matched operation's spec — required path / query / header parameters, JSON request body schema. Anything off-spec gets a 400 with field-level error details:
{
"error": "Request failed strict validation against the spec.",
"matchedPath": "/users",
"operationId": "createUser",
"errors": [
{ "field": "body.email", "code": "required", "message": "Missing required field 'email'." },
{ "field": "body.age", "code": "type", "message": "Expected integer, got string." },
{ "field": "query.limit", "code": "maximum", "message": "Value must be ≤ 100." }
],
"hint": "..."
}What's checked:
- Type —
string/integer/number/boolean/object/array/null. OpenAPI 3.1's type arrays (["string", "null"]) and 3.0'snullable: trueboth work. - Required fields — on the operation parameters and inside object schemas.
- Enum / const — value must match.
- String constraints —
minLength,maxLength,pattern,format(uuid, email, uri, date, date-time, ipv4, ipv6). - Number constraints —
minimum,maximum,exclusiveMinimum,exclusiveMaximum,multipleOf. - Array constraints —
minItems,maxItems,uniqueItems, plus per-item validation throughitems. - Object constraints —
properties,additionalProperties: false(rejects extras),additionalProperties: <schema>(validates extras). - Combinators —
allOf(all must pass),oneOf(exactly one),anyOf(at least one).$refresolves recursively with cycle protection.
Path / query / header params are coerced from strings to the spec's declared type before validation, so ?limit=50 against a spec that says type: integer validates as 50 (integer), not as "50" (string). Mirrors how Express, FastAPI, and similar frameworks read params.
Stateful overrides skip validation. An override is the owner saying "I know what I'm doing for this request shape" — bypassing validation lets you demo error states (400 with a custom shape, 422 with field errors) without the validator overriding your override.
Failed requests come back with X-Mock-Synth: validation-error and land in the activity feed so spec-mismatch traffic is visible in your usage stats.
Step 7: Auth enforcement
The default mock is open — anyone can hit any endpoint without auth, even endpoints your spec marks as secured. That's fine for prototyping, terrible for catching the most common frontend bug class: the SDK silently dropping the auth header.
Toggle Auth enforcement on the Mock Server Overview tab and the route reads your spec's security blocks (components.securitySchemes on OpenAPI 3, securityDefinitions on Swagger 2) and rejects requests missing the required auth:
401 Unauthorized
WWW-Authenticate: Bearer realm="mock"
{
"error": "Mock authentication required.",
"matchedPath": "/users/{id}",
"operationId": "getUser",
"schemes": [
{ "name": "bearerAuth", "type": "http-bearer", "hint": "Send header Authorization: Bearer <token>" },
{ "name": "apiKeyAuth", "type": "apikey", "hint": "Send header X-API-Key: <api-key>" }
]
}What's enforced:
- API key — in
header,query, orcookie. Header lookup is case-insensitive; cookie lookup parses theCookie:header. - HTTP Bearer —
Authorization: Bearer <non-empty token>. - HTTP Basic —
Authorization: Basic <non-empty credentials>. - OAuth2 / OpenID Connect — same as Bearer, conventionally.
- Other HTTP schemes (Digest, Negotiate, …) — accept any non-empty
Authorizationheader. The mock isn't going to validate the crypto.
The check is presence-only. The mock doesn't validate that an API key matches a real value — it has no notion of "real" credentials. But rejecting zero-byte tokens up front catches the bug everyone has shipped at least once: the SDK is "authenticated" but not actually attaching the header.
Per-operation security overrides the spec-level default. Empty security: [] on an operation explicitly opts that operation out — useful for /health, /auth/login, etc.
Stateful overrides skip auth enforcement. An override is the owner's deliberate "return this exactly" path; second-guessing it would defeat the point.
Failed requests get X-Mock-Synth: auth-required and land in the activity feed so you can see exactly which endpoints integrators are hitting without auth.
Step 8: Network simulation
Real APIs are slow sometimes and broken sometimes. Default mocks are neither, which means frontend code that handles "network is being weird right now" is the most under-tested code in the codebase. Network simulation fixes that.
The Mock Server Overview tab has a Network simulation card with four knobs:
- Min latency (ms) and Max latency (ms) — every request sleeps a uniform-random amount in this band before responding.
0–0is off;200–800simulates a flaky cellular connection;100–200simulates "looks fast but is far away". - Chaos rate (%) — fraction of requests forced to fail.
0is off,5is "occasional flake",25is "your tests should handle this". - Error status — what status to emit on chaos. Defaults to
503. Any 4xx or 5xx works.
Latency 200–800ms · Chaos 5% · Error 503Stateful overrides and strict-validation 400s bypass the simulator. An override is the owner's deliberate "return this exactly" — randomly breaking it would defeat the point. Same for validation errors: they're precise signals the frontend dev needs to see.
Diagnostic headers tell you exactly what happened:
X-Mock-Chaos-Latency-Ms: 437— how many milliseconds of injected delay this request waitedX-Mock-Synth: chaos-error— appears on responses where chaos rolled an error instead of returning the spec body
Chaos failures land in the activity feed with synth: chaos-error so usage stats keep simulation noise separate from real spec activity.
Step 9: Import / export override bundles
Once you've spent time tuning a set of overrides on staging, you don't want to re-click them all into prod. Hit Export on the Overrides sub-tab and you get a single JSON file with every rule on the project — match config, response config, templating tokens, GraphQL operation matchers, the whole shape. Project-local IDs and timestamps are stripped so the bundle imports cleanly into any other project. The filename looks like mock-overrides-a91f0c2d-2026-05-02.json — a stable project prefix plus the export date.
Import asks merge-or-replace:
- Merge — appends the imported rules alongside whatever's already there. Great for layering staging-only debug rules on top of a production set without losing the production rules.
- Replace — wipes every existing override on the project, then imports. Great for "make staging match prod exactly".
Every imported rule is normalised through the same validation as a manually-created override, so a hand-edited bundle that tries to smuggle reserved headers or oversized bodies in gets rejected per-row. Invalid rules are reported in the import toast; the rest import successfully. If merge would push you past the 50-override cap, the import refuses up-front instead of partially applying.
The bundle format is versioned ({ version: 1, exportedAt, overrides: [...] }) so future schema changes can add a v2 codec without breaking existing exports — the importer dispatches on the version field.
Step 10: GraphQL overrides
GraphQL doesn't have paths, so the override matcher takes a match_graphql_operation field instead. Set it to the operation name (query MyQuery, mutation CreatePost) and any request whose operationName matches gets the override response.
Combine that with match_body to match on variables: an override with match_graphql_operation: "GetUser" and match_body: { "variables": { "id": "42" } } only fires for that specific user lookup.
If no override matches, the GraphQL executor runs against the SDL — fields resolve to type-appropriate defaults (string, int, bool, enum first value, list of one) and your selection set is honoured. Custom scalars come back as strings unless you've put an example directive on them in the SDL.
Step 11: Multi-version projects
If every version of your project uses the same kind of spec (all OpenAPI, all Swagger, all GraphQL) the base URL https://docs.outworx.io/mock/<slug> resolves to the default version — there's only one route to remember.
If you mix REST and GraphQL versions, the base URL splits per-version because the routing differs:
https://docs.outworx.io/mock/<slug>/v2/users/42— REST versionv2https://docs.outworx.io/mock/<slug>/v1/graphql— GraphQL versionv1
The Overview sub-tab shows exactly which URLs you have. Hit the wrong one and we return a 400 with a hint and an alternativeVersions list pointing at the right format-aware URL.
Step 12: Disable specific operations
Sometimes you want most of your API mocked but a handful of dangerous endpoints (anything that would hit Stripe, send mail, or charge a card) to come back 501 instead. The Endpoints sub-tab lists every operation with a per-version toggle — flip the switch and that operation returns 501 Not Implemented with X-Mock-Synth: disabled.
Disabled state is per-version — POST /payments/charge can be mocked in v1 and disabled in v2.
Step 13: Watch the activity feed
Every mock request is logged: method, path, status, latency, override id (if any), X-Mock-Synth value, and the requesting IP's country. The Activity sub-tab shows the last 200 in real-time. The Overview sub-tab shows a 7-day usage summary so you can spot when traffic spikes — useful when an integration partner starts hammering your mock instead of the real API.
Step 14: Plan limits
Limits are enforced against the project owner's plan, not the requesting collaborator's — a Free user collaborating on a Business project gets the Business caps; a Business user collaborating on a Free project is gated to Free.
| Free | Pro ($9/mo) | Business ($19/mo) | |
|---|---|---|---|
| Mock requests / month | 10,000 | 100,000 | Unlimited |
| Stateful overrides per project | 5 | 25 | 50 |
| Activity log retention | 24 hours | 14 days | 30 days |
| Auth enforcement | ✅ | ✅ | ✅ |
| Strict request validation | ✅ | ✅ | ✅ |
| Dynamic templating in overrides | ✅ | ✅ | ✅ |
| Network simulation (chaos) | — | ✅ | ✅ |
| Override import / export bundles | — | ✅ | ✅ |
Hitting the monthly request cap returns 429 Too Many Requests with a Retry-After header counting down to the start of the next UTC month. The override count is enforced on create, duplicate, and import — going over returns a clear "limit reached" error with the cap and the plan name. Pro+-only features (network simulation, import/export) reject from server actions on Free with an upgrade message; the dashboard UI hides or locks the controls.
Free's correctness features (auth enforcement, strict validation, templating) are intentionally always-on — without them the mock isn't realistic enough to catch frontend bugs, which is the whole point. Plan tiering kicks in for workflow features (network simulation for stress-testing, import/export for staging↔prod parity) and scale (request count, override count, log retention).
Try It panel — running mocks from the docs page
The Try It panel at the top of every endpoint reference page now has a Mock toggle. Flip it on and the request goes to your mock URL with all your Prefer headers attached. The response panel shows the X-Mock-Synth value as a coloured badge so readers can see at a glance whether they hit a real example, a synthesised schema, or a custom override.
GraphQL operations get the same treatment via a query/variables/operation-name editor — same toggle, same badge. It's the fastest way to demo "here's what this endpoint returns" without leaving the docs page.
Diagnostic headers reference
| Header | Meaning |
|---|---|
X-Mock-Synth | override, example:<name>, example:random, example:sequential, schema, empty, disabled, fallback |
X-Mock-Override-Id | UUID of the override that fired (only when synth=override) |
X-Mock-Version | Which version of the spec served the response |
X-Mock-Operation-Id | The operation id from the spec — handy for log correlation |
X-Mock-Latency-Ms | Server-side latency including any latency_ms from an override |
CORS is wide open (Access-Control-Allow-Origin: *, no credentials) — the mock URL is meant to be hit from any browser, frontend dev server, or Postman tab. The threat model is the same as the public docs: nothing private goes through it, no auth, no shared state across projects.
What this replaces
If you were running Prism locally, paying Apidog for hosted mocks, or maintaining a mocks/ directory of hand-written Express handlers — this is the integrated version. Spec uploads become live mock URLs automatically, overrides give you the dynamic-state escape hatch when examples aren't enough, and the activity feed plus Try It integration mean the docs and the mock are the same surface.
Open any project's Mock tab to grab your URL. The first request takes about 30ms and you're already mocking.
Related guides
Outworx REST API — Manage Projects from CI
Drive every Outworx Docs action from a script: create projects, push spec versions, promote a default. Personal access tokens, copy-paste curl, and the full OpenAPI spec.
Outworx CLI — push specs from anywhere
Publish OpenAPI / Swagger / GraphQL specs to Outworx Docs from your laptop or any CI provider. One command, zero install graph, scriptable exit codes.
PR preview deploys — every pull request gets its own docs URL
Push specs from CI with `--preview` and Outworx spins up a throwaway version with a banner showing PR / branch / commit. 14-day rolling TTL, hidden from public version listings, three lines of GitHub Action.
Ship your API docs in under a minute.
Upload your OpenAPI, Swagger, or GraphQL spec and get beautiful hosted docs with AI chat and a per-project MCP server — free forever for 2 projects.