Test your API
Compare environments
Since 0.4.0 Requires Cloak Proxy 0.4.0 or later.
Send captured requests to two environments — or compare what production returned with what staging returns now — and see per request whether the status, the fields, their types or their values differ, with timestamps and request IDs ignored. Mirror live traffic to a second host with Map Remote's mirror mode. MCP and CI included.
“Does staging behave like production?” usually means two exports and a diff. Compare Environments answers it from the traffic you’ve already captured: each request is sent to the other environment (or to both), and the two answers are compared field by field — status, fields only on one side, types and values — with the fields that change on every call left out.
It has two modes:
- Run Against Both replays captured requests against two environments, or against one and the responses you captured.
- Mirror Live Traffic copies live requests to a second host as the app runs, with a Map Remote rule in mirror mode, and compares each copy’s answer with the original’s.
Each captured request against staging, worst first. The detail pane lists the fields that differ.
- Run against both, or mirror live traffic
- Compare with the captured responses, or send to a left environment too
- Where the right side goes, and headers it replaces
- The result for each request
- The fields that differ
Run against both
Section titled “Run against both”-
Capture the requests you want to compare — a journey through the app, or your end-to-end tests — against one environment (say production).
-
Open Tools › Compare Environments…, or select requests, right-click and choose Compare with Another Environment…, or right-click a host in the sidebar and choose Compare it with Another Environment…
-
Set up the two sides:
- Left: leave Captured on to compare with the responses you captured, or turn it off and give a scheme, host and port to send to a second environment too.
- Right: the environment to compare with — its scheme, host (such as
api.staging.example.com) and port. Empty fields keep the captured value. - Headers… on either side replaces headers for that environment, one per line as
Name: value, such as its ownAuthorizationtoken. A name with no value removes the header.
Click a side’s name to rename it (Production, Staging).
-
Choose the Scope — the selection, an app, a host, or all captured traffic — and click Run (⌘ ↩).
Each request is sent straight to the servers, not through Cloak Proxy’s tools and not into the traffic list. By default:
- one request per endpoint is sent (
/orders/42and/orders/57are one endpoint); POST,PUT,PATCHandDELETErequests aren’t sent, as they change data — they’re listed as Not sent;- static files, tunnels and CORS preflights are left out;
- four requests run at once, at most 10 per second per side.
Read the results
Section titled “Read the results”The window’s subtitle sums up the run: how many requests were sent and how many differ. Each row shows the request, both sides’ status and time, how alike the two bodies are, and how many fields differ:
| Result | Means |
|---|---|
| Status differs | The two sides returned different status codes, such as 200 and 503 |
| Error | One side didn’t answer: it refused the connection, timed out, or the TLS handshake failed |
| Shape differs | A field is only on one side, or has a different type (a number on one side, a string on the other) |
| Values differ | The same fields and types, with some different values |
| Same | The same status and body |
| Not sent | A write, left out unless you include writes |
Rows are sorted worst first; Only differences hides the ones that are the same. Alike is the body similarity score the Authorization Matrix uses, from 0 to 100%.
Select a row for its detail: both sides’ status, time and host, and every field that differs — its JSONPath ($.items[0].price), whether it’s only on one side, a different type or a different value, and the value on each side. Compare shows the two responses side by side, line by line. Right-click a row to show the captured request in the traffic list.
What’s ignored
Section titled “What’s ignored”- Fields that change on every call: timestamps, request and trace IDs, nonces, CSRF tokens, ETags (the same list the Authorization Matrix ignores).
- Key order and number formatting:
5and5.0are the same. - Anything under Options › Ignore Fields…: one JSONPath per line, such as
$.meta.buildor$.items[*].stock; a path covers the fields inside it.
When two arrays have different lengths, that’s one difference on the array, and the items they share are compared one by one.
Endpoint by endpoint
Section titled “Endpoint by endpoint”Open in Release Diff compares the two sides as two recordings in Release Diff: fields added, removed and retyped per endpoint, status changes and slower calls — useful when many requests share the same change.
Mirror live traffic
Section titled “Mirror live traffic”Mirror mode copies live requests to a second host while you use the app, and compares each copy’s answer with the original’s. The app’s own requests aren’t changed or delayed: the copy is sent after the original finishes.
-
Open Tools › Edit Map Remote Rules…, add a rule matching your API (such as
https://api.example.com/*) and set where copies go: the Scheme, Host and Port of the second environment. -
Under When a request matches, choose Send a copy there and compare, and make sure Map Remote is on.
-
Use the app. In Compare Environments, choose Mirror Live Traffic to watch the comparisons arrive.
Live requests copied to staging, each compared with the original's answer.
The bar above the list shows the mirror rules and counts: requests mirrored, how many differ, writes not mirrored and copies skipped while busy. Mirror writes too also copies POST, PUT, PATCH and DELETE requests — they change data on the mirror host. At most four copies are in flight and five are sent per second; requests that arrive while the mirror is busy are counted and skipped, not queued, so a busy app never floods the mirror. The latest 500 comparisons are kept, and Clear empties them with the session.
A mirror rule doesn’t redirect anything, so a request the rule matches still goes to its own host and shows in the traffic list as usual.
From an AI assistant
Section titled “From an AI assistant”The MCP server has two tools:
compare_environmentssends captured requests (scoped withids,apporhost) to therightside —{host, scheme, port, headers, name}— and compares the answers with the captured responses, or with aleftside given the same way. It returns the counts and each request that differs, with its fields.include_writes,every_request,ignore_fields,concurrencyandrequests_per_secondwork as in the window;formatcan bemarkdownorjson.mirror_reportreturns the mirror rules and the latest mirror comparisons. Turn a mirror on withupsert_rule(toolmap_remote,"mirror": true). On Linux, the app and the headless engine mirror too, somirror_reportworks overcloakctland MCP there.
"Compare the ShopApp requests with api.staging.shopapp.example using Authorization: Bearer $STAGING_TOKEN, and tell me which endpoints return a different shape."cloakctl compare-envs compares a recording with another environment and exits with 2 when a difference matches --fail-on.
cloakctl compare-envs --session prod-journey.har --right-host api.staging.example.com \ --right-header "Authorization: Bearer $STAGING_TOKEN" --fail-on shape --out env-diff.md| Option | What it does |
|---|---|
--session FILE / --har FILE |
The recording (required) |
--right-host HOST |
The environment to compare with (required), with --right-scheme http|https, --right-port N and --right-header "Name: value" (repeatable) |
--left-host HOST |
Send to this environment too, instead of comparing with the captured responses (with --left-scheme, --left-port, --left-header) |
--host HOST / --app NAME |
Only this host’s or app’s requests |
--include-writes / --every-request |
Also send writes; send every request, not one per endpoint |
--ignore-field JSONPATH |
Leave a field out (repeatable) |
--insecure |
Accept self-signed certificates |
--format markdown|json / --out FILE |
The output, and where to write it |
--fail-on RULE |
status (a status differs, or a side failed), shape (also fields and types), values or any (any difference), never (the default) |
| Exit code | Meaning |
|---|---|
0 |
No request matched --fail-on |
1 |
A usage error, such as a missing --right-host or an unreadable file |
2 |
At least one request matched --fail-on |
Limits
Section titled “Limits”- Requests are replayed as captured. A request that depends on state on the server (an order that only exists in production) can differ for that reason alone; compare read-only endpoints, or use test data that exists in both.
- Values are compared exactly. Data that legitimately differs between environments (prices, stock, user records) shows as Values differ; ignore those fields under Ignore Fields.
- Only JSON bodies are compared field by field; other bodies are compared whole.
- Redirects aren’t followed, so a redirect shows as its own status.
- Mirror mode copies requests only while Cloak Proxy is running and Map Remote is on, and only exchanges that completed.