Counterfact
OpenAPI / Swagger to TypeScript generator and mock server
Install / Use
/learn @pmcelhaney/CounterfactREADME
Counterfact instantly turns an OpenAPI/Swagge spec into a live, working API you can run locally.
Instead of waiting for a backend—or wiring up brittle mocks—it generates a server where every endpoint is backed by TypeScript code. Responses are valid by default, but fully customizable, and the system is stateful, interactive, and hot-reloading.
It’s not just a mock server.
It’s a controllable API environment you can shape in real time.
Built by Patrick McElhaney · Currently available for the right opportunity → https://patrickmcelhaney.org
Quick Start
npx counterfact@latest https://petstore3.swagger.io/api/v3/openapi.json api
That's it. Counterfact reads your OpenAPI spec, generates TypeScript route files in api/, and starts a mock server — all in one command. Point it at your own spec instead of the Petstore whenever you're ready.
Requires Node ≥ 17.0.0
Features
- ⚡ Zero config — one command to generate and start a simulated api
- 🔒 Type-safe by default — route handlers are typed directly from your OpenAPI spec
- 🔄 Hot reload — edit route files while the server is running; state is preserved
- 🧠 State management — POST data and GET it back; share state across routes with context objects
- 🖥 Live REPL — inspect and modify server state from your terminal without touching files
- 🔀 Hybrid proxy — route some paths to the real API while mocking others
- 🎲 Smart random data — uses OpenAPI examples and schema metadata to generate realistic responses
- 📖 Built-in Swagger UI — browse and test your mock API in a browser automatically
- 🔌 Middleware support — add custom middleware with
_.middleware.tsfiles
How It Works
- Generate — Counterfact reads your OpenAPI spec and creates a
routes/directory with a.tsfile for each path, plus atypes/directory with fully typed request/response interfaces. - Customize — Edit the route files to return exactly the data your frontend needs. The full power of TypeScript is at your disposal.
- Run — The server hot-reloads on every save. No restart, no lost state.
Examples
Zero effort: random responses out of the box
Generated route files return random, schema-valid responses immediately — no editing required.
// mock-api/routes/store/order/{orderID}.ts
import type { HTTP_GET } from "../../../types/paths/store/order/{orderId}.types.js";
export const GET: HTTP_GET = ($) => {
return $.response[200].random();
};
Typed custom responses
Replace .random() with .json() to return specific data. TypeScript (via your IDE's autocomplete) guides you to a valid response.
import type { HTTP_GET } from "../../../types/paths/store/order/{orderId}.types.js";
import type { HTTP_DELETE } from "../../../types/paths/store/order/{orderId}.types.js";
export const GET: HTTP_GET = ($) => {
const orders: Record<number, Order> = {
1: { petId: 100, status: "placed" },
2: { petId: 999, status: "approved" },
3: { petId: 1234, status: "delivered" },
};
const order = orders[$.path.orderID];
if (order === undefined) return $.response[404];
return $.response[200].json(order);
};
export const DELETE: HTTP_DELETE = ($) => {
return $.response[200];
};
Returning named examples
If your OpenAPI spec defines named examples, use .example(name) to return a specific one. The name is autocompleted and type-checked from your spec:
export const GET: HTTP_GET = ($) => {
return $.response[200].example("successResponse");
};
State management with plain old objects
Use a _.context.ts file to share in-memory state across routes. POST data and GET it back, just like a real API.
// mock-api/routes/_.context.ts
export class Context {
pets: Pet[] = [];
addPet(pet: Pet) {
const id = this.pets.length;
this.pets.push({ ...pet, id });
return this.pets[id];
}
getPetById(id: number) {
return this.pets[id];
}
}
// mock-api/routes/pet.ts
export const POST: HTTP_POST = ($) => {
return $.response[200].json($.context.addPet($.body));
};
// mock-api/routes/pet/{petId}.ts
export const GET: HTTP_GET = ($) => {
const pet = $.context.getPetById($.path.petId);
if (!pet) return $.response[404].text(`Pet ${$.path.petId} not found.`);
return $.response[200].json(pet);
};
You can also interact with the context object using a REPL. It's like DevTools on the server side. (See "Live REPL" below.)
Key Capabilities
🔄 Hot Reload
Save a route file and the server picks it up instantly — no restart, no lost state. Your in-memory context survives every reload.
🖥 Live REPL
The REPL gives you a JavaScript prompt connected directly to your running server. Inspect state, trigger edge cases, or adjust proxy settings without touching a file.
⬣> context.pets.length
3
⬣> context.addPet({ name: "Fluffy", photoUrls: [] })
⬣> client.get("/pet/3")
⬣> .proxy on /payments # forward /payments to the real API
⬣> .proxy off # stop all proxying
🔀 Hybrid Proxy
Mock the paths that aren't ready yet while forwarding everything else to the real backend. See Proxying for details.
npx counterfact@latest openapi.yaml mock-api --proxy-url https://api.example.com
🔒 Type Safety
Every route handler is typed to match your OpenAPI spec. When the spec changes, regenerating the types surfaces any mismatches at compile time — before they become bugs.
export const GET: HTTP_GET = ($) => {
return $.response[200]
.header("x-request-id", $.headers["x-request-id"])
.json({
id: $.path.userId,
});
};
CLI Reference
npx counterfact@latest [openapi.yaml] [destination] [options]
| Option | Description |
| ------------------- | ------------------------------------------- |
| --port <number> | Server port (default: 3100) |
| -o, --open | Open browser automatically |
| -g, --generate | Generate route and type files |
| -w, --watch | Generate and watch for spec changes |
| -s, --serve | Start the mock server |
| -r, --repl | Start the interactive REPL |
| --spec <path> | Path or URL to the OpenAPI document |
| --proxy-url <url> | Forward all requests to this URL by default |
| --prefix <path> | Base path prefix (e.g. /api/v1) |
Run npx counterfact --help for the full list of options.
About the Author
Counterfact came out of a pattern I kept seeing: teams are slowed down more by coordination than by code.
I’ve spent 25+ years building software and improving how engineering organizations operate across large enterprises, regulated industries, and complex systems. Most of that time, the real constraint wasn’t technology—it was dependency and coordination.
Counterfact is one way of removing that friction.
I’m currently available — not for long.
→ https://patrickmcelhaney.org
<div align="center" markdown="1"> </div>Related Skills
node-connect
335.8kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
82.7kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
Writing Hookify Rules
82.7kThis skill should be used when the user asks to "create a hookify rule", "write a hook rule", "configure hookify", "add a hookify rule", or needs guidance on hookify rule syntax and patterns.
openai-whisper-api
335.8kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).

