Your API changed. Find out what actually changed.
Compare live API responses across environments in one command. See added fields, missing fields, type changes, headers, and latency — without an OpenAPI file.
reqdiff https://staging.example.com/users https://api.example.com/usersStatus: 200 → 200
Latency: 84ms → 210ms (+126ms)
JSON differences:
+ /user/avatar: string
~ /user/id: number → string
- /user/username: string
Result: different
The example and GIF use synthetic data. Timing is one request per environment, not a benchmark.
A deployment can keep returning 200 and still remove a field your client needs.
reqdiff sends a real GET to each environment and shows the difference.
- Compare shape or values. Catch removed fields and type changes, or check exact data.
- Ignore the noise. Skip timestamps and generated IDs while keeping type checks.
- Use separate credentials. Each environment gets its own token, Basic Auth, API key, or cookie.
- Run it in CI. Structured JSON and predictable exit codes.
- Keep it small. One Go binary. No runtime dependencies. No account required.
Tools such as oasdiff compare OpenAPI contracts. reqdiff compares the responses that two endpoints actually return. These checks answer different questions.
Download a binary for your OS and CPU from the latest release, extract it, and put reqdiff on your PATH. Each release includes SHA-256 checksums.
Or install with Go:
go install github.com/yusufalper/reqdiff/cmd/reqdiff@v0.1.0To build with Docker or from source, start with a checkout:
git clone https://github.com/yusufalper/reqdiff.git
cd reqdiffdocker build -t reqdiff:local .
docker run --rm reqdiff:local \
https://staging.example.com/users https://api.example.com/usersThe runtime image runs as a non-root user and includes CA certificates for HTTPS.
It contains no shell. To access an API running on your host from Docker Desktop,
use host.docker.internal rather than localhost.
On Linux Engine, add --add-host=host.docker.internal:host-gateway if needed.
Requires Go 1.26 or newer.
go build -trimpath -o reqdiff ./cmd/reqdiff
./reqdiff --help
./reqdiff https://staging.example.com/users https://api.example.com/usersExamples below use reqdiff on your PATH. Use ./reqdiff if running the local build.
Flags work before or after the URLs.
The local demo serves two synthetic environments with a removed username, an added avatar, and an ID that changed from number to string.
# Terminal 1 (Python 3, standard library only)
python3 scripts/demo.py
# Terminal 2, after the Go build
./reqdiff http://127.0.0.1:8787/staging/users \
http://127.0.0.1:8787/production/usersOr compare the demo from Docker Desktop:
docker run --rm reqdiff:local \
http://host.docker.internal:8787/staging/users \
http://host.docker.internal:8787/production/usersdifferent exits with code 1. That is an expected result in this demo.
reqdiff https://staging.example.com/users/42 https://api.example.com/users/42The default structure mode compares field presence, types, array length, and
structures at matching array indexes. Primitive values can differ.
Object key order does not matter.
reqdiff https://staging.example.com/users https://api.example.com/users \
--mode values \
--ignore /request_id \
--ignore-value /updated_at \
--ignore-value '/users/*/id'--ignore removes a field and its entire subtree from the comparison.
--ignore-value keeps checking presence and type, but ignores primitive values.
An ID changing from 42 to 43 can be ignored while 42 becoming "42" is caught.
Paths use JSON Pointer: /user/id, not $.user.id. A full * segment matches
any object key or array index. Quote wildcard paths in your shell.
Pass the name of an environment variable, not the secret itself:
# Populate these through your shell, secret manager, or CI secrets.
reqdiff https://staging.example.com/users/me https://api.example.com/users/me \
--left-bearer-env STAGING_TOKEN \
--right-bearer-env PROD_TOKENDifferent auth methods can be used on each side:
reqdiff https://staging.example.com/users/me https://api.example.com/users/me \
--left-basic-user-env STAGING_USER \
--left-basic-password-env STAGING_PASSWORD \
--right-header-env 'X-API-Key=PROD_API_KEY'For an existing session, use --left-cookie-env STAGING_COOKIE and
--right-cookie-env PROD_COOKIE. Values should contain the Cookie header, such
as session=.... reqdiff does not log in or refresh sessions for you.
An email can be a Basic Auth username; an email/password JSON login endpoint is
a separate flow and is not supported in v0.1.
For Docker, forward the variables by name:
docker run --rm -e STAGING_TOKEN -e PROD_TOKEN reqdiff:local \
https://staging.example.com/users/me https://api.example.com/users/me \
--left-bearer-env STAGING_TOKEN --right-bearer-env PROD_TOKENUse equivalent accounts and resources. Comparing different users can produce valid differences that have nothing to do with the deployment.
reqdiff https://staging.example.com/health https://api.example.com/health \
--compare-header Content-Type \
--compare-header Cache-Control \
--max-latency-regression 150msOnly Content-Type is compared by default. Providing --compare-header replaces
that default list. Latency is informational unless a threshold is set. The
threshold fails only when the right endpoint is slower than the left by
more than the given duration.
See examples/reqdiff.json. Secrets stay in environment variables; the config contains their names.
reqdiff --config examples/reqdiff.json
reqdiff --config examples/reqdiff.json --mode structure --format jsonDocker can mount a config read-only:
docker run --rm -e STAGING_TOKEN -e PROD_TOKEN \
-v "$PWD/examples/reqdiff.json:/config.json:ro" reqdiff:local \
--config /config.jsonThere is no automatic config or .env discovery. CLI options override config;
the reference explains list and auth replacement rules.
reqdiff https://staging.example.com/users https://api.example.com/users \
--left-bearer-env STAGING_TOKEN --right-bearer-env PROD_TOKEN \
--format json > comparison.json| Exit | Meaning |
|---|---|
0 |
No differences under the selected rules |
1 |
Differences, or a latency threshold exceeded |
2 |
Invalid input, auth setup, network, size limit, or JSON error |
130 |
Comparison cancelled |
JSON output uses schema_version: 1. Usage errors also produce one JSON report
when --format json is requested. A difference makes the command fail a normal
CI step; preserve comparison.json as an artifact to inspect it.
- Live samples, not a contract. One response cannot prove every possible response is compatible.
- Arrays match by index. Order matters in
valuesmode. No key-based matching or sorting. - Equal errors are still errors from your API. Two identical
401responses can compare equal; reqdiff prints warnings for HTTP error statuses. - GET and JSON only. Empty bodies are supported. Non-empty HTML or plain text returns a parse error. Redirects are reported without being followed.
- Numbers stay exact.
1,1.0, and1e0compare equal; large integers do not lose precision. - Bounded requests. Default timeout is 10 seconds; default decompressed body limit is 10 MiB per endpoint. JSON nesting and numeric size are also limited.
Use HTTPS with credentials. URL userinfo is rejected; URL query strings are omitted
from reports. Supply secrets via the auth flags or --*-header-env.
Known credential strings are masked in both output formats. Raw bodies and request
headers are never dumped. values mode can still display other personal or
sensitive response data — automatic PII detection is not provided.
TLS verification stays enabled. Redirects are not followed. There are no application-level retries. Standard Go proxy environment variables are respected; make sure your proxy is appropriate for the credentials you send.
# Everything needed for tests is inside the image
docker build --target test -t reqdiff:test .
# Or with Go
make test # includes the race detector
make vet
make buildTests use local HTTP/TLS servers and synthetic data. No production endpoints or real credentials are needed. See CONTRIBUTING.md for details, and docs/reference.md for the full CLI and output contract.
make release VERSION=v0.1.0 produces Linux, macOS, and Windows archives for
amd64 and arm64, plus SHA-256 checksums in dist/. A tag matching v* triggers
the included GitHub release workflow.
Licensed under MIT.
