Skip to content

Test your API

Release diff

Since 0.4.0 Requires Cloak Proxy 0.4.0 or later.

Record the same journey on two builds, or yesterday and today, and Cloak Proxy compares them endpoint by endpoint — endpoints added and no longer called, fields added, removed or retyped, status changes, slower calls, extra requests and new third parties receiving user data — with the requests on both sides, MCP and a CI exit code.

A release changes more on the network than its changelog says. A field becomes a string, a call fires three times instead of once, a new SDK starts sending the user’s email to an analytics company. Release Diff answers “what did this release change on the network?”: record the same journey on the old build and the new one, and it compares the two recordings endpoint by endpoint.

Schema changes spot a response that changes shape within one session. Release Diff compares whole sessions, so it also sees endpoints that stopped being called, requests that got slower or more frequent, and data that started going to someone new.

Every difference between two builds, most severe first. The detail pane shows the values on each side and the requests behind them.

  1. Choose what each side is
  2. Differences by kind
  3. A difference, with where it is
  4. The value before and after
  5. The requests on each side
The Release Diff window comparing ShopApp 2.3 with ShopApp 2.4: 19 differences, 6 high, 7 medium and 6 low. The Before and After cards show each side's API requests and endpoints with a Change button. The sidebar lists All differences, Endpoints, Fields, Status codes, Speed, Request counts and Third parties with counts, then Every endpoint and Every third party. The list shows, most severe first, Type changed at $.items[*].price for GET /v2/products, Type changed at $.price, Field removed at $.loyalty for GET /v2/users/me, Status changed for POST /v2/orders (now returns 500 in 1 of 2 calls), New third party AppsFlyer, Sends new data to Mixpanel, Endpoint no longer called for GET /v2/recommendations, Now null at $.trackingUrl, Query parameter removed ?limit, Slower and More requests for GET /v2/products/{id}. The selected type change's detail pane shows number before and string after, request #12004 on 2.3 and #12020 on 2.4, and the endpoint's calls, median and statuses.
  1. Record the journey on the old build: sign in, browse, add to the cart, check out — whatever the release touches.

  2. Open Tools › Release Diff… (⇧ ⌥ ⌘ D) and choose Keep Current Traffic as Before. Cloak Proxy saves a copy of the session for the comparison, so you can clear the traffic list.

  3. Clear the session, install the new build and record the same journey.

  4. Choose After › Change › All Captured Traffic (or one app, host or version) and click Compare (⌘ ↩).

You don’t have to clear the session in between. When the app puts its version in its User-Agent (ShopApp/2.4 (iPhone; iOS 26.0), which most iOS and Android HTTP stacks do), or uses the in-app SDK, both builds can be recorded into one session: each side’s Change menu lists the App Versions it found. Right-click an app in the sidebar and choose Release Diff for the app to compare its two newest versions straight away.

Each side’s Change menu offers:

Source What it compares
All Captured Traffic Everything in the traffic list
App Versions One app at one version, such as ShopApp 2.4 (52) (the number is its requests)
App / Host One app’s or one host’s requests
Selected Requests The requests selected in the traffic list. Right-click a selection and choose Diff Against Another Recording… to make it the after side
Session File… A saved .cloaksession or .har file, such as last week’s recording or one from a teammate
Keep Current Traffic as Before A copy of the traffic list as it is now (before side only)

The cards show what each side holds once compared: its API requests, endpoints, and where it came from. ⇄ swaps the sides. When you change a side or an option after comparing, a bar offers Compare Again.

Requests are grouped into endpoints the way Endpoints does — GET /v2/orders/42 and GET /v2/orders/57 are both GET /v2/orders/{id} — and each endpoint is compared across the two sides. Images, scripts, styles, fonts, tunnels and requests still in flight are left out.

Difference When it’s reported Severity
New endpoint Called after, never before Low
Endpoint no longer called Called before, never after. If the journey was the same, the screen that used it may have changed or broken Medium
Field removed A JSON field in successful responses before is in none after High if it was in every response before; medium if it was optional
Field added A field in responses after that wasn’t there before Low
Type changed A field was a number and is now a string, say (number → string) High in a response; medium in a request body
Now null A field that was never null is null in some responses after Medium
Query parameter removed / added The app stopped sending ?limit, or started sending ?sort Medium / low
Status changed A status the endpoint didn’t return before: a 5xx or no response is high, a 4xx or a different success code is medium; an error that went away is low High, medium or low
Content type changed Successful responses were application/json and are now text/csv Medium
Slower The median time is at least 1.5× the old one and at least 100 ms slower (both in Options) Medium; high when over 3× and a second slower
More requests An endpoint is called at least 25% more often and at least once more — a duplicated call, a retry or a new refresh Medium; high when over 3× and three or more extra
More requests (whole journey) The journey made at least 5 and 20% more API requests Medium
New third party A company receives user data after and received none before High for personal data (email, phone, name, location, device, advertising and user IDs, your app’s tokens); medium for IDs from your backend; low when it’s contacted with no user data
Sends new data A company already receiving data now gets a new kind, such as the email address High or medium, as above
No longer sent data A company stopped receiving user data Low

