--- title: Speakeasy vs Stainless: SDK generation comparison | Stainless docs description: A detailed comparison of Stainless and Speakeasy for SDK generation: features, language support, configuration, documentation, and more. --- Choosing an SDK generation platform is a significant decision that affects your developer experience, maintenance burden, and API adoption. This page provides an honest, detailed comparison of Stainless and Speakeasy to help you evaluate which platform best fits your needs. Both platforms generate SDKs from OpenAPI specifications and support multiple languages. They differ in their approach to SDK design, configuration, documentation, and what ships out of the box versus what requires paid add-ons. **Why teams choose Stainless:** - **Battle-tested at scale**: Stainless SDKs are downloaded over 130 million times per week and trusted by API leaders like Anthropic, Cloudflare, Google, and OpenAI. That scale means more edge cases solved and more production-hardened output than any other codegen platform. - **Flexible and customizable**: Add custom code anywhere in your SDKs (persists across regeneration), fully customize your generated docs, and fine-tune naming, design, and deployment to match your needs. - **Complete API platform**: SDKs, docs, CLI tools, Terraform providers, and MCP servers all generate from one OpenAPI spec and one config file, so each additional target you adopt makes the whole platform more valuable. This comparison is based on publicly available documentation and [public GitHub repositories](https://github.com/speakeasy-sdks) from both platforms as of February 2026. Features and capabilities may change; check each platform’s documentation for the latest information. ## Codegen targets Both Stainless and Speakeasy support SDKs for the most popular programming languages. The key differences lie in additional codegen targets beyond traditional SDKs. Stainless generates more than just language SDKs. Terraform providers, CLI tools, and MCP servers extend how your users interact with your API beyond traditional SDK integrations. | Language | Stainless | Speakeasy | | -------------------------------------------------- | --------------- | --------- | | [TypeScript](/docs/targets/typescript/index.md) | ✅ | ✅ | | [Python](/docs/targets/python/index.md) | ✅ | ✅ | | [Go](/docs/targets/go/index.md) | ✅ | ✅ | | [Java](/docs/targets/java/index.md) | ✅ | ✅ | | [Kotlin](/docs/targets/kotlin/index.md) | ✅ | ❌ | | [C# / .NET / Unity](/docs/targets/csharp/index.md) | ✅ | ✅ | | [Ruby](/docs/targets/ruby/index.md) | ✅ | ✅ | | [PHP](/docs/targets/php/index.md) | ✅ | ✅ | | [Terraform](/docs/targets/terraform/index.md) | ✅ | ✅ | | [CLI](/docs/targets/cli/index.md) | ✅ | ⚠️ Beta | | [SQL](/docs/targets/sql/index.md) | ⚠️ Experimental | ❌ | | [MCP server](/docs/mcp/index.md) | ✅ | ✅ | Stainless is the only platform that generates Kotlin SDKs from your OpenAPI spec, and both platforms can generate CLI tools (Speakeasy’s CLI generation is in beta). The Kotlin SDK has a distinct public API from the Java SDK, using nullable types instead of `Optional`, `Sequence` instead of `Stream`, and `suspend` functions instead of `CompletableFuture`. ## SDK features The features built into your generated SDKs determine how much boilerplate your API consumers write and how robust their integrations are out of the box. Stainless SDKs ship with sensible defaults like rich error hierarchies, forward-compatible response handling, and per-call raw responses, so your users write less boilerplate and build more resilient integrations. | Feature | Stainless | Speakeasy | | ----------------------- | ------------------------------ | ------------------------------------------ | | Authentication | ✅ | ✅ (incl. OAuth 2.0, mTLS via hooks) | | Retries with backoff | ✅ (with idempotency keys) | ✅ | | Timeouts | ✅ | ✅ | | Auto-pagination | ✅ | ✅ | | Error handling | ✅ Rich error hierarchy | ⚠️ Per-status classes when schemas defined | | Raw responses | ✅ Per-call | ⚠️ SDK-level configuration | | Response validation | ✅ | ⚠️ Not forward compatible by default | | Forward compatibility | ✅ Extra params on every method | ⚠️ Response-side only | | Webhook deserialization | ✅ | ✅ | | WebSocket support | ⚠️ Beta | ❌ | | Server-sent events | ✅ All languages | ⚠️ 7 languages | | Automated SDK tests | ✅ Included | ✅ | ### Error handling Stainless generates a rich error hierarchy with typed access to error details. Speakeasy generates per-status error classes only when error response schemas are defined in the spec. - [Stainless TypeScript](#tab-panel-29) - [Speakeasy TypeScript](#tab-panel-30) ``` import Example, { APIError, APIConnectionError, APIConnectionTimeoutError, APIUserAbortError, BadRequestError, AuthenticationError, PermissionDeniedError, NotFoundError, ConflictError, UnprocessableEntityError, RateLimitError, InternalServerError, ExampleError, } from "stainless-demo-sdk"; const client = new Example({ apiKey: "..." }); async function createWidget() { try { const widget = await client.widgets.create({ name: "Test" }); return widget; } catch (err) { // --- HTTP status code errors (most specific first) --- if (err instanceof BadRequestError) { // 400 - invalid request parameters console.error("Bad request:", err.status, err.message); console.error("Error body:", err.error); return; } if (err instanceof AuthenticationError) { // 401 - missing or invalid credentials console.error("Auth failed:", err.message); return; } if (err instanceof PermissionDeniedError) { // 403 - insufficient permissions console.error("Forbidden:", err.message); return; } if (err instanceof NotFoundError) { // 404 - resource not found console.error("Not found:", err.message); return; } if (err instanceof ConflictError) { // 409 - conflicts with current resource state console.error("Conflict:", err.message); return; } if (err instanceof UnprocessableEntityError) { // 422 - validation failed console.error("Validation error:", err.message, err.error); return; } if (err instanceof RateLimitError) { // 429 - rate limited const retryAfter = err.headers?.get("retry-after"); console.error("Rate limited. Retry after:", retryAfter); return; } if (err instanceof InternalServerError) { // 5xx - server error console.error("Server error:", err.status, err.message); return; } // --- Connection errors --- if (err instanceof APIConnectionTimeoutError) { // Request timed out (subclass of APIConnectionError, check first) console.error("Timeout:", err.message); return; } if (err instanceof APIConnectionError) { // Network failure, DNS resolution, etc. console.error("Connection failed:", err.message, err.cause); return; } // --- User abort --- if (err instanceof APIUserAbortError) { // Request cancelled via AbortSignal console.error("Request aborted:", err.message); return; } // --- Catch-all for any other API error --- if (err instanceof APIError) { // Unexpected status code not covered above console.error("API error:", err.status, err.message); console.error("Headers:", err.headers); console.error("Body:", err.error); return; } // --- Base SDK error (non-API) --- if (err instanceof ExampleError) { console.error("SDK error:", err.message); return; } throw err; // Re-throw unknown errors } } ``` ``` import { DemoCore, DemoError, ConnectionError, RequestTimeoutError, RequestAbortedError, InvalidRequestError, UnexpectedClientError, ResponseValidationError, HTTPValidationError, ErrorResponse, SDKValidationError, } from "speakeasy-demo-sdk"; async function createWidget() { // Speakeasy returns a Result - no try/catch needed const result = await client.widgets.create({ name: "Test" }); if (!result.ok) { const err = result.error; // --- Input validation (before request is sent) --- if (err instanceof SDKValidationError) { console.error("Input validation failed:", err.pretty()); return; } // --- Network/request errors --- if (err instanceof RequestTimeoutError) { console.error("Timeout:", err.message); return; } if (err instanceof RequestAbortedError) { console.error("Request aborted:", err.message); return; } if (err instanceof ConnectionError) { console.error("Connection failed:", err.message, err.cause); return; } if (err instanceof InvalidRequestError) { console.error("Invalid request:", err.message); return; } if (err instanceof UnexpectedClientError) { console.error("Unexpected SDK error:", err.message, err.cause); return; } // --- HTTP response errors (DemoError subclasses) --- if (err instanceof ResponseValidationError) { // Response shape didn't match the expected schema console.error("Response validation failed:", err.pretty()); console.error("Status:", err.statusCode); return; } if (err instanceof HTTPValidationError) { // 422 - server-side field validation error console.error("Validation error:", err.message); console.error("Details:", err.detail); return; } if (err instanceof ErrorResponse) { // Structured API error with machine-readable type console.error("API error:", err.error, err.message); if (err.suggestion) console.error("Suggestion:", err.suggestion); if (err.documentationUrl) console.error("Docs:", err.documentationUrl); return; } if (err instanceof DemoError) { // Catch-all for any other HTTP error console.error("HTTP error:", err.statusCode, err.message); console.error("Body:", err.body); return; } throw err; // Re-throw unknown errors } return result.value; } ``` ### Forward compatibility Stainless SDKs are designed with evolution in mind so that you can update your APIs easier. For example, SDKs preserve unknown fields from API responses, so your users can access new fields before updating to the latest SDK version. - [Stainless TypeScript](#tab-panel-31) - [Speakeasy TypeScript](#tab-panel-32) ``` // Stainless preserves unknown fields on responses - new API fields are // accessible immediately, before the SDK is updated const user = await client.users.get('123'); console.log(user.newField); // accessible even before SDK update // Extra params are also forwarded on every request method const widget = await client.widgets.create({ name: "Test", // @ts-ignore - forward extra request params to the API newParam: "value", }); ``` ``` // Speakeasy uses Zod to validate responses against generated schemas. // Unknown fields (new API fields not in the schema) are stripped by default. const result = await client.users.get({ userId: '123' }); if (result.ok) { const user = result.value; console.log(user.newField); // undefined - Zod stripped the unknown field // TypeScript also won't allow this without a cast: // Property 'newField' does not exist on type 'User' // To access unknown fields, parse the raw response manually: const raw = await result.rawResponse.json(); console.log(raw.newField); // accessible, but requires SDK update for typed access } ``` ## SDK design The design of a generated SDK directly affects how quickly developers can adopt your API and how confidently they can build on it. Well-designed SDKs reduce friction by providing clear types, idiomatic patterns, and consistent behavior across languages. Stainless invests in clean type consolidation and compile-time safety, producing SDKs that feel hand-written while keeping bundle sizes small by avoiding runtime validation libraries. | Aspect | Stainless | Speakeasy | | ---------------------- | --------------------------- | ---------------------------- | | Type consolidation | ✅ Merges equivalent schemas | ⚠️ Direct OpenAPI mapping | | Validation | Compile-time types | Zod (TS) / Pydantic (Python) | | Sync and async clients | ✅ | ✅ (with configuration) | **Type safety approach** Stainless and Speakeasy take different approaches to type safety. Stainless relies on compile-time type checking and generates clean type hierarchies that consolidate equivalent OpenAPI schemas. Speakeasy uses runtime validation libraries like Zod (TypeScript) and Pydantic (Python) to validate responses at runtime. Stainless intentionally avoids runtime request validation to keep bundle sizes small, preserve forward compatibility, and avoid rejecting valid requests when API constraints change. This is particularly important for frontend use cases and edge runtimes where code size matters. See the [FAQ](/docs/resources/faq#does-stainless-support-runtime-request-validation/index.md) for more on this design decision. ## Workflow How you configure SDK generation and integrate it into your development workflow affects how quickly you can iterate on your SDKs. A single configuration file and semantic merge for custom code let teams iterate faster without worrying about losing their customizations or managing multiple config files. | Aspect | Stainless | Speakeasy | | --------------------- | ----------------------------------------- | -------------------------------- | | Configuration | 1 (`stainless.yml`) | 2+ (`gen.yaml`, `workflow.yaml`) | | Custom code | ✅ Semantic merge | ✅ Code regions and hooks | | Local development | ✅ CLI, VS Code Extension, LSP, MCP server | ✅ CLI | | OpenAPI customization | Transforms | Overlays | | CI/CD integration | GitHub Actions | GitHub Actions | Stainless uses a single [`stainless.yml`](/docs/reference/config/index.md) file to define your SDK configuration: resources, methods, pagination, authentication, publishing targets, and more. Speakeasy splits configuration across `gen.yaml` (generation settings), `.speakeasy/workflow.yaml` (CI/CD workflow), and `x-speakeasy-*` OpenAPI extensions for feature-specific configuration like retries, pagination, and error handling. ### Validation Stainless shows [diagnostics](/docs/reference/diagnostics/index.md) in the Studio and CLI output if we detect problems with or potential improvements in your OpenAPI spec or Stainless config. An AI agent based workflow helps you fix diagnostics in your OpenAPI spec and config, and you can also use the [Stainless VS Code extension](/docs/guides/iterate-with-lsp/index.md) to get diagnostics feedback directly in your editor. \~193 diagnostics span across 23 categories, covering issues from OpenAPI schema problems to suboptimal SDK design patterns. Speakeasy provides a linter that validates OpenAPI 3.x documents for correctness, style, and SDK generation readiness. It is built on the open-source `speakeasy-api/openapi` linter framework and runs using the `speakeasy-recommended` ruleset by default, which can be extended with additional rulesets. ### Custom code Both platforms allow you to add custom code to generated SDKs. You commit custom code directly to your SDK repository, and both platforms preserve your changes across regeneration cycles. See [Add custom code](/docs/guides/add-custom-code/index.md) for details. The difference is subtle. If you have multiple environments, preview builds, or feature branches, Speakeasy’s custom code will create friction. Stainless handles this natively. Stainless: When you create a new branch, custom code from the parent branch is automatically inherited. You can freely add new custom code to your branch and resolve branch-specific merge conflicts as needed. Once you merge your branch, any custom code you added is pulled into the base branch, and conflict resolutions are [rerere’d](https://git-scm.com/book/en/v2/Git-Tools-Rerere) so you do not resolve the same conflict twice. Speakeasy: Custom code state is tracked in a lockfile that points to low-level git objects. Branches that diverge cannot easily be merged because the lockfiles will also diverge, and there is no way to determine a semantically correct resolution. ### OpenAPI customization When your OpenAPI spec needs adjustments for SDK generation, Stainless provides [transforms](/docs/reference/transforms/index.md), a declarative system for modifying your spec during the generation pipeline without changing the source file. Stainless transforms provide feedback when they become unused, preventing configuration from silently drifting out of sync with your spec. Overlays do not provide this feedback. Speakeasy uses [OpenAPI Overlays](https://github.com/OAI/Overlay-Specification), a specification-level standard for modifying OpenAPI documents. Both approaches let you fix incorrect types, add missing fields, and customize the spec without altering the original file. ``` # Stainless transform - warns if path no longer matches transforms: - target: "$.paths['/users'].post.requestBody" update: required: true ``` For a deeper look at how transforms work, see [Stainless Transforms: The Fast Lane for Fixing Imperfect OpenAPI Specs](https://stainless.com/blog/stainless-transforms-the-fast-lane-for-fixing-imperfect-openapi-specs). ## Dependencies The number of runtime dependencies in your generated SDKs affects installation size, security surface area, and compatibility with different environments. Fewer dependencies mean a smaller attack surface, faster installs, and fewer version conflicts for your SDK consumers. | Language | Stainless | Speakeasy | | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | TypeScript | 0 | 1* `zod` | | Python | 6* `httpx` * `pydantic` * `typing-extensions` * `anyio` * `distro` * `sniffio` | 4* `httpcore` * `httpx` * `jsonpath-python` * `pydantic` | | Go | 2* `tidwall/gjson` * `tidwall/sjson` | 2* `spyzhov/ajson` * `stretchr/testify` | | Java | 11* `jackson-core` * `jackson-databind` * `jackson-annotations` * `jackson-datatype-jdk8` * `jackson-datatype-jsr310` * `jackson-module-kotlin` * `error_prone_annotations` * `httpcore5` * `httpclient5` * `okhttp` * `logging-interceptor` | 10* `jackson-annotations` * `jackson-databind` * `jackson-datatype-jsr310` * `jackson-datatype-jdk8` * `jackson-databind-nullable` * `commons-io` * `jakarta.annotation-api` * `slf4j-api` * `json-path` * `reactive-streams` | | Ruby | 2* `cgi` * `connection_pool` | 6* `base64` * `faraday` * `faraday-multipart` * `faraday-retry` * `janeway-jsonpath` * `sorbet-runtime` | | PHP | 5* `php-http/discovery` * `psr/http-client` * `psr/http-client-implementation` * `psr/http-factory-implementation` * `psr/http-message` | 2* `ext-json` * `guzzlehttp/guzzle` | | C# / .NET / Unity | 2–4* `System.Text.Json` * `System.Net.ServerSentEvents` * `Microsoft.SourceLink.GitHub` *(build-time only)* * `System.Collections.Immutable` *(netstandard2.0 only)* | 2* `Newtonsoft.Json` * `NodaTime` | | Terraform | 10–11* `davecgh/go-spew` * `hashicorp/terraform-plugin-docs` * `hashicorp/terraform-plugin-framework` * `hashicorp/terraform-plugin-framework-jsontypes` * `hashicorp/terraform-plugin-framework-timetypes` * `hashicorp/terraform-plugin-framework-validators` * `hashicorp/terraform-plugin-go` * `hashicorp/terraform-plugin-log` * `stainless-sdks/project-sdk-go` *(project's own generated Go SDK)* * `tidwall/gjson` * `tidwall/sjson` | 8* `hashicorp/go-uuid` * `hashicorp/terraform-plugin-docs` * `hashicorp/terraform-plugin-framework` * `hashicorp/terraform-plugin-framework-validators` * `hashicorp/terraform-plugin-go` * `hashicorp/terraform-plugin-log` * `apyzhov/ajson` * `stretchr/testify` | | MCP Server | 15–16* `project-sdk` *(project's own generated TypeScript SDK)* * `@cloudflare/cabidela` * `@modelcontextprotocol/sdk` * `@valtown/deno-http-worker` * `cookie-parser` * `cors` * `express` * `fuse.js` * `jq-web` * `morgan` * `qs` * `typescript` * `yargs` * `zod` * `zod-to-json-schema` * `zod-validation-error` | 5* `@modelcontextprotocol/sdk` * `@stricli/core` * `bun` * `express` * `zod` | ### Size The table below shows the on-disk size of each installed SDK package. Stainless optimizes for size at the language level: TypeScript SDKs support opt-in tree shaking so bundlers can eliminate unused resources, and Python SDKs use lazy loading so imports are deferred until first use. | Language | Stainless | Speakeasy | | ---------- | --------- | --------- | | TypeScript | 1.9M | 81M | | Python | 2.4M | 107M | | Go | 1.4M | 4.0M | | Java | 4.4M | 62M | | Ruby | 2.5M | 10M | | PHP | 1.6M | 58M | | C# / .NET | 2.3M | 2.8M | | Terraform | 1.5M | 1.6M | ## Documentation generation API documentation is a critical part of developer experience. The approach your SDK generator takes to documentation determines how much additional tooling you need. Stainless provides a complete documentation platform with prose content, SDK references, and self-hosting. Speakeasy provides API reference generation only, powered by Scalar. | Feature | Stainless | Speakeasy | | --------------------------------- | -------------------------- | ----------------------------- | | Full documentation site | ✅ | ⚠️ API reference only | | Prose documentation | ✅ | ❌ | | API reference | ✅ Auto-generated | ✅ Auto-generated (via Scalar) | | SDK reference | ✅ Specialized per language | ⚠️ Code samples per-language | | Custom hosting | ✅ | ❌ | | Docs-as-code workflow | ✅ | ❌ | | Code samples for third-party docs | ✅ | ✅ | Stainless includes a complete [documentation platform](/docs/docs-platform/index.md) built on Astro that generates API references, SDK references, and supports custom prose content with a docs-as-code workflow. You can host Stainless-generated docs on your own domain with full control over branding, navigation, and content. Speakeasy offers “Speakeasy Docs” (powered by Scalar), which generates API reference documentation with auto-synced SDK code samples from your OpenAPI spec. Both platforms can generate code snippets (via `x-codeSamples` extensions) that enrich existing OpenAPI-powered documentation. Stainless gives you a full Astro repository that you own and control: add React, Vue, or Svelte components, write custom CSS, and deploy to any static host including Vercel, Netlify, or Cloudflare Pages. Changes go through pull requests and CI/CD, following a true docs-as-code workflow. Speakeasy Docs is focused on API reference and does not support custom prose content, self-hosting, or docs-as-code workflows. For a full documentation site with Speakeasy SDKs, you need to set up and maintain a separate tool. ## MCP servers Model Context Protocol (MCP) servers allow AI agents to interact with your API. Both platforms can generate MCP servers from your OpenAPI spec. The two platforms take different approaches: Stainless generates MCP servers with code execution and documentation search tools, while Speakeasy offers a broader managed platform with Gram for cloud hosting and OAuth support. | Feature | Stainless | Speakeasy | | -------------------------- | --------- | --------- | | Code execution tool | ✅ | ❌ | | Documentation search tool | ✅ | ❌ | | Publishing (npm) | ✅ | ✅ | | Cloudflare Workers hosting | ✅ | ✅ | | Managed cloud platform | ✅ | ✅ | | Custom tool definitions | ❌ | ✅ | | Scope-based filtering | ✅ | ✅ | | OAuth support | ✅ | ✅ | Stainless-generated MCP servers include two tools: a **code execution tool** that runs TypeScript code against your SDK in a sandboxed environment, and a **documentation search tool** that looks up SDK usage information. This approach lets AI assistants write and execute actual SDK code rather than just making raw API calls. Speakeasy generates MCP servers with individual tools for each API operation, derived directly from your OpenAPI spec. You can filter tools by scope (for example, `--scope read` for read-only access) and customize tool names and descriptions using the `x-speakeasy-mcp` extension. Speakeasy also offers Gram, a managed MCP cloud platform with auto-scaling, OAuth support, and custom tool definitions. MCP servers can be deployed as Cloudflare Workers or hosted on the Gram platform. ## Next steps Both Speakeasy and Stainless generate SDKs from OpenAPI specifications, but they differ in runtime features, design quality, developer workflow, and additional capabilities like documentation and CLI generation. The right choice depends on your priorities: whether you value zero-dependency output, hand-written SDK design patterns, or breadth of language support. - [Get started with Stainless](/docs/index.md) - Create your first SDK in minutes - [SDK configuration reference](/docs/reference/config/index.md) - Full configuration documentation