Introducing ExtractBench, the most comprehensive document extraction benchmark. Learn More →

Au Revoir, Stainless

Background

For the last couple of years, Stainless has quietly powered an important part of the developer experience at LlamaIndex: our generated SDKs.

We picked Stainless because building an API is only half the work. If you want developers to actually enjoy using that API, you need great SDKs too—and maintaining high-quality, idiomatic clients across Python, TypeScript, Go, and other languages gets expensive surprisingly quickly.

Stainless gave us a much better answer.

Now, Stainless is joining Anthropic, and its hosted products—including the SDK generator—are being wound down. So as we migrate off the platform, we wanted to share why we chose Stainless in the first place, what worked well for us, and what we're taking away from the experience.

The problem: SDKs are deceptively hard

LlamaParse is an API-first product.

As the API grew, we wanted users to have the same experience whether they were interacting with it directly over HTTP or through one of our client libraries.

The obvious approach was to generate SDKs from our OpenAPI specification.

The problem is that generating an SDK is easy; generating a good, maintainable SDK is hard.

You can point most OpenAPI generators at a spec and get thousands of lines of technically functional code. But that's very different from something you'd actually want to publish as your official client library.

A good SDK needs to feel native to its language. It needs:

  • sensible resource hierarchies
  • predictable method names
  • good typing
  • pagination
  • retries
  • error handling
  • async support
  • package management
  • documentation
  • a release process that doesn't become somebody's full-time job.

And every API change needs to propagate through all of that.

We didn't want to build an SDK-generation company inside LlamaIndex.

The Decision Process

When we evaluated Stainless, one thing stood out immediately: the output looked like SDKs we would have wanted to write ourselves.

That's not entirely accidental. Stainless came out of the experience of building SDK infrastructure at Stripe and went on to generate SDKs for companies including OpenAI, Anthropic, and Cloudflare.

For us, there were a few especially important pieces.

1. Idiomatic SDKs instead of OpenAPI-shaped SDKs

A common failure mode of code generation is leaking the structure of your OpenAPI document directly into the user experience.

Your API specification is an implementation artifact. It shouldn't dictate whether a developer has to call something like:

client.pipeline_api_v1_pipeline_jobs_create_pipeline_job(...)

when the natural interface is closer to:

client.pipelines.create(...)

Stainless gave us a layer between the raw OpenAPI schema and the public SDK surface.

That turned out to be extremely valuable.

2. One API, multiple languages

We could define the API once and generate clients across languages rather than maintaining parallel implementations.

That matters a lot for a relatively small engineering team.

Every new endpoint didn't turn into:

update the Python SDK → update the TypeScript SDK → update the Go SDK → publish everything → realize one implementation behaves differently → fix it again.

Instead, the API definition became much closer to the source of truth.

3. The generated code was still real code

Our SDK repositories aren’t opaque binaries sitting behind a vendor API. They were normal source code that could be inspected, modified, tested, and extended.

Stainless explicitly gives customers ownership of the SDKs they've generated, which also matters quite a bit now that the hosted service is being wound down.

We could add custom behavior where necessary without abandoning code generation entirely.

4. It forced us to improve our API

This might have been the most underrated benefit.

Once your OpenAPI spec generates a public SDK, inconsistencies stop being abstract API-design problems.

  • Naming matters.
  • Schemas matter.
  • Nullability matters.
  • Whether two endpoints model the same concept differently matters.

A generator acts like a very opinionated consumer of your API. If something is awkward in the SDK, there's a decent chance something is awkward in the underlying API too.

Using Stainless pushed us toward treating our OpenAPI definition as a first-class product artifact rather than documentation generated as an afterthought.

What we built with it

Today, Stainless-generated tooling sits behind parts of the LlamaParse developer experience, including our official API clients and CLI tooling. When we launched our updated API, it greatly simplified the developer experience and maintainability.

We got additional languages, a CLI, and much more maintainable documentation.

The important part wasn't simply that Stainless produced code; it gave us an automated workflow:

API implementation → OpenAPI → SDK definition → generated clients → package releases

That made it possible for our API to evolve rapidly without requiring us to maintain an equivalent amount of handwritten SDK code.

And LlamaCloud evolves a lot.

For a fast-moving API company, that leverage was invaluable.

The interesting part of migrating away

On May 18, 2026, Stainless announced that the team was joining Anthropic.

As part of that move, Stainless said it would wind down all of its hosted products, including the SDK generator. New signups, projects, and SDKs stopped immediately, with existing customers given a transition period.

It's a somewhat unusual ending for a developer-tooling product we were very happy with, but it's also hard not to see the logic.

The obvious thought when replacing an SDK generator is:

We have OpenAPI. Just run it through another generator.

Unfortunately, it's not quite that simple.

Once customers depend on an SDK, the generated interface itself becomes an API.

Users don't depend on your OpenAPI operation IDs. They depend on:

client.files.upload()

They depend on particular model names, namespaces, parameter names, return types, pagination behavior, and exceptions.

Stainless encoded some of those decisions outside of OpenAPI in its own configuration.

That means changing generators isn't just a build-system migration. Done carelessly, it's an API migration for every developer using your SDK.

That's probably the biggest lesson we're taking from this transition:

Generated code doesn't eliminate API surface area. It creates another one that you need to own.

So our goal as we move forward is not simply to find something capable of turning OpenAPI into Python and TypeScript.

It's to preserve the interfaces our users already rely on while making the generation pipeline something we can sustainably operate for the long term.

What we'd choose again

There are other great SDK tools we evaluated like Fern (acq. by Postman), HeyAPI, and Speakeasy. Despite having to migrate now, we'd still make the decision to use Stainless.

Great SDK tooling lets a relatively small team ship and maintain a developer experience that otherwise would have required substantially more engineering effort.

More importantly, it changed how we thought about our API.

We came away believing even more strongly in a few principles:

  • Your OpenAPI spec should be a real source of truth.
  • SDK design is product design.
  • Generated SDKs should feel handwritten.
  • The public surface of generated code belongs to you, not your generator.

And perhaps most importantly:

  • Developer experience is infrastructure.

The best infrastructure is often the stuff your users never have to think about.

For a brief time, Stainless was exactly that for us.

Thanks to Alex and the entire Stainless team for building a great product—and for saving us from writing a lot of SDK code.

Au revoir, Stainless. 👋

We'll see you on the other side of the API.

Related articles

PortableText [components.type] is missing "undefined"

Start building your first document agent today

PortableText [components.type] is missing "undefined"