Fields are compared across every successful JSON response of the endpoint on each side (up to 60), request bodies across every JSON request body, and both are read after decoding gzip, deflate and Brotli. A field’s path is a JSONPath with [*] for array items ($.items[*].price); when a whole object is added or removed, only the object is listed, not each field inside it. Things that aren’t changes are left out: the same fields with different values or in a different order, an array that’s empty on one side, maps keyed by IDs ({"8812": …}), and cache-busting query parameters (_, t, ts, cb, nonce…).

Third parties come from the Privacy Map’s scanner run on each side, so the same data kinds and company names apply. The app’s own domains are guessed from its name and its busiest domain; set them in Options › App’s Own Domains… if the guess is wrong.

The sidebar groups the differences: All differences, then Endpoints, Fields, Status codes, Speed, Request counts and Third parties, each with a count and its most severe icon. Hide low leaves out additions and the other changes that rarely break anything.

Each row says what changed (with the field path in the severity’s colour), one sentence about it, where it is — the endpoint, or the company and its domain — and, when there’s room, the value before and after. Select a row for its detail:

  • the sentence in full, with how sure it is: “in all 3 samples before, in none of 2 after”, or “it was optional before, so this may be sampling”;
  • the before and after values;
  • the requests on each side that show the difference — click one to compare it with the first request on the other side, or Show in Traffic to select them in the traffic list (for a side from the captured traffic);
  • the endpoint’s calls, median time and status codes on both sides, and its other changes.

Compare opens the two requests side by side, line by line, with JSON pretty-printed and keys sorted so formatting doesn’t count, and the changed field in bold.

Compare: the before and after requests line by line. Here the price became a string.

Two GET /v2/products responses side by side: ShopApp 2.3's request #12004 on the left and ShopApp 2.4's #12020 on the right. Lines with price are highlighted, 89 in red on the left and the string 89.00 in green on the right, and the same for 12.50 and 129.00. A Request and Response switch, an Only changes checkbox and 3 lines differ at the top.

Every endpoint lists each endpoint on either side, changed ones first: New and Gone badges, calls before → after, the median time (in red when it’s slower), the status codes on each side and how many differences it has. Select one to see its changes and its requests on both sides.

Every endpoint side by side: calls, median time, statuses and how many differences each has.

Every endpoint: POST /v2/orders called 1 → 2 times, GET /v2/products/{id} called 1 → 3 times with a median of 148 ms → 637 ms in red, GET /v2/recommendations marked Gone, GET /v2/wishlist and the AppsFlyer event endpoint marked New, and unchanged endpoints with an equals sign. The selected GET /v2/products/{id} lists its changes (Type changed at $.price, Slower, More requests) and its one request on 2.3 next to three on 2.4.

Every third party lists each outside company either side talks to, with its requests before → after and the user data it receives: new kinds in red with a +, kinds it already received in grey, and kinds it no longer gets struck through.

Which companies receive what, before and after.

Every third party: AppsFlyer, marked New, with 0 → 1 requests and + Advertising ID and + User ID in red; Mixpanel with 1 → 1 requests, + Email address in red and User ID in grey. The selected Mixpanel's detail lists Email address as New and User ID as before and after, with request #12003 on 2.3 and #12019 on 2.4.
Option Default What it does
Slower when the median is 1.5× the old one How much slower an endpoint must be: 1.25×, 1.5×, 2× or 3×
And at least 100 ms slower Ignore small differences in fast calls: 25, 50, 100, 250 or 500 ms
Compare request bodies On Also report fields the app added to, removed from or retyped in what it sends
Compare third parties On Run the Privacy Map’s scanner on each side
Include images, scripts and fonts Off Compare static files as endpoints too
Ignore Fields… None JSONPaths to leave out, one per line, such as $.meta.generatedAt or $.items[*].views. A path covers the fields inside it, and $.items[3].x is read as $.items[*].x
Match Hosts… None When the sides use different hosts, pairs such as api.staging.shopapp.example = api.shopapp.example (the after host, then the before host), so their endpoints line up
App’s Own Domains… Guessed Registrable domains that belong to the app; everything else is a third party

