โ† All guides

A VCR alternative when the cassette needs to be a URL

Honest opening: VCR and vcrpy are healthy, maintained, excellent libraries โ€” this is not a "the project is dead" page. Checked against the registries the week of writing (Sep 2026): Ruby's vcr gem is at 6.4.0 (released Dec 2025, back under a dual MIT license since 6.3.0) with over 160 million downloads; Python's vcrpy is at 8.3.0 (released Jul 2026). Record a real HTTP interaction into a YAML cassette once, replay it in every test run after: deterministic, offline, fast. The pattern earned its decade of popularity.

The reasons people search "vcr alternative" aren't neglect โ€” they're structural, baked into the cassette-file-in-one-process model. We reproduced every one of them before writing this page (Ruby VCR 6.4.0 via webmock 3.26.4, vcrpy 8.3.0 with requests, Sep 2026 โ€” every snippet below was actually run).

The structural gotchas, reproduced

The secrets run, verbatim

vcrpy 8.3.0, default configuration, one recorded request against our live demo API:

import vcr, requests, yaml

with vcr.use_cassette('cassette1.yaml'):
    requests.get('https://mockbird.mockbird.workers.dev/m/demo/products?limit=2',
                 headers={'Authorization': 'Bearer super-secret-token',
                          'X-Api-Key': 'sk-live-123'})

print(yaml.safe_load(open('cassette1.yaml'))['interactions'][0]['request']['headers'])
# โ†’ {'Authorization': ['Bearer super-secret-token'], 'X-Api-Key': ['sk-live-123'], ...}

Ruby VCR 6.4.0 behaved identically โ€” File.read('rb_cassettes/demo.yml').include?('ruby-secret-token') printed true. Neither library redacts anything unless you configure it to.

And the escape-hatch proof: with the cassette active and its body hand-edited to TAMPERED-BY-EDITOR (so a replay is unmistakable), the in-process client got the tampered recording while a subprocess got the live API:

with vcr.use_cassette('cassette1.yaml', record_mode='none'):
    requests.get(URL).text          # โ†’ [{"id":1,"name":"TAMPERED-BY-EDITOR"}]
    subprocess.run(['curl','-s',URL])  # โ†’ live data. The cassette never existed for curl.

Record & replay at the URL level instead

Mockbird has the record-and-replay loop built in, but the "cassette" is a hosted route at a real URL โ€” so curl, browsers, Playwright, mobile apps, other services, teammates and CI all replay the same recording. Point a project's proxy at the real API and switch recording on:

# 1. create a project (no signup)
curl -X POST https://mockbird.mockbird.workers.dev/api/projects \
  -H "content-type: application/json" -d '{"name":"recorded","blank":true}'
# โ†’ {"id":"<id>","adminKey":"<key>","baseUrl":".../m/<id>", ...}

# 2. proxy unmatched paths to the real API, and record what comes back
curl -X PUT https://mockbird.mockbird.workers.dev/api/projects/<id>/settings \
  -H "x-admin-key: <key>" -H "content-type: application/json" \
  -d '{"proxyBase":"https://dummyjson.com","proxyRecord":true}'

First request for a path forwards upstream and records the 2xx response; every request after that replays locally. The response headers tell you which happened โ€” from our verification run against dummyjson.com:

$ curl -sD - https://mockbird.mockbird.workers.dev/m/<id>/products/1
x-mockbird-proxied: dummyjson.com
x-mockbird-recorded: 1        โ† first hit: fetched upstream + recorded
{"id":1,"title":"Essence Mascara Lash Princess", ...}

$ curl -sD - https://mockbird.mockbird.workers.dev/m/<id>/products/1
(no x-mockbird-proxied header) โ† second hit: replayed locally, byte-identical body

How this answers each gotcha above:

Honest scope notes: recorded bodies are text-ish (JSON/XML/text) up to 16 KB, non-2xx responses proxy through without being saved, first recording wins, and the 20-routes/project limit applies. Full semantics: docs โ†’ record & replay.

Already have the traffic? Import a HAR

