Skip to content
Mittelware
All posts

Why developers still need a network interceptor in the AI era

AI-written code breaks at the network boundary. See how a network interceptor lets you edit API responses in transit, force errors and find bugs without touching the backend.

The Mittelware team 13 min read

AI assistants have made it cheap to write code. They have not made it cheap to trust code. A generated API client compiles, reads well and passes the tests the same assistant wrote, and then shows undefined on a real screen, because the server returned "price" where the code expected "amount".

That mismatch lives in one place: the network. It's the seam between code you (or an AI) wrote and a system you can't see into. And the fastest way to debug a seam is to stand in the middle of it. That's what a network interceptor does, and it's more useful now than it was before AI started writing half the client code.

What a network interceptor is#

"Interceptor" means two different things, and mixing them up makes search results confusing:

  • A code-level interceptor lives inside your app: an Axios interceptor, a fetch wrapper, a mock handler library. You write it, ship it and remove it. It only works in the app you changed.
  • A network interceptor (an intercepting proxy) sits outside your app, on your machine, between the app and the internet. Every request passes through it, so it can show, block, change, delay or redirect traffic without a single line of your code changing. It works for any app, including ones you didn't write.

This post is about the second kind. If you've used Charles, Fiddler, Proxyman or mitmproxy, you know the idea. Here the worked examples use Mittelware, a desktop interceptor with a rules engine, but the workflow applies to any tool in that class.

Why AI makes this more important, not less#

AI assistants are good at the code in front of them and blind to what happens when it runs against a real server. Three things follow from that:

  1. The API contract is often a guess. The assistant infers a response shape from a prompt, a type definition or an old example. The real payload has extra nesting, a renamed field, or a null where the code assumed a string.
  2. The failure paths are the least exercised. Generated code tends to cover the happy path first. Checks for a non-2xx status, a timeout, a 429 or an empty list are easy to leave out, and nobody notices until production.
  3. There's more code nobody read closely. When a feature arrives as a 400-line diff, you review the logic. You rarely check whether it fires the same request three times, or sends an auth token in a query string.

None of these show up in the code. They show up on the wire. A proxy is how you look.

1. Fix the response first, then fix the code#

This is the scenario that saves the most time, so it comes first.

The bug: a cart screen shows NaN for the total. The question that decides where you spend the next hour is simple. Is the server sending bad data, or is the client mishandling good data? Reading code won't tell you quickly, and neither will asking an assistant to guess. Changing the response will.

  1. Switch Mittelware on and reproduce the bug. Open Flows, search for the endpoint and read the real response in the Response tab. Say it contains "total": "19.99", a string, where the UI does arithmetic on a number.
  2. Create a Modify Response rule that matches that URL and returns the body the way you believe it should look, with "total": 19.99.
  3. Reload the screen.
The Create Rule form: source Microsoft Edge, label Docs: fake user, a URL contains condition, Modify Response with a raw JSON body.
Source, label, condition, action and the replacement body: a whole rule on one screen.

Here's the rule as it looks in the editor. The numbered boxes are, in order: (1) the Source, the app whose traffic the rule watches; (2) the Label, a name you'll recognize later; (3) the When… condition that decides which requests match; (4) the Then… action, here Modify Response; and (5) the body that replaces what the server sent. This example returns a fake user from a practice API, but the shape is identical for your own endpoint.

Now read the result:

  • The screen is correct. The client is fine and the backend is the problem. You now have the exact payload to put in a bug report, and the frontend work is unblocked while the backend catches up.
  • The screen is still broken. The data was never the problem. The bug is in the client, and you've ruled out half the system in a couple of minutes, without opening a backend repo or redeploying anything.

Either way, you tested the hypothesis before writing a fix, which is the opposite of the "apply the AI's patch and see" loop.

Flows with the Details panel open. A badge reading Docs: fake user executed, and below it the Response tab showing the replaced JSON body.
The badge names the rule that ran. Below it, the body your app actually received.

Flows marks every request a rule touched. In this screenshot, the badge at (1) says which rule ran, and the body at (2) is the response the app received, not the one the server sent. That matters when you're debugging: you can always tell whether you're looking at the real thing or your own edit.

WARNING

Modify Response lets the real request reach the server and only changes what comes back. If the request creates an order or takes a payment, that still happens. For requests with side effects, use Block Request instead (see Simulate a 502 without touching the backend).

