I helped maintain the public API language bindings at Stripe for many years, and although I'd defend it to the death as the right DX (SDKs are so much easier to use than raw web APIs), it's hard to appreciate just how onerous of a process this was before Alex pioneered a codegen-based solution.
Some of the language SDKs were dynamic (e.g. Ruby, Python) and could adapt automatically to small API changes like if a new field was added in a response. Some were not (e.g. Java, Go), and for every API change, someone had to manually make the change to that SDK's codebase (add a field, add a struct, add a function for a new endpoint), get it reviewed and merged, and cut a release. As Stripe got bigger and there were API changes all the time, the only way this was even remotely functional was that we had a couple heroic workhorses that'd watch for changes and open hundreds of PRs a year for them. Honestly, in retrospect, it's amazing this even worked.
Getting everything switched over to a generated solution was an arduous process because the new generated code had to be API-compatible with the existing code so that the cutover to generated bindings didn't break every Stripe user under the sun. We eventually got there, but it took a long time.
I like Stainless' mission because after seeing the crazy maintenance hassle that all of this was at Stripe, I think it makes way more sense to save all that engineering time for your more concerns, and outsource this problem to someone else. A plug-and-play way of getting high quality SDKs in all common languages that get pushed to appropriate repositories for distribution and comes with quality companion documentation.
We've actually had pretty good luck at my current job with open source tools like openapi-generator [1], but this sort of codegen is so convoluted that the sustainability of a pure open source solution makes me a little afraid, and you still end up doing a lot of the last mile work yourself.
I've worked on 4 different (pretty heavily used) developer-focused products in my career in product management, and we hand-built SDKs for each of them. This experience is so common: in all cases, a lot of the leadership went into it thinking it would be an easy-to-maintain "background task" to maintain 4-6 SDKS because we generally hired pretty well seasoned and hard working developers
Inevitably in all cases language-specific problems exactly like this happened, but also just some frustrating drift. One of those heroic workhorses would go out on parental leave and maybe a backup would go on vacation or out for extended sick time and the SDKs would start drifting away from the API. Someone would be stuck maintaining code in their absence. At one point, I (a product manager at the time) got tasked with maintaining a Perl SDK while a developer was out on leave for several months because my background is in software development and I was the only person that could really maintain it in the company.
I'm happy to see tools like this come about because it seems everyone underestimates how much work SDKs take to really build functional SDKs. You can get bad ones cheaply by just putting a few people on and accepting bugs and drift. The counter-argument against these tools that I've generally heard is that they'll also produce worse ones cheaply or require effectively the same maintenance to figure out a machine-generated problem as a human-generated problem.
Never been a fan of code-gen solutions that generates both DTOs and client proxies which IMO promotes a lot of fragile code-gen that's less resilient to changes. The solution we've adopted in ServiceStack to solve this with minimal code generation and better reusability is to only generate the message-based Typed DTOs for each language but have them able to be used by the same generic JSON ServiceClient.
This approach takes minimal effort since we only need to generate Typed DTOs in each language, which all works the same way, where you use the same generic `JsonServiceClient` (created once per language/platform) that use same methods to make API requests making it easy to for our built-in API Explorer [1] (Live Demo [2]) to auto generate API pages for all 11 supported languages which also supports dynamic languages like JS/TS, Python and PHP with additional type hints [3].
That's interesting — the typed request and response objects that you get with an API SDK are certainly one of the biggest upsides, and I certainly see the benefit of having full control of the transport layer yourself so that you can add any common telemetry / logging / statistics at your leisure.
On the other hand, a benefit of a more complete API is that in typed languages you can tab-complete your way to success. With each endpoint a function and all the requisite configuration (API URL, etc.) bundled in, for basic integrations you may never even have to reference documentation, or if you do, very little of it, as your IDE finds functions/properties for you and you can read documentation right out their docstrings.
Sending Messages also have many benefits over RPC methods [1] that's especially important for evolvable APIs across process boundaries.
The DTOs are still typed so you still get AutoComplete that also include API Docs and Type hints in the generated DTOs since we full control how DTOs are generated and we're able to capture richer type information in the server C# DTOs (used as blueprints to generate DTOs in different languages).
All the information about how to call the API and what it returns is captured in the Request DTOs, and the only thing the Service Clients need is the BaseUrl for where the APIs are hosted. So you could create a higher level SDK client that just needs to inherit the Service Client and hard code its URL, e.g:
class MyClient : JsonServiceClient(BaseUrl) {}
Where they'll also be able to add any helper methods specific to their APIs (e.g. Custom Auth). For the trade-off of not being able to reuse that client to call different APIs and endpoints, but will still share the same base class so you could still create reusable functionality that can be shared across all Service Clients.
Some of the language SDKs were dynamic (e.g. Ruby, Python) and could adapt automatically to small API changes like if a new field was added in a response. Some were not (e.g. Java, Go), and for every API change, someone had to manually make the change to that SDK's codebase (add a field, add a struct, add a function for a new endpoint), get it reviewed and merged, and cut a release. As Stripe got bigger and there were API changes all the time, the only way this was even remotely functional was that we had a couple heroic workhorses that'd watch for changes and open hundreds of PRs a year for them. Honestly, in retrospect, it's amazing this even worked.
Getting everything switched over to a generated solution was an arduous process because the new generated code had to be API-compatible with the existing code so that the cutover to generated bindings didn't break every Stripe user under the sun. We eventually got there, but it took a long time.
I like Stainless' mission because after seeing the crazy maintenance hassle that all of this was at Stripe, I think it makes way more sense to save all that engineering time for your more concerns, and outsource this problem to someone else. A plug-and-play way of getting high quality SDKs in all common languages that get pushed to appropriate repositories for distribution and comes with quality companion documentation.
We've actually had pretty good luck at my current job with open source tools like openapi-generator [1], but this sort of codegen is so convoluted that the sustainability of a pure open source solution makes me a little afraid, and you still end up doing a lot of the last mile work yourself.
---
[1] https://github.com/OpenAPITools/openapi-generator