The free tiers are a real product — projects, endpoints, saved responses. Pro is for rehearsing the things that go wrong, and for mirroring an API large enough to matter.
Make an API fail, on purpose
Name a set of responses — "outage", "rate limited", "expired token" — and flip your whole project into it. Your error handling finally gets exercised, without editing a single endpoint or waiting for the real API to have a bad day.
curl https://apipreflight.io/m/abc123/orders \
-H "X-Mock-Scenario: outage"
Switch from the dashboard, or per request from a test — so two people can drive different scenarios against the same project at once.
A different answer on each call
A scenario can be an ordered sequence: the first call times out, the second returns 500, the third succeeds. That is the retry-and-backoff path you cannot test against a real API, and the reason this product exists.
call 1 → 408 Request Timeout
call 2 → 500 Server Error
call 3 → 200 OK ← holds here
Holds on the last step so a failure stays reproducible. ?_reset=1 starts over.
Save the arrangement, not just the scenario
A real rehearsal is rarely one endpoint failing. Pin several to different scenarios — payments timing out while search is fine — then save that whole arrangement under a name and put the project back into it whenever you want. A batch is the set of pins, not a copy of the responses, so editing a response afterwards changes what the arrangement plays.
payments → "timeout"
search → base
auth → "expired token"
save as "checkout down"Apply it from the dashboard or from a test, and everything moves together.
Write a response once, use it everywhere
The same 429, the same expired-token body, the same empty-list shape — every project grows a handful of responses it uses again and again. Save them to a reusable library and drop one into any endpoint instead of retyping it, or worse, retyping it slightly differently. A free account gets a few; Pro is unlimited.
rate limited → 429 {"retryAfter":30}
expired token → 401 {"error":"expired"}
empty list → 200 []Applying one fills the form, so you can still change it for this endpoint.
The agent writing your code can drive the mock
Connect Claude, Cursor or VS Code to a project and it can read what the endpoints return, create new ones, flip a scenario mid-test and read the request log — while it writes the code that calls them. It is the difference between describing your mock to an assistant and handing it the controls.
you → "make /orders fail, then test"
agent → set_mock_scenario("outage")
agent → runs your suite, sees it fail
agent → read_request_log()
← what your code really sentOver MCP, the protocol these tools already speak. Each project has its own URL and a connection reaches that project only: add the URL in claude.ai and approve a sign-in screen — no token to paste — or paste a token into Claude Code, Cursor, VS Code or a CI job. Connections are listed on the project page and can be cut off there at any time.
Your build can make its own mock
Create a mock at the start of a CI run, define exactly the endpoints your suite needs, flip a scenario to exercise the retry path, assert what your code actually sent, then throw it away. No shared fixture drifting between branches, and no two builds fighting over one mock.
POST /api/mocks → a mock
POST …/endpoints → your API
npm test → against it
PATCH … activeScenario → outage
DELETE /api/mocks/<id> → gone
A token reaches the project it was minted in and throwaway mocks it creates itself — it cannot touch your other projects or delete the one it belongs to, which is what makes it safe in a CI secret. Throwaways expire on their own, so a failed run leaves nothing behind. The full OpenAPI spec is published at /api/openapi/management.
Bodies that look like real data
Generate a list instead of copying one row ten times — with names, emails, prices, UUIDs and timestamps that differ every call. Each row is internally consistent, so a person’s email matches their name.
{{#repeat 10}}
{
"id": "{{uuid}}",
"name": "{{faker.name.fullName}}",
"email": "{{faker.internet.email}}",
"price": {{faker.commerce.price(5,80)}}
}
{{/repeat}}Click tokens in from a palette rather than memorising them.
A login that really asks for the six digits
Two endpoints: the first hands back a QR code your authenticator app scans, the second checks the digits it shows. It is checked per email — the address someone signs in with is the one the code is verified against — so the enrolment screen, the code screen and the wrong-code path can all be built and exercised before any of it exists for real.
POST /login
→ { "qr": "data:image/png;base64,…",
"secret": "JBSWY3DP…" }
POST /verify { "email": "…", "code": "314159" }
→ 200 if that is what the app is showing now
→ 401 { "reason": "wrong" } if it is notStandard TOTP — 6 digits, 30 seconds, one step of drift allowed either way — so a real authenticator app works. A refusal says which check failed and where it looked, and never echoes the secret or the correct code. For an automated test, {{totp.code}} renders the digits that are valid right now, so there is no phone in the loop.
POST a record, then find it in the next GET
Stateful endpoints. Switch it on for an endpoint and writes stick: a POST is stored, a GET lists it, PUT and PATCH change it, DELETE removes it. A screen that creates something and navigates back to the list finds it there — which is the first thing anyone building against a backend that does not exist yet tries, and the moment a stateless mock stops being able to play along.
POST /products {"name":"Chair"}
→ 201 {"id":"a1f…","name":"Chair"}
GET /products
→ [ …, {"id":"a1f…","name":"Chair"} ]
GET /products/a1f… → the one you just created
DELETE /products/a1f… → and now it is gonePair /products with /products/{id} and they share a collection automatically — there is no field to wire up. The response body you designed seeds it, so a list of five starts as five rather than empty. Each caller gets their own copy, so two people testing against the same project never see each other’s writes.
An AI reply that breaks halfway through, whenever you ask
Streamed responses. Any signed-in plan can stream a reply word by word, then send the product cards and buttons your chat screen renders. Pro adds the failures real AI backends produce at the worst moment — the reply cut off mid-sentence, an error after the third word, a stream that stalls — and can stream in OpenAI or Anthropic format, so the official SDKs read it unchanged.
POST /chat {"message":"dinner ideas?"}
→ data: {"type":"text","delta":"Here "}
→ data: {"type":"text","delta":"are "}
… a word every 40ms
→ event: card {"title":"Lemon chicken"}
→ data: [DONE]
scenario "dies-halfway":
→ the same reply stops after six wordsYour app’s Stop button shows up in the request log too: "client stopped after 7 of 12".
Mock only what does not exist yet
Proxy passthrough. Point an endpoint at your real API and it forwards the request — path, query, headers and all — and returns what actually comes back. Mock the two endpoints that are not built; let everything else hit the real thing.
GET /orders/{orderId}
→ mocked, returns your fixture
GET /customers/{id}
→ forwarded to https://api.yourco.comhttps only, and private or internal addresses are refused.
Start from your OpenAPI spec, and hand it back
Paste a spec — JSON or YAML — and get a working project, with example bodies built from its schemas when it has no examples of its own. Export any project, or your whole account as one document, to share with the people writing the real API.
Exports are valid OpenAPI 3.1 and carry your scenarios and sequences, so a colleague importing the file rebuilds the whole project — not a hollow copy of its paths.
Your own address, not ours
Serve a project from a subdomain of your own — yourteam.apipreflight.io — or from a domain you own, mock.yourcompany.com. The hostname identifies the project, so the URLs your app calls carry no /m/{id} prefix, and the HTTPS certificate is issued and renewed for you.
every project, free
https://apipreflight.io/m/abc123/users/1
your subdomain
https://yourteam.apipreflight.io/users/1
your own domain
https://mock.yourcompany.com/users/1
Your own domain takes one TXT record to prove it is yours and one CNAME to point it here — both shown in the dashboard with copy buttons, and the status updates itself while you wait.
A request log you can actually review
Every call to your project is recorded: method, path, query, headers (credentials redacted), body, the status returned and how long it took — plus which scenario and which sequence step served it, so a failing test on call 3 is explainable. Pro keeps 500 recent calls instead of 25, and exports them as CSV or JSON.
Kept for 30 days. Response bodies are not stored — see the privacy policy.
Room to work
200 endpoints per project instead of 10, and unlimited projects instead of three — enough to mirror a real API surface rather than a corner of one.