The sides and options are remembered. Kept traffic is stored on this Mac (only the newest copy), through a store that self-test and gallery runs redirect to a throwaway folder.

The share menu exports the comparison as:

  • Markdown — a table of both sides’ totals (requests, endpoints, hosts, errors, median time, data received), then a section per kind with each difference’s severity, endpoint, field, before and after values, and request numbers on both sides. It reads well as a pull request comment.
  • JSON — the whole comparison as data: both sides’ totals, every endpoint, every difference and every third party.

The MCP server’s release_diff tool compares two sides. Each side is an object: session_path for a saved .cloaksession or .har file, or nothing for the captured traffic, narrowed with app, app_version, host, ids, from_id and to_id. It returns both sides’ totals, the counts by severity and kind, each difference (severity, kind, endpoint, field path, before, after, a sentence and the request ids on both sides) and the third parties whose data changed — or the comparison as markdown or json. min_severity, category and limit narrow the list; the options above are arguments too.

Ask your assistant
"Compare ShopApp 2.3 with 2.4 in the captured traffic and tell me which changes could break the old app."

See the MCP tools reference for every argument.

cloakctl diff compares two recordings and exits with 2 when a difference matches --fail-on, so a build can fail when a release changes the API in a way you care about.

Terminal
cloakctl diff --before baseline.cloaksession --after e2e.har --fail-on high --out release-diff.md
Option What it does
--before FILE / --after FILE The older and newer recordings: .cloaksession or .har (both required)
--session FILE with --before-version V --after-version V One recording with both builds in it, split by the app version in the User-Agent
--app NAME / --host HOST Compare only this app’s or host’s requests on both sides
--host-map NEW=OLD Line up hosts that differ between the recordings (repeatable)
--first-party DOMAIN The app’s own domains (repeatable). Default: guessed
--ignore-field JSONPATH Leave a field out (repeatable)
--slower-ratio 1.5 / --slower-min-ms 100 When an endpoint counts as slower
--include-static Also compare images, scripts, styles and fonts
--no-third-parties Don’t compare third parties
--format markdown|json The output format (default markdown)
--out FILE Write the comparison to a file instead of printing it
--fail-on RULE When to exit 2: a severity (high, medium, low — that or worse), kinds of difference (endpoints, fields, statuses, timing, requests, third-parties), single differences (field-removed, third-party-added…), comma-separated, or never (the default)
Exit code Meaning
0 No difference matched --fail-on
1 A usage error, such as a missing or unreadable file, or a side with no requests
2 At least one difference matched --fail-on

A typical setup keeps a recording of the end-to-end tests from the main branch as the baseline, and compares each pull request’s recording with it. Record both with cloakctl run --record:

.github/workflows/release-diff.yml
name: Release diff
on: [pull_request]
jobs:
diff:
runs-on: [self-hosted, macOS]
env:
CLOAKCTL: /Applications/Cloak Proxy.app/Contents/Resources/cloakctl
steps:
- uses: actions/checkout@v4
- name: Record the end-to-end tests through Cloak Proxy
run: |
"$CLOAKCTL" run --ssl 'api.example.com' --record pr.har --quiet -- npm run test:e2e
- name: Compare with the main branch's recording
run: |
"$CLOAKCTL" diff --before baseline/main.har --after pr.har \
--fail-on high,third-parties --out release-diff.md
- name: Keep the comparison
if: always()
uses: actions/upload-artifact@v4
with:
name: release-diff
path: release-diff.md
  • Release Diff compares what was recorded. Request counts and the “no longer called” check assume both sides cover the same steps; a shorter journey reads as missing endpoints.
  • Fields come from samples. An optional field that happened not to appear on one side can read as removed or added; the detail says how many samples had it, and optional fields are never reported as high.
  • Timing depends on the network, the device and the server’s load. Record both sides in the same conditions, and repeat the journey when a difference is small.
  • Only JSON bodies are compared field by field. Bodies cut short when captured, and bodies over 4 MB, aren’t sampled.
  • Endpoints line up by method, host and path template. An endpoint that moved to a new path reads as one removed and one added.
  • Request numbers are each recording’s own. Sessions saved by Cloak Proxy 0.3 or earlier store times to the whole second, so their durations are approximate; save them again to keep milliseconds.