2. Reproduce UI states that are hard to cause#

Frontend engineers spend a surprising amount of time trying to get the screen into the state they need to build or test: an empty list, a half-loaded profile, an expired session, a rate limit. Seeding backend data for each one, or writing temporary mock handlers you must remember to delete, is slow and leaves debris.

With an interceptor you describe the state once and switch it on. Against the same endpoint you might keep:

State to test Rule
Empty Modify Response, body { "items": [] }
Partial data Modify Response, body with an optional field removed or set to null
Permission denied Block Request, 403 Forbidden
Session expired Block Request, 401 Unauthorized
Rate limited Block Request, 429 Too Many Requests
Broken payload Modify Response, body { "items": [ (invalid JSON)

The Block Action list is where you choose the status code your app will see:

The Block Action dropdown listing 401 Unauthorized, 403 Forbidden, 404 Not Found, 410 Gone, 429 Too Many Requests, 502 Bad Gateway and Timeout (no response).
Each of these is a different code path in your app.

The same pattern works for mocking an endpoint that doesn't exist yet: the app calls the real URL and the rule answers. Your app code never learns that a mock is involved, and switching the rule off sends traffic to the real thing. We covered that end to end in Mock an API endpoint before the backend exists.

This is also where AI and an interceptor complement each other. An assistant is a good way to draft the realistic JSON for each state. The interceptor is how you deliver it to the running app instead of a unit test that never touches your actual rendering code. For a chat screen, a rule can even stream the fake answer a word at a time, in the format of the OpenAI or Anthropic APIs, so you can build the typing effect without paying for a real model call: Stream a response.

3. Debug applications you don't own#

Code-level mocking has a hard limit: you need to be able to edit the app. A lot of the time you can't.

  • A desktop client or Electron app that calls an API and fails with a vague error.
  • A third-party tool or SDK that behaves differently in production.
  • A legacy app with its API address baked in.
  • A script or command-line tool, whether you wrote it or an AI agent did.

An external interceptor doesn't care who wrote the app. On Mittelware's Applications page you click an app and it relaunches through the proxy; for anything not listed, you point the app at the proxy address by hand.

The Applications page with cards for VS Code, Chrome, Edge and Postman, and a Manual proxy setup panel showing host 127.0.0.1 and port 8080.
Click a card to relaunch that app through Mittelware, or use Manual proxy setup for everything else.

In the screenshot, (1) is an installed app's card; clicking it restarts that app so its traffic flows through the proxy. (2) is the Manual proxy setup panel, with the host (127.0.0.1) and port (8080) to give to a command-line tool or any app that has its own proxy setting.

This also covers an increasingly common case: finding out what your own AI-enabled app sends to a model provider. You can read the request that leaves your machine, including the endpoint, headers, model name and the prompt in the body, and check that nothing you didn't mean to include is in there. The answer comes back the same way it does in a chat window, a few words at a time, and Mittelware follows it live: every chunk is listed with the moment it arrived, so you can see how long the model took to start and whether the stream finished or was cut off. See Monitoring streaming responses.

Two honest limits. Apps that pin their own certificates can't be inspected, and HTTPS inspection requires trusting a local certificate on your own machine. Both are covered in HTTPS & certificates.

4. Check what the client really sends#

Everything above changes responses. The other half of the wire is the request, and it's where generated client code goes quietly wrong: a retry that fires three times, a header that's missing, an ID that's an object instead of a string, a token sitting in a query string that ends up in server logs.

Flows shows every request your app sends. A Pause and Review rule goes further and works like a breakpoint for HTTP: it holds a matching request before it leaves your machine, so you can read it, edit it and decide whether to send it.

The paused request's Details panel, with the URL field editable and a Drop button and a Resume button at the bottom.
The URL is editable, and Drop or Resume decides what happens next.

In this screenshot (1) is the URL, an editable field (we changed comments/1 to comments/2), and (2) holds the Drop and Resume buttons. You can edit the method, URL, headers and body too. That makes it a quick way to check whether the server validates what the UI never lets you send, such as a negative quantity, or an ID that belongs to someone else. The full walkthrough is in Pause a request, edit it by hand, then send it.

5. Simulate unreliable networks and APIs#

AI can write a test that mocks a timeout. It can't tell you what your running app does when a timeout happens: whether the spinner stops, whether the double-click on Pay sends two requests, whether retries back off.

Mittelware has a Delay field on every rule and a Timeout (no response) option on block rules. Together they let you make a real request slow, late or silent.

The Flows list with one row for albums/1 with status 200 and a highlighted Duration of 4080 ms, and a Details panel showing the badge Slow feed 4s executed.
The highlighted Duration cell reads 4080 ms: 4000 ms of added delay plus the real round trip.

The highlighted cell is the Duration: 4080 ms, which is the 4000 ms delay from the rule plus the real round trip. Try a few values and watch what changes:

  • 300 to 800 ms shows whether loading states flicker.
  • A few seconds exercises skeleton screens and "taking longer than usual" messages.
  • A hung request reveals whether the user can cancel, and what happens when the client finally gives up.

More in Test timeouts, retries and slow networks on purpose.

6. Keep scenarios you can switch back on#

The first time you set up a failing checkout, it's a clever trick. The fifth time, it's a chore. Rules persist, and every rule has an on/off switch, so a handful of them become a small test library for your product that you can switch on one at a time.

The Rules list with seven rules, each with Rule name, Action, Application and Status columns, all switched off.
A small library: a mock, a redirect, a pause, a slow response, a 502, a teapot, a fake user. All off until needed.

Name rules after the situation (Cart total as string, Checkout 502, Slow feed 4s), not the technique, and a bug that took an afternoon to reproduce becomes a one-click scenario. Keep them off by default, because a forgotten rule is a confusing bug of its own.

Which tool for which job#

An interceptor doesn't replace your other tools. It covers a gap they leave:

Tool Best at Where it falls short
Browser DevTools Looking at web traffic, quick one-off overrides Browser only, limited rules, no other apps
Code-level mocks and interceptors Repeatable automated tests, CI You have to change and maintain code; mocks drift from the real API
A mock server A full fake backend Another process to run, another base URL to switch
A network interceptor Changing real traffic from any app, with no code changes Not a replacement for automated tests; some apps (pinned certificates) can't be inspected

Use all of them. The interceptor is the one you reach for when you want to know right now what the system does, without changing it.

The common thread: don't touch the backend#

Every scenario above has the same property. You change what the app sees, or what the server sees, from the outside. You don't edit the backend, deploy a branch, ask someone for test data or add temporary code to the client. That keeps experiments cheap to run and cheap to undo: switch the rule off and the app is talking to the real system again.

In an era when code arrives faster than anyone can read it, that's the point. The skill worth keeping sharp isn't writing the first draft. It's checking, quickly and cheaply, what the first draft actually does.

FAQ#

Is the bug in my frontend or my backend?#

Compare the real response with what your client expects, then change the response in transit to the shape you expect. If the screen is fixed, the backend is sending the wrong data. If it's still broken, the bug is in the client. Section 1 shows the steps.

How do I modify an API response without changing the backend?#

Use an intercepting proxy with a rule that matches the request and replaces its status, headers or body on the way back. Your app and your server stay untouched. In Mittelware that's a Modify Response rule.

What's the difference between an HTTP interceptor and a network proxy?#

An HTTP interceptor in code (such as an Axios interceptor) runs inside the app you wrote and only affects that app. A network interceptor is a proxy outside the app, so it works with any application and needs no code changes.

How do I simulate a 502, a 429 or a timeout?#

Create a rule that blocks the matching request with the status you want, or choose a no-response timeout. Because the request is blocked, it never reaches the server, so nothing happens on the other side. See Simulate a 502 without touching the backend.

Can I trust AI-generated API client code?#

Treat it like any other code you didn't write: verify it against real behavior. Check what it sends, check what it does with unusual responses and errors, and check that it handles slow and failed requests. All three are easy to see at the network layer.

Is it safe to intercept HTTPS traffic on my own machine?#

Yes, when the tool keeps everything local. Mittelware creates its own certificate authority, trusts it only for your user account, and doesn't upload your traffic. Details are in HTTPS & certificates.

Keep reading

how-todebugging

Pause a request, edit it by hand, then send it

Use a Pause and Review rule to freeze matching requests before they leave your machine, change the method, URL, headers or body, and resume or drop them. A manual breakpoint for the network.

4 min