Skip to content
Ozan Doruk Yavuz logo Ozan Doruk Yavuz
Reliability Python Refactoring

One request builder can fix hundreds of flaky API calls

If inherited code calls a flaky API in hundreds of places, you do not fix it call by call. You fix the client once, and every call inherits the fix.

The problem with scattered calls

I took over a large app that called an external API from a few hundred places, and almost none of those calls retried, recovered a dropped connection, or set a timeout. The symptoms were the classic ones: it would hang, fail to connect, or just error. Rewriting every call site would have been slow and risky.

Fix the client, not the call sites

Instead I wrapped the client itself, injecting one custom request type that every call flows through. Nothing at the call sites changed. They all kept calling the API the same way, but now each call ran through one place where the resilience lives.

What the wrapper does

That one place handles the things every call needed. It retries rate limits and server errors with backoff. It resets a stale connection and tries again on a fresh one. It gives each user their own quota so they do not starve each other. And it sets a timeout, so a frozen network becomes a retryable error instead of a frozen app. It also logs only what matters: slow calls, retries, and failures.

Where I used it

This was the core of making an inherited internal ERP reliable. One wrapper, a few hundred call sites fixed at once, and the hangs and dropouts went away, without rewriting the app.

Questions

Why not just add retries to each call?

Because there can be hundreds of call sites. Fixing them one by one is slow and error prone. Wrapping the client once fixes all of them at the same time, with no change to the calls.

Does this hide real errors?

No. It retries only the transient failures, like rate limits, dropped connections, and timeouts, and still surfaces genuine errors to the caller, now with logging so you can see what happened.