Counterfact logo

Not just a
mock server.

Point Counterfact at an OpenAPI spec and get a live, stateful API in seconds — with TypeScript safety, hot reload, shared state, and a programmable REPL.

terminal
$npxcounterfact@latest
  https://petstore3.swagger.io/…/openapi.jsonapi
Reading OpenAPI spec…
Generating routes/ and types/ …
✓ 17 route files written
✓ Server → http://localhost:3100
✓ Swagger UI → /counterfact/swagger/
⬣>
What you get

Everything a mock server
should have been.

Static canned responses aren't enough. No shared state. No failure injection. No control mid-run. Counterfact is an API simulator without those limits.

Contract-driven generation

TypeScript handlers and typed interfaces for supported operations, derived from your OpenAPI spec. Regenerate after a contract change and TypeScript identifies incompatible handler code.

Hot reload, no restart

Save a route file and the server picks it up instantly. Your in-memory state survives every reload — no lost context, no interruptions.

Programmable REPL

A live JavaScript prompt wired directly to your running server. Inspect state, inject failures, toggle proxy routes — while the server handles real traffic.

Shared state across routes

Drop a _.context.ts in any directory to share typed in-memory state. Routes in that context subtree see the same data.

Hybrid proxy mode

Forward some routes to a real backend while mocking everything else. Toggle individual paths live from the REPL as the real API comes online.

Deterministic resets

Keep seed logic in a typed startup scenario. Each fresh process applies the same scenario; each test worker can use its own port and in-memory state.

How it works

From spec to live
server in one
command.

  • Read your OpenAPI spec

    Point Counterfact at a local OpenAPI 3.x file or URL. It generates from the paths, operations, parameters, request bodies, responses, and schemas it supports.

  • Generate typed TypeScript handlers

    A routes/ directory with one.ts per endpoint, plus full typed interfaces in types/. Existing handler code is preserved when generation adds operations.

  • Server starts immediately

    Generated handlers are live, returning schema-derived random data by default. Swagger UI is available so you can explore immediately.

  • Program the behavior you need

    Replace .random() with.json(), add state, wire up logic. Save — the server reloads instantly, state preserved.

  • Control live state and routing

    Inspect state, inject failures, fire requests, toggle proxy routes — all from a live prompt, no file edits required.

api/routes/pet/{petId}.ts
import type { HTTP_GET, HTTP_DELETE }
  from "../../types/paths/pet/{petId}.types.js";

export const GET: HTTP_GET = ($) => {
  const pet = $.context.get($.path.petId);

  if (!pet) {
    return $.response[404].text(
      `Pet ${$.path.petId} not found`
    );
  }

  return $.response[200].json(pet);
};

export const DELETE: HTTP_DELETE = ($) => {
  $.context.remove($.path.petId);
  return $.response[200];
};
api/routes/_.context.ts
import type { Pet }
  from "../types/components/pet.types.js";

export class Context {
  private pets = new Map<number, Pet>();
  private nextId = 1;

  add(pet: Omit<Pet, "id">): Pet {
    const id = this.nextId++;
    const rec = { ...pet, id };
    this.pets.set(id, rec);
    return rec;
  }

  get(id: number) { return this.pets.get(id); }
  list()         { return [...this.pets.values()]; }
  remove(id: number) { this.pets.delete(id); }
}
A repeatable workflow

Seed. Exercise.
Reproduce.

Here is one checkout edge case carried from a clean start through a real HTTP request and into an automated test. The scenario code stays with the project, so a teammate or coding agent can recreate the same API world.

01 · deterministic start

Seed a declined payment

// scenarios/index.ts
export const startup: Scenario = ($) => {
  $.context.orders.reset();
  $.context.paymentMode =
    "declined";
};

// routes/orders.ts
export const POST: HTTP_POST = ($) =>
  $.context.paymentMode === "declined"
    ? $.response[402].json({ code: "card_declined" })
    : $.response[201].random();

