TwinBridge is a desktop developer tool for creating, managing, and testing API digital twins -- local mock proxies that learn from your real traffic and replay it for offline development, CI testing, and behavioral regression detection.
You point TwinBridge at a live API. It captures every request and response through a local proxy, builds a schema of how that API behaves, and lets you replay that traffic against the twin later -- without touching the real API. When the twin's responses diverge from the original, you know immediately.
It also ships a CLI, a GitHub integration for sharing twin schemas as OpenAPI specs, a versioning system for snapshotting and diffing schemas over time, and a shadow mode that mirrors production traffic against your twin in real time.
TwinBridge is a three-tier Electron desktop app:
+--------------------------------------------------+
| Electron shell (electron/) |
| Spawns backend, waits for health, loads UI |
+---------------------+----------------------------+
| React frontend | Express backend |
| (frontend/) | (backend/) |
| Vite + Zustand | REST API + WebSocket |
| Port 5173 (dev) | Port 7891 |
+---------------------+----------------------------+
|
~/.twinbridge/twinbridge.json <- data store
~/.twinbridge/packages/ <- local package cache
The backend is pure Node.js with no native dependencies. Data is stored as JSON -- no SQLite, no Postgres, no setup required.
Requirements: Node.js 18+, Windows / macOS / Linux
cd twinbridge
npm install
npm install --prefix backend
npm install --prefix frontend
npm install --prefix electronRun in development mode:
npm run devThis starts three processes concurrently:
| Process | What it does |
|---|---|
[BACK] |
Express API + WebSocket on http://127.0.0.1:7891 |
[FRONT] |
Vite dev server on http://localhost:5173 |
[ELEC] |
Electron window loading the Vite frontend |
Expected startup output:
[BACK] OK DB ready at ~/.twinbridge/twinbridge.json
[BACK] OK TwinBridge backend on http://127.0.0.1:7891
[FRONT] VITE ready -> http://localhost:5173
[ELEC] Electron window open
A twin is a named proxy that sits in front of a real API. Create one, point it at an upstream URL, and start capturing.
- Dashboard -- manage all twins, see event counts and accuracy scores
- Twins -- create, clone, edit notes, add tags, delete
Start a local proxy on any port. Route your app's HTTP traffic through it. Every request and response is recorded -- headers, body, timing breakdown (DNS / connect / TTFB / download). Auth headers are automatically redacted.
- Start/stop proxy, live stream of captured requests
- Click any row to inspect headers, body, and timing
- Export as HAR, JSON, or OpenAPI
Replay all captured events against the running twin. Optionally hit the real upstream simultaneously and compare responses field by field.
- Start a replay run and watch results stream in
- Per-request status, latency, and body diff
- Click a completed run to reload its results
Shadow mode spins up a secondary HTTP server that you point real traffic at. Each request is forwarded to the real upstream (your callers get a real response) and simultaneously replayed against the twin -- giving you a live accuracy score against production traffic without changing anything in your stack.
- Set duration, start shadow session
- Point traffic at the shadow port
- Watch twin vs upstream comparison in real time
Compare the endpoint schemas of any two twins side by side. Useful for spotting API drift between environments or versions.
Snapshot a twin's current endpoint schema at any point in time, label it, and diff any two snapshots to see exactly what changed.
- Select a twin and click Snapshot (with optional label)
- Tag any two snapshots A and B
- See +added / -removed / unchanged endpoints in a unified diff
A curated catalog of pre-built twin packages for common APIs, plus support for local .twinpkg packages you build yourself.
Included packages: Stripe, GitHub, HubSpot, Twilio, Shopify, Salesforce, OpenAI, SendGrid
Pull a package to instantly create a twin with seeded synthetic events -- no capture session needed.
Package format (.twinpkg):
{
"name": "my-api-v2",
"service": "My API",
"category": "internal",
"upstream": "https://api.mycompany.com",
"version": "2.1.0",
"description": "Internal service twin",
"endpoints": [
{ "method": "GET", "path": "/v2/users", "example_status": 200, "example_latency_ms": 85 },
{ "method": "POST", "path": "/v2/users", "example_status": 201, "example_latency_ms": 140 }
],
"schemas": {},
"metadata": { "author": "you", "license": "MIT", "tags": ["internal"] }
}Install a .twinpkg via Settings -> Local Package Cache -> Import, or via the CLI:
twin registry pull my-api-v2Export any twin as .twinpkg via Settings -> Export Twin as Package.
Push a twin's learned OpenAPI spec to any GitHub repository, or pull an OpenAPI spec from a repo to create a twin.
Setup (Settings -> GitHub Integration):
- Create a Personal Access Token with
reposcope - Paste it in Settings, set a default repo, Save
- On any Registry card for a pulled twin, click GitHub to push
POST /api/github/push -> creates/updates twins/{name}/openapi.json in your repo
POST /api/github/pull -> fetches an OpenAPI file and creates a local twin
Filterable real-time log stream from all subsystems -- proxy, replay, system events. Export as CSV or JSON.
The CLI talks to a running TwinBridge backend over HTTP. Install it globally:
cd twinbridge/cli
npm linkOr run directly:
node twinbridge/cli/src/index.js listOverride the backend URL with TWIN_API if needed:
TWIN_API=http://127.0.0.1:7891 twin listCommands:
Twin management
twin list List all twins
twin new <n> <service> [url] Create a twin
twin delete <n> Delete a twin
twin status Show running proxies
Capture and replay
twin start <n> <upstream-url> Start proxy capture
twin stop [name] Stop proxy (or all)
twin replay <n> Replay captured traffic
Versioning
twin snapshot <n> [label] Snapshot current schema
twin versions <n> List snapshots
twin diff <n> <vA-id> <vB-id> Diff two snapshots
Export and GitHub
twin export <n> [json|har|openapi] Export captured data
twin push <n> <owner/repo> [path] Push OpenAPI to GitHub
twin pull <owner/repo> <path> [name] Pull OpenAPI from GitHub
Registry
twin registry list Browse registry packages
twin registry pull <package> Pull a registry package
Cache
twin cache list Show locally cached packages
twin cache clear Clear local cache
The backend exposes a REST API on http://127.0.0.1:7891/api.
| Method | Path | Description |
|---|---|---|
| GET | /api/health |
Health check |
| GET | /api/twins |
List all twins |
| POST | /api/twins |
Create twin |
| PATCH | /api/twins/:id |
Update twin |
| DELETE | /api/twins/:id |
Delete twin |
| POST | /api/twins/:id/clone |
Clone twin |
| GET | /api/twins/:id/schema |
Endpoint schema from events |
| POST | /api/proxy/start |
Start capture proxy |
| POST | /api/proxy/stop |
Stop capture proxy |
| GET | /api/proxy/status |
List running proxies |
| GET | /api/capture/events |
Paginated event log |
| GET | /api/capture/export |
Export as HAR / JSON / OpenAPI |
| GET | /api/replay/runs |
List replay runs |
| POST | /api/replay/start |
Start a replay run |
| GET | /api/replay/runs/:id/results |
Per-request results |
| POST | /api/replay/shadow/start |
Start shadow session |
| POST | /api/replay/shadow/stop |
Stop shadow session |
| GET | /api/replay/shadow/:id/results |
Shadow comparison results |
| GET | /api/registry |
List all packages |
| POST | /api/registry/pull |
Pull package -> create twin |
| POST | /api/registry/install |
Install .twinpkg to local cache |
| GET | /api/registry/export/:twinId |
Export twin as .twinpkg |
| POST | /api/versions/:twinId/snapshot |
Create schema snapshot |
| GET | /api/versions/:twinId |
List snapshots |
| GET | /api/versions/:twinId/diff |
Diff two snapshots |
| GET | /api/github/status |
Check GitHub connection |
| POST | /api/github/settings |
Save token + default repo |
| GET | /api/github/repos |
List user repos |
| POST | /api/github/push |
Push OpenAPI spec to GitHub |
| POST | /api/github/pull |
Import OpenAPI from GitHub |
WebSocket -- connect to ws://127.0.0.1:7891/ws:
| Event | Payload |
|---|---|
capture:event |
Full captured request/response |
twin:created / twin:updated / twin:deleted |
Twin object or { id } |
proxy:started / proxy:stopped |
{ twinId, port, upstream } |
replay:started / replay:result / replay:complete |
Run progress and per-request results |
shadow:started / shadow:result / shadow:stopped |
Shadow session progress |
version:created |
{ twinId, version } |
github:pushed |
{ twinId, repo, path, sha } |
Everything lives in ~/.twinbridge/:
~/.twinbridge/
twinbridge.json <- all twins, events, replay runs, versions
packages/ <- locally installed .twinpkg files
stripe-v2.twinpkg
my-api.twinpkg
To reset all data, delete ~/.twinbridge/twinbridge.json. The app starts fresh on next launch.
twinbridge/
package.json <- root: runs all three via concurrently
backend/
package.json
src/
index.js <- Express app, mounts all routes
db.js <- Pure-JS JSON store (no native deps)
ws/server.js <- WebSocket broadcast server
proxy/
engine.js <- HTTP intercepting proxy per twin
routes.js
twins/routes.js <- CRUD + clone + schema
capture/routes.js <- Event log + export
replay/routes.js <- Replay runs + shadow mode
registry/routes.js <- Curated catalog + local cache
versions/routes.js <- Schema snapshots + diff
github/routes.js <- GitHub push/pull
frontend/
package.json
vite.config.js
src/
App.jsx <- WS wiring, view routing
store/index.js <- Zustand global state
api/index.js <- REST client
api/ws.js <- WebSocket hook (useWS)
components/ <- Titlebar, Sidebar, CmdPalette, etc.
views/ <- Dashboard, Twins, Capture, Replay,
Diff, Logs, Versions, Registry, Settings
electron/
package.json
src/main.js <- Spawns backend, opens window
cli/
package.json
src/index.js <- Full CLI (zero external deps)
MIT