If "record" for you means a browser session, skip the proxy: use your app for a minute with DevTools open, Network โ†’ Export HAR, and POST the file โ€” the JSON API responses the page actually received become hosted, stateful collections (bodies served verbatim, full CRUD/filtering/pagination on top):

curl -X POST https://mockbird.mockbird.workers.dev/api/projects/import --data-binary @myapp.har

Walkthrough: HAR file โ†’ live mock API.

Or import the cassettes you already have

The same endpoint accepts VCR and vcrpy cassettes directly โ€” the YAML files sitting in your cassettes/ or fixtures/vcr_cassettes/ directory right now (http_interactions: / interactions:; the JSON serializers work too):

curl -X POST https://mockbird.mockbird.workers.dev/api/projects/import \
  --data-binary @spec/cassettes/checkout.yml
# โ†’ {"id":"abc123","kind":"cassette","baseUrl":"โ€ฆ/m/abc123","resources":[{"name":"products","records":24,โ€ฆ}]}

The 2xx JSON responses recorded in the cassette become hosted records, served verbatim โ€” the data your suite already trusts, now behind a URL any process can hit. Two details that matter here specifically:

Cassette-per-scenario, without the files

The other thing teams use cassette libraries for is fixtures: a known data state per test scenario. That's snapshots โ€” save the project's entire dataset under a name (baseline, empty, edge-cases), restore it in beforeEach, or pin a GET to a named snapshot per request with the X-Mockbird-Snapshot header so parallel workers each see their own state. Full recipe: deterministic test data.

Or use both โ€” they compose

Honest comparison

VCR (Ruby) / vcrpy (Python)Mockbird
What it isfree OSS record/replay librariesfree hosted mock API service
Maintenanceactive โ€” VCR 6.4.0 (Dec 2025), vcrpy 8.3.0 (Jul 2026), verifiedhosted, actively maintained
Recording livesYAML/JSON cassette files in your repohosted routes (editable, deletable, listed via API)
Secrets by defaultrecorded verbatim into cassettes (verified both); filtering is opt-inno repo artifact; inspector redacts auth headers to scheme
Reachable fromthe one process the library patchesanything with HTTP: curl, browser, mobile, other services, CI, agents
Request matchingprecise + configurable (method/URI/host/path/body/headers)method + path only; query ignored (looser โ€” by design)
Stalenesssilent โ€” once mode replays old responses forevervisible recorded: true flags; re-record per route by delete + re-hit
Error/latency drills on recordingsno โ€” replay is verbatim?mock_status / ?mock_delay / ?mock_chaos / ?mock_seq on any recorded route
Stateful CRUD on recorded datanoHAR-imported records become real collections with writes
Works offlineyes (replay mode)no โ€” it's a real network call
Latencyzero (in-process)real network latency
Cost / capsfree forever, no capsfree (beta), 10,000 requests/project/day

Written by the Mockbird maker โ€” bias disclosed. Where VCR and vcrpy genuinely win: offline, zero-latency replay with zero network flakiness; automatic capture of your real API with full headers and every interaction of a test, matched per-test with real precision; cassette diffs in code review; no request caps; and they mock any host with no per-service setup. If your tests are single-process and you've wired up secret filtering, they remain the right tool. When the recording needs to be a URL โ€” for a browser, a subprocess, another language, a teammate, or CI against a deployed build โ€” that's us. (License footnote for completeness: VCR 6.0โ€“6.2 were Hippocratic-licensed only, which some legal teams flagged; since 6.3.0 it's dual-licensed MIT, so that objection is dated.)

โšก No terminal handy? This link creates a live seeded e-commerce API in one click โ€” no signup. Set proxyBase + proxyRecord in its dashboard settings and you're recording.

Full API reference in the docs. More guides: Polly.JS alternative ยท HAR file โ†’ mock API ยท nock alternative ยท MSW alternative ยท mock API for Ruby ยท mock API for Python ยท deterministic test data ยท testing loading & error states ยท free mock API tools compared. Create your API โ†’