SDKs vs. Plain HTTP Calls in Production
A recurring debate in API design circles is whether clients should integrate with a service through its official SDK or just make direct HTTP calls. For many modern RESTful APIs, the endpoints already encapsulate the core actions well enough that a plain HTTP client is entirely workable. The real trade-off then becomes: do you take on an extra dependency to get a convenience layer in your preferred language, or do you keep the dependency footprint small and talk to the API directly?
At Heroku, the internal Ruby culture often led teams shipping new services to also ship a client gem. These gems functioned as lightweight SDKs, and were bundled into any project that needed to talk to the service they wrapped. While the gesture was appreciated, I found myself steadily removing these SDKs from the applications I worked on, once I saw the side effects of relying on them.
Observability: Logging and Metrics
When something goes wrong in a high-traffic application, you need detailed log trails to reconstruct what happened. Standard request information like response codes and elapsed time is a good start, but you also want application-specific context such as the current request ID, all formatted consistently with the rest of your app.
SDKs can be wrapped to add this extra logging, but if you're making your own HTTP calls you can build a single reusable Excon instrumentor that applies to every service you call. The same logic holds for metrics: you want to track average service time, frequency of 503 responses, and internal server errors for each foreign service. With your own HTTP layer, one pattern handles monitoring and alerting for all external dependencies.
Connection Management and Performance
For services in hot paths, keeping pools of persistent connections is often necessary to hit performance targets. Whether an SDK handles this well depends entirely on the implementation and your application's concurrency model; every SDK must be evaluated individually. You can sidestep that uncertainty by standardizing on a common connection-reuse pattern across all services you interact with. One well-understood approach beats five different SDKs, each with its own connection semantics.
Error Handling and Retries
SDK error handling is inconsistent. Some let exceptions from their internal HTTP library bubble up to your code; others swallow failures entirely and pass back invalid data. Even a well-documented SDK requires you to reason through each failure scenario and decide what to do. With direct HTTP calls, you can apply the same pattern everywhere. For instance, when a service returns 503, you can propagate that status up to your own API consumer.
Retries are a similar story. If you hit a network problem and the endpoint is known to be idempotent, retrying a few times is sensible. An SDK might do that, but you can't know its behavior without reading its source code, and even then it may not match your needs.
The Grep Test
When errors bubble up, it helps to instantly identify which part of the code was calling a given endpoint. With plain HTTP calls, that's a simple grep away. With SDKs, you have to map a hostname to a library name and then figure out where that library is used. Vendoring dependencies to make that search easier is not an attractive option.
Can't You Just Patch Around It?
It's true that careful wrapping, monkey patching, or designing pluggable SDKs can address many of these concerns. But the SDKs you're dealing with come from different companies and different authors, each with its own conventions and capabilities. Learning the quirks of each one is not obviously faster than wrapping the HTTP calls yourself, especially when you can reuse patterns you already have in place.
So by all means, keep shipping SDKs—they're useful for onboarding new users and shaking bugs out of an API. Just don't be surprised if the teams running high-traffic services prefer your well-designed web API over the convenience of your client library.



