Mock APIs
Building a workflow against a real API means credentials, rate limits, and side effects — none of which you want while designing. Flowdrome lets you swap any API for a mock that is itself a workflow: no real keys, deterministic synthetic data, and full visibility (every mock call is an ordinary run you can inspect).
The fastest way: ask an AI for it
The Copilot (or any MCP-connected AI) builds a
working mock from one sentence — the create_api_workflow tool takes an endpoint list and
assembles a routed HTTP trigger, a response per route, and a 404 for everything unmatched:
“create a mock api named time with two endpoints: /time returns the current time and /timeutc returns it in utc format”
The result serves immediately at /api/mock/time/time and /api/mock/time/timeutc (and as
mock:time/… from other workflows) — and the tool’s reply quotes the real URLs, string
bodies may compute values with {{ }} expressions, and unmatched paths answer a clean 404.
Everything below explains what that tool builds, for when you want to shape it by hand.
The mock: URL scheme
Anywhere a node takes a URL, a URL starting with mock: reroutes to a mock workflow
instead of the network:
mock:gmail/users/me/messages
mock:petstore/pet/10?verbose=1
mock:name/path runs the workflow registered as mock name and answers with whatever its
output.http-response node produced. Swap it back to the real https://… URL when you’re
done designing — nothing else about the node changes. The editor’s test engine provides the
mock host automatically; the SSRF guard still applies to every real URL.
A mock is just a workflow
Any workflow named Mock · <name> is a mock (the prefix and punctuation are forgiving —
mock:petstore finds “Mock · Petstore”). The canonical shape is all real nodes:
- an HTTP Trigger (the entry — and the whole API surface). Its Routes table —
GET /users/{id}→ portgetUser— gives every endpoint its own labelled output port, with template parameters extracted onto the payload aspathParams. Its Authentication property gates every endpoint exactly like a real webhook — set it to Bearer and a request with a bad/missing token leaves the trigger’s optionalunauthorizedoutput, which you wire to a 401 response. No auth node, no router node; the trigger does both, - one branch per endpoint — typically a Set node shaping the synthetic response with
{{ }}expressions ("id": "{{ $json.pathParams.id }}"), but any nodes you like, - a shared
output.http-response200 the branches feed — and proper 4xx pages off the trigger’sunmatchedport: everything unclaimed leaves there with$json.methodMismatchset totruewhen a known path was hit with the wrong verb, so an If splits it into a 405 versus a real 404 error body ({ "error": { "code": 404, … } }).
The request reaches the trigger as the same content wrapper a real served webhook delivers:
the parsed body is the content ($json), and the request metadata rides the envelope —
request.method, request.path, headers.*, query. Routing reads request.method /
request.path, so a mock you design routes identically if you later deploy it for real — no
mock-only shape to unlearn. The trigger preserves that envelope through the hop, so a per-route
auth Check downstream can still read headers.authorization. Because
every endpoint is a visible branch, the mock teaches the pattern —
and you reshape it like any workflow: add latency with a Wait node, fail every third call with
an If, return a 429 to test your retry logic. A mock with no http-response node answers 200
with the run’s final output — the zero-ceremony version.
Mocking a login API
Mock · Auth ships the pattern: POST /oauth/token is open and answers with real-shaped
auth data (access_token, token_type: "Bearer", expires_in, refresh_token), while the
protected endpoints (GET /users/me, POST /oauth/revoke) each guard their own branch with a
Check node matching the exact issued token — right token → data, anything else → 401:
POST mock:auth/oauth/token → 200 { "access_token": "mock-access-token-2026", … }
GET mock:auth/users/me (Authorization: Bearer mock-access-token-2026) → 200 { "email": "ada@example.com", … }
GET mock:auth/users/me (no/wrong token) → 401 { "error": { "status": "UNAUTHENTICATED", … } }
DELETE mock:auth/oauth/token → 405 { "error": { "status": "METHOD_NOT_ALLOWED", … } }
GET mock:auth/definitely/not → 404 { "error": { "status": "NOT_FOUND", … } }
The OpenAPI mock wizard
Workflows → New mock API takes an OpenAPI (JSON) spec — a URL or pasted — and generates the
whole mock for you as a real graph: every operation in the spec becomes a route on the HTTP
Trigger with its own output port, feeding a Set branch that answers with synthetic data derived from
its response schema (examples and enums honored, format: email/date-time/uuid faked
sensibly, path parameters overlaid, so GET /pet/10 answers with id: 10). Wrong verbs answer
405, unknown paths a proper 404 error body — and a spec that declares security gets a
Check auth gate answering 401 until the caller sends an Authorization header. Open
the generated workflow and every endpoint is a branch you can reshape on the canvas.
GET mock:petstore/pet/10 → { "id": 10, "name": "doggie", "status": "available", … }
PUT mock:petstore/pet/10 → 405 { "error": { "status": "METHOD_NOT_ALLOWED", … } }
GET mock:petstore/definitely/not → 404 { "error": { "status": "NOT_FOUND", … } }
Prebuilt mocks: Gmail, Google Sheets, Discord, Auth
npm run seed:mock-apis (included in seed:all) installs ready-made mocks — enough surface to
exercise the matching nodes with zero real access. Like the real APIs, the three service mocks
gate on the trigger’s Bearer auth — send Authorization: Bearer mock-token and answer 401
without it:
| Mock | Answers |
|---|---|
mock:gmail | message list/get/send, labels, profile |
mock:google-sheets | values.get, values.append, spreadsheet metadata |
mock:discord | channel lookup, message list/post |
mock:auth | POST /oauth/token login → auth data; token-protected profile/revoke |
The tier-4 Google nodes read their API base from FLOWDROME_*_API_BASE environment seams — point
FLOWDROME_GMAIL_API_BASE at http://127.0.0.1:48170/api/mock/gmail and the Gmail node itself
runs against the mock, untouched — give its credential the token mock-token and it passes the
trigger’s Bearer gate.