Use Cases

Each of these is a situation the tools were built for, with the smallest config that handles it. The case studies walk through three of them in full, with real commands and real output.

A field was renamed

The service now answers "amount" where your app reads "total". One AfterPatch rule puts the old name back:

rules:
  - id: total-renamed
    when: {path: /orders/*, status: 200, content_type: application/json}
    response:
      json:
        - rename: {from: amount, to: total}

The key keeps its place in the object, and every other byte of the answer stays as it was.

The service now wants a header

A new version answers 400 unless the request carries a version header, and the old client doesn’t send one:

rules:
  - id: send-api-version
    when: {path: /api/**}
    request:
      set_headers: {X-Api-Version: "2026-10"}

An endpoint moved

/v1/rate is gone and the same thing now lives at /v2/rate. The query string goes along exactly as the client wrote it:

rules:
  - id: rate-moved
    when: {method: GET, path: /v1/rate}
    request:
      rewrite_path: {from: "^/v1/rate$", to: "/v2/rate"}

The exchange rate case study combines this with the two cases above.

Something leaks that shouldn’t

An upstream started sending a debug header and an internal field to every client:

rules:
  - id: strip-internals
    when: {content_type: application/json}
    response:
      remove_headers: [X-Debug-Trace]
      json:
        - delete: {path: internal}

A whole API version was retired

When the paths, the body shapes and the status codes all changed, AfterAPI is easier to read than a stack of patch rules. You write each old endpoint in terms of the new one:

endpoints:
  - id: get-customer
    old: GET /api/v1/customer?id={id}
    new: GET /api/v2/customers/{id}
    response:
      body:
        customer_id: $.customer.id
        name: $.customer.full_name
      error:
        message: $.error.message

The status codes changed

The new API answers 201 to a create and 204 to a delete, where the old client expects 200 both times. AfterAPI maps the codes and can add the body the old client looks for:

endpoints:
  - id: delete-customer
    old: DELETE /api/v1/customer?id={id}
    new: DELETE /api/v2/customers/{id}
    response:
      status: {204: 200}
      body: {deleted: true}

The customer API case study moves a whole client this way.

Try a fix on live traffic first

Every run command takes --dry-run. Traffic passes through untouched, and the audit log says what each rule would have changed:

aftermarket patch run --config rules.yaml --upstream http://127.0.0.1:9000 --dry-run

Check a new release before it ships

Record what the current version answers, then send the same requests to both versions:

aftermarket record --upstream http://old:9000 --out traffic.yaml
aftermarket replay --fixture traffic.yaml --a http://old:9000 --b http://new:9000 --ignore created_at

Every difference is listed by field. Key order and number formatting don’t count, and only GET and HEAD requests are sent unless you add --include-writes. The release check case study finds a renamed field this way and proves the repair.

Turn real traffic into regression tests

A recording is an ordinary fixture file, with credentials saved as REDACTED. Run it against your rules in CI, and the build fails if a rule starts changing answers it shouldn’t:

aftermarket patch test --config rules.yaml --fixture traffic.yaml

Compare a service with what it used to answer

Without --b, replay compares one service with the answers in the recording. That fits a migration, a dependency upgrade or a config change, where the before picture is only available as a recording:

aftermarket replay --fixture traffic.yaml --a http://127.0.0.1:9000 --ignore created_at

The full formats are in the AfterPatch, AfterAPI and AfterTest and AfterDiff references.