The biggest reason we started working with Stainless was to migrate from the open source generator process we were struggling with, but actually ended up transitioning our hand-rolled Node/JS SDK first because it's both our most popular SDK and because the fragmentation of the JS ecosystem has been a surprisingly big headache[1]. Went into it for reduced maintenance, came out pleasantly surprised at how useful I was finding my own SDK's features around `for...of` on paginated routes, etc.
Congrats on the launch, Stainless crew!
[1] https://www.mux.com/blog/keeping-up-with-the-node-ish-ecosys...
You can see an example of the Go code Stainless produces here: https://github.com/cloudflare/cloudflare-go
(I'm the Stainless founder)
In the end...ongoing maintenance there was still enough of a pain that we made the decision to outsource it. For just one or two SDKs I think we probably would have kept going with it, though.
[1] https://github.com/OpenAPITools/openapi-generator
[2] https://www.mux.com/blog/an-adventure-in-openapi-v3-api-code...
I've spent more of my time than I'd like to admit managing both OpenAPi spec files [1] and fighting with openapi-generator [2] than any sane person should have to. While it's great having the freedom to change the templates an thus generated SDKs you get with using that sort of approach, it's also super time consuming, and when you have a lot of SDKs (we have 6 generated SDKs), in my experience it needs someone devoted to managing the process, staying up with template changes etc.
Excited to see more SDK languages come to Stainless!
[1] https://www.mux.com/blog/an-adventure-in-openapi-v3-api-code...
(I haven't tried it since then, so it may work now).
Here's an example of a typical RESTful endpoint (Lithic's `client.cards.create()`:
https://github.com/lithic-com/lithic-node/blob/36d4a6a70597e...
Here are some example repos produced by Stainless:
1. https://github.com/openai/openai-node 2. https://github.com/openai/openai-python 3. https://github.com/cloudflare/cloudflare-go 4. https://github.com/Modern-Treasury/modern-treasury-java
Definitely the place to go these days if you have a public API and want it to be as developer friendly as they come.
One thing I do not see with Stainless is also using it to generate the server side of the API.
We use OpenAPI as a design doc, and generate client SDKs and Datos for the server from the same spec. This gives us pretty solid control over interoperability.
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.
---
Separately, as a consumer of many afterthought-ish SDKs for popular services, I’m extremely excited to see their work opening up to more developers. I hope it raises the bar for SDKs everywhere!
I guess my question is, what is the key differentiator that Stainless offers above just using OpenAPI and its huge library of existing generators?
https://buildwithfern.com/ https://liblab.com/ https://www.speakeasyapi.dev/
Between this and fancy API doc generators (Mintlify, etc.), I feel like we're finally getting close to being able to just go from OpenAPI to Stripe-level "polish".
As far as generating the first spec from an existing API I think there was shown here on HN a while ago a tool that can generate the spec from you just "browsing" the API with developer tools or something. Maybe even from a HAR file IIRC.
The amount of runtime code generated per endpoint is typically around 3 short lines – here's an example, also posted elsewhere on this thread: https://github.com/lithic-com/lithic-node/blob/36d4a6a70597e...
Stripe built their own Ruby DSL for exactly this, and it worked great. It looked something like this:
class CreateCustomerMethod < AbstractAPIMethod
endpoint "post /v1/customers"
returns CustomerAPIResource
required :name, String, description: "The name of the customer."
optional :description, String, description: "Additional information about the customer."
def execute
# …
end
end
Unfortunately, I don't know of an open-source equivalent for Ruby; FastAPI in Python is the closest I know of in any language. At Stainless, we're building a TypeScript version of this, but it's still deep in dogfooding.Most people just end up maintaining by hand and using a tool like Optic or Akita to check that it's not wildly out-of-date.
I worked on this extensively inside Azure and I know it is not an easy problem (and with more JSON Schema coming in 3.1/4.0, it is only getting harder). There are a lot of API patterns that you want to expose purpose built client abstractions for. Pagination is a big example. If you stick to OpenAPI, you have to ensure your specs use the patterns your client generator recognizes, and it's not always trivial for authors to know how to express that pattern and for your codegen to infer that pattern. In Azure we tended to rely more on custom extensions to OpenAPI because it made the contract a lot clearer and less error prone, but then you lose interoperability.
One thing to consider - I work on TypeSpec[1], and one of the main reasons we built it is to allow encapsulation and reuse of patterns in a first-class way. So, rather than the contract being "endpoints which declare parameters and/or return types with these shapes are inferred to be paginated endpoints", the contract can be "use Page<T>" and the emitted OpenAPI conforms to the contract without effort. It would be fun to see a Stainless TypeSpec library for all the patterns your codegen supports!
Nothing fancy but maybe more helpful than picking through SEO articles in you’re looking for options.
Generated code has a bad rap for results that are not idiomatic or user-friendly, but I think it's clear that it doesn't have to be that way—getting it right just takes a lot of care and effort that is typically overlooked.
Stainless is definitely best-in-class here as far as I've seen.
Stainless: The standout for maturity and idiomatic code generation. While method signatures across products may look the same, Stainless shines during developing & debugging - making their codebase easier to navigate. They have a practical separation of SDK configuration from OpenAPI specification, setting it apart from others reliant on OpenAPI overlays. The Stainless Studio also proved invaluable for refining our OpenAPI specs during our exploration phase.
Fern: Notable for being open-source, though not free. It provides a robust end-to-end Developer Experience, covering everything from SDKs and documentation to Postman collections. Fern uses an internal "Fern Definition" language (~ think Smithy), it's optional and enables capabilities like merging multiple specs, but is adding another layer to navigate in our view.
Speakeasy: Moves at a fast pace, which could be a double-edged sword. Rapid iterations may lead to frequent, potentially disruptive updates for customers. A minor gripe was the inclusion of "Speakeasy" in class names, which felt overly branded.
Liblab: Initially limited in language support, they've expanded but still lag behind in establishing a strong customer base, which might be a red flag for some adopters.
BTW all folks are very approachable and collaborative!
I agree, I love them in theory, but wrangling mustache templates is a hassle. They end up super coupled to the code that populates them which makes them a bear to modify.
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].
[1] https://docs.servicestack.net/api-explorer#code-tab
[2] https://vue-vite-api.jamstacks.net/ui/QueryBookings?tab=code
[3] https://docs.servicestack.net/add-servicestack-reference
From what I've seen, a lot of API developers find that openapi-generator flat-out breaks or produces broken code for their OpenAPI spec (varies language-to-language of course).
Beyond hitting the "it just works" mark more often, some other key differentiators:
1. much easier to configure & customize
2. auto-retry with exponential backoff (this is huge for our customers)
3. auto-pagination
4. overall ergonomics of using the SDK
5. internals that look handwritten (some people care, others don't)
6. one-click releases to github & package managers w/ semver, changelogs, etc
7. careful handling of edge cases (eg, will adding a new enum variant cause your Java client to crash?)
8. a long tail of more advanced features, like webhook signature verification, streaming, etc
Thanks for the question – hope this helps :)
Yeah, Stainless is more focused on public SDKs, which can be a lot trickier and more demanding.
Some more details replying to your other comment here: >>40148629
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.
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.
[1] https://docs.servicestack.net/advantages-of-message-based-we...
Is Stainless similar, different?
Really impressive stuff, I can't imagine doing all this with a team as lean as yours.
I resonate with a lot of what is said in the comments. Last mile API tooling is underrated and most companies don't have the deep API platform expertise to build from scratch. The OSS options in the space proved SDK generation could be done but the actual product that you got in hand still needed a lot of work to be enterprise ready. The code was poor quality, wasn't customisable and there was no change management story. We're seeing a new wave of work in this space that's making api platform engineering much more accessible.
The API devex story starts with SDK creation and maintenance but there is a lot more to come!
On the Speakeasy branded classes. We got rid of that some time ago based on customer feedback. Check out our new TS generator https://www.speakeasyapi.dev/post/how-we-built-universal-ts
Their team is great to work with, which helps make it worth it.
(Note that a lot of the SSE type stuff we do generally so we're able to support a variety of AI companies and other streaming use cases, though disclaimer it is a premium feature)
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.
Curious, wouldn’t this be great for folks with internal APIs as well?
Team 1 can consume team 2’s API with an SDK instead of a raw REST client.
zone, err: = client.Zones.New(context.Background(), zones.ZoneNewParams {
Account: cloudflare.F(zones.ZoneNewParamsAccount {
ID: cloudflare.F("023e105f4ecef8ad9ca31a8372d0c353"),
}),
Name: cloudflare.F("example.com"),
Type: cloudflare.F(zones.ZoneNewParamsTypeFull),
})