Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

kopya

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.

Why

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.

Install

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.

How it works

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.

Commands

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 it

Useful flags:

  • --out / --in — fixture file path (default .kopya/fixtures.json)
  • --port — proxy port (default 8888)
  • --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.

Streaming responses

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.

Strict by default

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.

Fixtures are meant to be committed

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-key and friends, plus bare sk-/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.

Status

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.

License

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.

About

Record your app's real API calls once. Replay them offline, forever.

Resources

Stars

Watchers

Forks

Releases

Contributors

Languages