OpenAPI Generator vs Kiota vs Speakeasy
This comparison belongs to Client SDK Generation Pipelines in the API Design Fundamentals & Architecture reference. All three tools read an OpenAPI document and emit a client, and all three will produce something that compiles. They differ in what the output feels like to use, how you customise it, and how much of the release pipeline they own — which is what this page compares.
The decision trigger
| Situation | Points toward |
|---|---|
| You need clients in eight languages, including niche ones | OpenAPI Generator |
Your document is OpenAPI 3.1 with type: [T, "null"] throughout |
Kiota or Speakeasy |
| The SDK is a public product and ergonomics are a selling point | Speakeasy |
| You want a fluent, request-builder call style | Kiota |
| You must vendor the generator into an air-gapped build | OpenAPI Generator or Kiota |
| You want publishing, changelogs and versioning handled for you | Speakeasy |
| You already run a JVM-based build toolchain | OpenAPI Generator |
Side-by-side comparison
| Dimension | OpenAPI Generator | Kiota | Speakeasy |
|---|---|---|---|
| Licence / cost | Apache 2.0, free | MIT, free | Commercial, free tier |
| Runtime required | JVM (or Node wrapper, or Docker) | .NET CLI (or Docker) | Single binary CLI |
| Language coverage | ~50 generators | ~8 first-class | ~10 first-class |
| OpenAPI 3.1 support | varies per template | native | native |
| Customisation model | Mustache template overrides | limited; abstractions library | config file + overlays |
| Call style | one class per tag | fluent request builders | one namespace per tag |
| Error model | thrown exception per language | typed error union | typed error classes |
| Publishing automation | you build it | you build it | built in |
| Output determinism | high, once timestamps are off | high | high |
| Best fit | broad coverage, full control | strongly typed graph-style APIs | public product SDKs |
The rows that actually decide it are usually language coverage and customisation model. If you need a Rust and a PHP client, the choice makes itself. If you need one TypeScript client whose ergonomics are part of your developer marketing, the calculation is entirely different.
The same operation, three clients
The clearest way to choose is to read the call site each tool produces for one operation — GET /invoices/{invoiceId} with operationId: getInvoice and tags: [invoices].
// OpenAPI Generator (typescript-fetch): one API class per tag
import { Configuration, InvoicesApi } from "./sdk/typescript";
const invoices = new InvoicesApi(new Configuration({
basePath: "https://api.example.com/v4",
headers: { Authorization: `Bearer ${token}` },
}));
const invoice = await invoices.getInvoice({ invoiceId: "inv_123" });
// Kiota: a fluent request builder mirroring the URL structure
import { createBillingClient } from "./sdk/kiota";
const client = createBillingClient(adapter);
const invoice = await client.invoices.byInvoiceId("inv_123").get();
// Speakeasy: namespaced methods with typed error unions
import { Billing } from "./sdk/speakeasy";
const billing = new Billing({ bearerAuth: token });
const invoice = await billing.invoices.get({ invoiceId: "inv_123" });
Kiota’s builder style pays off when URLs are deeply nested and clients traverse them dynamically; it costs you a longer call chain for a flat CRUD API. The other two converge on the same shape, differing mainly in error handling: Speakeasy tends to surface documented error responses as typed values, while OpenAPI Generator throws a language-native exception carrying the raw response — which puts more of the burden on your wrapper to map an RFC 7807 Problem+JSON body into something callers can branch on.
Evaluating them on your own document
Do not choose from a comparison table — including this one. Run all three against your real spec and read the diff. Twenty minutes of this saves a year of regret.
# 1. Bundle once so every generator reads the identical input.
npx @redocly/cli bundle openapi.yaml -o dist/openapi.yaml --dereferenced=false
# 2. OpenAPI Generator
npx @openapitools/[email protected] generate \
-i dist/openapi.yaml -g typescript-fetch -o /tmp/eval/oag \
--additional-properties=hideGenerationTimestamp=true
# 3. Kiota
dotnet tool install --global Microsoft.OpenApi.Kiota
kiota generate -l typescript -d dist/openapi.yaml -c BillingClient -o /tmp/eval/kiota
# 4. Speakeasy
brew install speakeasy-api/tap/speakeasy # or the install script
speakeasy generate sdk --schema dist/openapi.yaml --lang typescript --out /tmp/eval/speakeasy
# 5. Compare what each one actually produced.
for d in oag kiota speakeasy; do
printf '%-12s files=%-5s lines=%s\n' "$d" \
"$(find /tmp/eval/$d -type f | wc -l)" \
"$(find /tmp/eval/$d -type f -name '*.ts' -exec cat {} + | wc -l)"
done
Then read, in each output: the method name for one operation, the type of one nullable field, the shape of one discriminated union, and what happens on a documented 404. Those four answers predict most of your future friction.
Standard compliance
None of these tools implements the whole specification, and the gaps cluster in predictable places:
| Spec feature | Clause | Typical support |
|---|---|---|
oneOf + discriminator |
OAS 3.1 §4.8.25 | good in all three when mapping is explicit |
type: [T, "null"] |
JSON Schema 2020-12 §6.1.1 | native in Kiota/Speakeasy; template-dependent in OpenAPI Generator |
style / explode for arrays |
OAS 3.1 §4.8.12 | good, but verify explode: false |
unevaluatedProperties |
JSON Schema Core 2020-12 §11.3 | widely unimplemented |
webhooks |
OAS 3.1 §4.1 | rarely generated as client code |
links |
OAS 3.1 §4.8.20 | rarely generated |
The unimplemented rows are not blockers, but they should shape what you put in the contract: a document that leans on unevaluatedProperties for validation is fine on the server and invisible to every client generator.
The four probes above map onto concrete failure modes, and each tool fails differently:
What changing generators costs
Switching is a full rewrite of the SDK’s public surface: method names, type names, error types, package layout and initialisation all change. For consumers that is a major version bump and a migration guide, not a patch release — plan it with the same care as an API version transition described in Breaking Change Detection. The mitigation is the wrapper layer: if the surface consumers import is hand-written and delegates to the generated code, a generator swap becomes an internal change and the public API stays put.
Common mistakes
| Mistake | Correct approach |
|---|---|
| Choosing from a feature table alone | Generate with all three from your real spec and read the output |
| Assuming 3.1 support because the README says “OpenAPI 3” | Test a type: [T, "null"] field and a discriminated union |
| Exposing generated types directly to consumers | Wrap them so a generator swap stays internal |
| Running an unpinned generator in CI | Pin the version; treat upgrades as reviewed changes |
| Picking a generator before the spec is linted | Fix operationIds and component names first — they dominate output quality |
| Ignoring publishing when comparing | Factor in the release automation you will otherwise build yourself |
FAQ
Which SDK generator handles OpenAPI 3.1 documents best?
Kiota and Speakeasy were built after 3.1 landed and target the JSON Schema 2020-12 dialect natively. OpenAPI Generator’s support depends on the individual language template, and several templates historically down-converted 3.1 documents, silently dropping type arrays that include "null" and producing non-nullable fields. The test takes five minutes: put one nullable field and one discriminated union in a scratch document, generate, and look at the emitted types. Whichever tool preserves both is the one that understands your contract.
Can I switch generators later without breaking consumers?
Not transparently. Every tool names methods, types and packages differently, structures errors differently, and initialises the client differently, so the generated surface is not portable. If you expect to re-evaluate, insulate consumers now: publish a thin hand-written façade that delegates to the generated client, and keep the generated code an implementation detail. Then a switch is an internal refactor plus a patch release, rather than a major version and a migration guide.
Do I need a paid tool to generate good SDKs?
No. Output quality is dominated by contract quality — explicit operationIds, named components, documented error schemas, correct required lists — and those cost nothing. The open-source tools generate perfectly serviceable clients from a well-specified document. What paid tooling buys is the surrounding machinery: publishing to several registries, generated changelogs, usage snippets, docs integration and support. That is worth money when the SDK is a public product carrying your developer experience, and hard to justify for an internal service-to-service client.
Related
- Client SDK Generation Pipelines — up-link: the pipeline the chosen generator plugs into
- API Design Fundamentals & Architecture — up-link: the parent reference for contract-first design
- Naming Operation IDs for Readable SDK Methods — the input that most affects output quality in every generator
- Publishing Generated SDKs from GitHub Actions — the release automation the free tools leave to you
- Discriminator Mapping in Generated Clients — the union-handling probe worth running on each candidate