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:

  1. an HTTP Trigger (the entry — and the whole API surface). Its Routes table — GET /users/{id} → port getUser — gives every endpoint its own labelled output port, with template parameters extracted onto the payload as pathParams. 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 optional unauthorized output, which you wire to a 401 response. No auth node, no router node; the trigger does both,
  2. one branch per endpoint — typically a Set node shaping the synthetic response with {{ }} expressions ("id": "{{ $json.pathParams.id }}"), but any nodes you like,
  3. a shared output.http-response 200 the branches feed — and proper 4xx pages off the trigger’s unmatched port: everything unclaimed leaves there with $json.methodMismatch set to true when 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:

MockAnswers
mock:gmailmessage list/get/send, labels, profile
mock:google-sheetsvalues.get, values.append, spreadsheet metadata
mock:discordchannel lookup, message list/post
mock:authPOST /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.