Counterfact runs startup for each new server process. Restarting creates a fresh in-memory context and applies the same seed you keep in version control.

02 · real client traffic

Drive the browser flow

$ curl -i -X POST \
  localhost:3100/orders

HTTP/1.1 402
content-type: application/json

{ "code": "card_declined" }

Requests to simulated routes and their preflight receive CORS headers. Supported Basic/API-key values reach handlers; your scenario code decides which credentials or permissions succeed.

03 · non-interactive CI

Control it from the test

const app = await counterfact(config);
const { stop } = await app.start(config);

app.contextRegistry.find("/")
  .paymentMode = "approved";

await expectCheckout(200);
await stop();

The programmatic API starts and stops with the suite—no REPL or remote credentials. Give parallel workers distinct ports and processes for isolated state.

Counterfact makes this simulation repeatable; it does not prove the production payment service behaves the same. Keep a targeted test against the real backend before release.
counterfact REPL
⬣>context.list()
[ { id: 1, name:'Fluffy', status:'available' } ]
⬣>context.add({ name:'Rex', photoUrls: [], status:'pending' })
{ id: 2, name:'Rex', … }
⬣>client.get("/pet/2")
{ status: 200, body: { id:2, name:'Rex', … } }
⬣>.proxy on /payments
✓ /payments/* → https://api.example.com
⬣>context.pets.clear()
✓ Cleared. GET /pet → []
⬣>
The REPL

DevTools
for your API.

A live JavaScript prompt wired directly to your running server. The REPL exposes a context object and aclient — inspect state, modify data, and fire requests all at once.

Want to test what happens when the database is empty? Clear it. Need to simulate a 500? Set it up. Proxy half the API to a real backend? One command.

A fresh process starts with fresh in-memory context. Add a typed startup scenario when the exact seed must be repeatable.

Who it's for

Real enough to be useful.
Fake enough to be usable.

Frontend developer

Stop waiting on the backend.

Your team has an OpenAPI spec. The backend is two sprints out. Run Counterfact and build against a live, typed, stateful API today. The handlers and seed scenarios live in version control, and regenerated types surface contract changes in your editor.

Test engineer

Reproducible state, every time.

Set up exactly the state you need from the REPL. Run your test. Or start Counterfact from test code, set context directly, and stop it with the suite—without a shared database fixture.

AI agent / automation

A stable API substrate.

Agentic workflows need deterministic, scriptable APIs. Counterfact runs locally with explicit scenario code and programmatic state control, leaving a reproducible verification environment for human review.

Contract credibility

What Counterfact checks.
What it cannot know.

A contract-backed simulator catches structural mismatches early. You still own the behavior that is not expressed in OpenAPI.

Checked or surfaced by Counterfact

  • Generated handler types constrain declared status codes and response shapes at TypeScript compile time.
  • Required query, header, and cookie parameters—and supported JSON/form request bodies—are checked by default; detected mismatches return HTTP 400.
  • Declared response-header mismatches are returned as advisory response-type-error headers.
  • Watching a local spec regenerates types and exposes incompatible handler code while the simulator stays local.

Behavior your team still owns

  • Business rules, authorization decisions, side effects, and realistic data relationships live in handler/context code.
  • Generated random responses are schema-shaped examples, not proof of production behavior or data quality.
  • Hot reload preserves live state; use a fresh process plus a startup scenario when a clean reset matters.
  • Keep targeted real-backend and end-to-end tests—Counterfact complements them; it does not replace them.
RuntimeNode.js 22 or newer; local npm package and CLI.
ContractOpenAPI 3.0–3.2 or Swagger 2.0; local file or URL.
Browser & authCORS/preflight built in. Handlers implement auth outcomes.
IsolationIn-memory state. Use one process and port per parallel worker.
Get started

Try it in one command.

No global install. Requires Node.js 22+ and a supported OpenAPI or Swagger document.


npx counterfact@latest https://petstore3.swagger.io/api/v3/openapi.json api

Read the getting started guide