Should you hand-write your SDKs or generate them?
The honest answer depends on how many languages you ship and how often your API changes. For one language and a stable API, hand-writing wins.

The case for hand-writing is better than generation advocates admit, and it has a shape.
A hand-written client can be idiomatic in ways a generator cannot reach. It can name a method after what your users call the thing rather than after your operationId. It can collapse three endpoints into one convenience call because that is how the workflow actually goes. It can have a helper that does the retry-and-poll dance your API requires and your spec does not describe.
If you ship one language, your API changes a few times a year, and you have someone who cares about that library, hand-writing produces a better artifact. That is the honest version.
The problem is that almost nobody stays in that situation.
What actually breaks it
Two variables, and they multiply.
Languages. One library is a craft project. Six is a maintenance obligation, and the sixth is always the one nobody on the team writes daily. Your Rust SDK gets written by whoever was free, and it gets updated at whatever rate that person has time for.
Change rate. An API that changes monthly means every change lands six times. Miss one and you have SDKs that disagree about what your API does, which is worse than having fewer SDKs.
Multiply them and you get the failure everyone recognises: the TypeScript client is current because that is what the team uses, Python is one version behind, and Ruby has an open issue from March saying a field is missing.
The 2026 drift data puts numbers on the pressure. 41% of APIs see schema drift within thirty days, and 86% of those events are field additions, which are exactly the changes nobody remembers to propagate because nothing breaks when you skip them. Six hand-written clients means six chances to skip.
What generation is actually good at
Not the method names. The parts nobody enjoys writing and everybody gets subtly wrong on the fifth language:
Consistency across languages. One retry policy, one pagination contract, one auth mechanism, emitted six times. Teams that hand-write six clients write six retry policies, and by year two they disagree about which status codes are safe to repeat.
The unglamorous correctness. Backoff with jitter, per-attempt timeouts, idempotency keys on writes, typed errors split by cause. Every client needs these and hand-written ones acquire them incrementally after incidents.
Keeping up. A field added to a response appears in every language on the next generation, without anyone remembering.
Reaching languages you do not staff. You probably do not have a Dart developer. Your Flutter users still want a client.
What generation is bad at, and what to do about it
This is the part worth being straight about, because it is where the objection lives.
Generated names can be bad. They come from your operationIds, and if those were written for a linter rather than a call site you get client.actions.actionsActionsGet(). The fix is not to hand-write the client, it is to fix the ids or override the generated method name per endpoint. Both are cheaper than maintaining a library.
Generators do not know your workflows. If using your API means "create, then poll until ready, then fetch", no generator will produce the helper that does it. That helper is real value and it has to be written.
Generated code can be ugly. Sometimes true, and it matters less than people think because most consumers read the method signature and the types, not the transport layer.
The synthesis most teams land on is the useful one: generate the client, hand-write a thin layer of workflow helpers on top of it. The generated part tracks your API automatically and the hand-written part is small enough that one person can own it in every language you care about. You get idiom where idiom matters and coverage everywhere else.
The decision, plainly
Hand-write if you ship one or two languages, your API is stable, and someone owns the library as part of their job rather than as an occasional errand.
Generate if you ship three or more languages, or your API changes more than quarterly, or you want to support a language nobody on your team writes.
Generate plus a helper layer if your API has multi-step workflows worth wrapping, which is most APIs past a certain size.
The crossover is lower than instinct suggests, because the cost of a hand-written SDK is not the writing. It is the year of small updates afterwards, paid in the languages you are least equipped to maintain.
The question that settles it
Ask what happens the next time you add a required parameter to an endpoint.
If the answer is "I update the spec and every client regenerates", you are fine. If it is "I update the spec, then open six repositories", you already know which of the six will be late, and your users will find out before you do.
If you want to see what generated output actually looks like before deciding, the OpenAPI to TypeScript SDK, Python, Go and Java pages show the call site each one produces from the same document, including the default client shape and what you can change about it.
And whichever way you decide, the spec audit is worth running first, because a document that scores badly produces a bad generated client and a hand-written one built on the same missing schemas will have the same gaps, only hidden behind code somebody wrote.



