Record your app's real API calls once. Replay them offline, forever.
kopya (Filipino for "copy") is a single static binary that sits between your app and
the internet. Run your test suite once against the real API, and every call after that is
served from a fixture file — no network, no API bill, no flakes.
$ kopya record -- pytest tests/
...
kopya recorded 47 calls → .kopya/fixtures.json
Secrets were redacted. Review the file before committing it.
$ kopya replay -- pytest tests/
kopya loaded 47 recorded calls from .kopya/fixtures.json
...
kopya replayed 47 calls · 0 live
Saved ~128,400 tokens and 94.2s.Your application code does not change. No mock library, no dependency injection, no monkeypatching.
Tests that hit OpenAI, Anthropic, Stripe or any other third-party API are slow, nondeterministic, and cost real money. The existing answers all have a catch: language -bound mock libraries only cover one runtime, and the proxy-based tools need Python or a JVM in your CI image.
kopya is one ~7MB binary with no runtime. It boots instantly in a container and works the
same for Python, Node, Go, Rust, Ruby, Java or curl.
Whichever ecosystem you already have. All three install the same prebuilt binary — there is no Node or Python runtime involved once it is on disk.
npx @kopya/cli --help # Node
uvx kopya --help # Python
cargo install kopya # Rust(The npm package is scoped @kopya/cli; the installed command is still kopya.)
Or grab a binary from the releases page. Prebuilt for macOS (Intel and Apple Silicon), Linux (x64 and arm64, statically linked against musl) and Windows x64.
kopya runs an HTTP/HTTPS proxy on 127.0.0.1 and launches your command with the proxy and
CA environment variables already set:
| Variable | Why |
|---|---|
HTTP_PROXY / HTTPS_PROXY |
Routes traffic through kopya |
NODE_USE_ENV_PROXY=1 |
Node's built-in fetch ignores HTTP_PROXY without it |
NODE_EXTRA_CA_CERTS |
Node uses its own CA bundle, not the system keychain |
REQUESTS_CA_BUNDLE |
Python requests / httpx use certifi |
SSL_CERT_FILE, CURL_CA_BUNDLE |
OpenSSL and curl |
This matters: trusting a root CA in the system keychain is not enough for Node, Python
or Java. Setting these per-process is what makes "zero code changes" actually true. If you
point an already-running process at kopya instead, run kopya ca trust and export the
variables it prints.
kopya record -- <command> # run <command>, save its outbound calls
kopya replay -- <command> # run <command> against the saved calls
kopya ca path # print the root CA path (creates it on first run)
kopya ca trust # install the root CA into the system trust store
kopya ca untrust # remove itUseful flags:
--out/--in— fixture file path (default.kopya/fixtures.json)--port— proxy port (default8888)--allow-live— let unmatched requests reach the network instead of failing--realtime— reproduce the original timing of recorded streams--no-redact— keep credentials in the fixture. Don't.
Running kopya record or kopya replay with no -- command starts the proxy in the
foreground so you can point an existing process at it.
text/event-stream responses — every streaming LLM API — are recorded frame by
frame, with the offset each frame arrived at, and replayed with those boundaries
intact. A recording is passed through to your app live as it happens, so the run
you record behaves exactly like an unproxied one.
By default replay emits the frames as fast as the client will take them, because the point of replay is to make a slow test fast:
recorded 7 frames over 1015ms
replayed 7 frames over 7ms # 145x faster, same frames
replayed 7 frames over 1012ms # with --realtime
Use --realtime when the thing under test is the streaming behaviour itself —
time-to-first-token, incremental rendering, or a client-side timeout.
Token usage is parsed out of the final frame, so the summary can report what a replayed run would have cost.
In replay, a request with no recorded match returns 502 and fails the run, even if
your test swallowed the error. A green replay run therefore proves the suite is genuinely
hermetic. Pass --allow-live when you want the softer behaviour.
The fixture file is the artifact your team shares, so it is designed to survive code review:
- Headers are stored in sorted maps and bodies as plain text, so diffs stay readable
- Credentials are redacted before anything touches disk —
Authorization,Cookie,x-api-keyand friends, plus baresk-/ghp_/AKIA-style tokens found in bodies - Binary payloads are dropped rather than base64-inflating the file
Always read a fixture before committing it the first time. Redaction is conservative by design, not a guarantee.
Early. 0.1 covers HTTP/1.1 record and replay, including server-sent event streams,
with redaction, response decompression and strict matching.
Roadmap: HTTP/2, a GitHub Action, Homebrew, request-count assertions, and latency + error injection.
Not planned for 0.1: gRPC, WebSocket, a GUI, declarative YAML rule files.
Licensed under the Apache License, Version 2.0.
Contributions are accepted under the same license, per section 5 of the Apache 2.0 terms — no separate CLA.