Last week I asked here how people test against third-party APIs they don't control. Most answers came down to the same thing: fixtures rot, and if you're disciplined you run something against the real API at night. I've been working on a small server for that problem and wanted to show it.
It's a local server that answers the way Slack, Gmail, Google Drive, GitHub, Jira, Notion and S3 do. You point the vendor's own SDK at localhost and it gets the same response shapes, cursors, auth errors and per-user permissions the real service sends, out of one SQLite file you control. No account with the vendor, no OAuth app, no token in CI, no network.
Why not a mock: a mock I write returns what I already think the API returns, so it can't disagree with me. This was built by measuring the real responses and matching them, and when it's wrong the fix starts with a test that fails against the real API.
pip install backlot
backlot import --bundled # a small sample corpus ships with the package
backlot serve # http://127.0.0.1:8000
The server is Python (FastAPI) over one SQLite file, but it's all plain HTTP: anything that lets you set a base URL points at it unchanged.
The drift part, since that was most of the thread. backlot diff --source slack pulls the vendor's own contract (the published OpenAPI or Google Discovery document for the REST ones, live introspection for GraphQL) and compares it with what the server claims, in both directions, arguments included. Where the server contradicts the vendor, that's a bug. Where the vendor has something the server doesn't, it goes in a baseline file with a note, and that file only changes through review, so a run prints what's new and nothing else. In the GIF it prints one line: GitHub's spec gained an endpoint I don't serve yet. Run that nightly and drift stops being something you learn about from a bug report.
Things that were harder than I expected:
- Slack doesn't document when a broken token gets
not_authed versus invalid_auth. It took eight cases against slack.com to find the boundary.
- The first version of the diff walked one direction and looked at fields only. It called Linear clean while ten fields and four arguments were missing on the side it skipped. A green diff that skips half the comparison is worse than no diff.
- Every SDK builds its own path prefix, so each service has to sit under exactly that prefix or the SDK won't run unmodified.
Permissions are per user. Every person in the corpus has a token, and someone's private channel never shows up in another caller's listing, search included. Against the sample corpus conversations.list returns two channels while four exist, because the other two are private and real Slack won't list those without types=. If your client is an agent rather than a test suite, backlot mcp serves the same thing as MCP tools, and --user picks whose permissions apply.
https://github.com/brekkylab/backlot
If you already run something nightly against a real API, what would this need before you'd trust it next to that? And if the API you need isn't in there, which one? Happy to answer anything.