# speakeasy-vs-stainless
Source: https://speakeasy.com/blog/speakeasy-vs-stainless


import { Callout, Table } from "@/mdx/components";
import { FileTree } from "nextra/components";

<Callout title="NOTE" type="info">
  This comparison of Speakeasy & Stainless reflects the current state of both
  platforms as of September 2025. Both companies continue to evolve rapidly,
  particularly around AI-agent integration and MCP capabilities. If you think we
  need to update this post, please let us know!
</Callout>

In this post, we'll compare generated SDKs, as well as the underlying philosophies that guide the development of the two companies. And while we acknowledge that our views may be biased, we'll show our work along with our conclusions so that readers can decide for themselves.

## In short: How is Speakeasy different?

### OpenAPI-native vs OpenAPI-compatible

Speakeasy is OpenAPI-native; Stainless is OpenAPI-compatible. Stainless is built on a custom DSL known as the [Stainless config](https://app.stainlessapi.com/docs/reference/config). This approach requires your team to manage an additional config file. Speakeasy has no intermediary DSL. Your OpenAPI spec is the only source of truth for SDK generation.

Being OpenAPI-native is beneficial for integration into an existing stack. Regardless of the API proxies, server-side code, or specific web framework that you're using, you can plug in Speakeasy's tools. Stainless is doubling down on a vertically integrated approach by building a [backend TypeScript framework](https://github.com/stainless-api/stl-api) which will become the foundation for their solution. Their product will work best when you buy the entire stack, Speakeasy will shine regardless of the other tools in your existing stack.

### Type-safe vs type-faith

There's [more on this topic](#runtime-type-checking) in the technical deep dive below. Speakeasy SDKs guarantee true end-to-end type safety with runtime validation using Zod schemas, meaning that types are generated to validate both request and response objects defined in your API. Stainless SDKs, on the other hand, are mainly type-hinted, not guarding the API from bad inputs.

### Velocity and maturity

[Stainless was founded in 2021](https://www.sequoiacap.com/companies/stainless/) and has expanded its language support to seven languages. By comparison, Speakeasy launched in October 2022 and has released support for ten languages in less time. The Speakeasy platform is also broader, with support for additional generation features, like [webhooks](/post/release-webhooks-support), [React Hooks](/post/release-react-hooks), and [contract testing](/post/release-contract-testing), not supported by Stainless.

Both companies are financially secure, having raised $25M+ in funding.

## Platform

<Table
  title="Platform Comparison"
  columns={[
    { key: "product", header: "Product" },
    { key: "speakeasy", header: "Speakeasy" },
    { key: "stainless", header: "Stainless" },
  ]}
  data={[
    { product: "**SDK generation**", speakeasy: "✅", stainless: "✅" },
    { product: "**MCP server generation**", speakeasy: "✅", stainless: "✅" },
    { product: "**Terraform generation**", speakeasy: "✅", stainless: "✅" },
    { product: "**Docs generation**", speakeasy: "✅", stainless: "❌" },
    { product: "**E2E testing**", speakeasy: "✅", stainless: "❌" },
  ]}
  rowKeyField="product"
/>

## SDK generation

<Table
  title="SDK Language Support"
  columns={[
    { key: "language", header: "Language" },
    { key: "speakeasy", header: "Speakeasy" },
    { key: "stainless", header: "Stainless" },
  ]}
  data={[
    { language: "TypeScript", speakeasy: "✅", stainless: "✅" },
    { language: "Python", speakeasy: "✅", stainless: "✅" },
    { language: "Go", speakeasy: "✅", stainless: "✅" },
    { language: "Java", speakeasy: "✅", stainless: "✅" },
    { language: "Kotlin", speakeasy: "✅", stainless: "✅" },
    { language: "Ruby", speakeasy: "✅", stainless: "✅" },
    { language: "C#", speakeasy: "✅", stainless: "⚠️ incomplete" },
    { language: "PHP", speakeasy: "✅", stainless: "⚠️ incomplete" },
    { language: "Unity", speakeasy: "✅", stainless: "❌" },
  ]}
  rowKeyField="language"
/>

If there's a language you require that we don't support, [add it to our roadmap](https://roadmap.speakeasy.com/roadmap).

### SDK features

Breadth matters, but so does the depth of support for each language. The table below shows the current feature support for Speakeasy and Stainless's SDK generation.

<Table
  title="SDK Feature Comparison"
  columns={[
    { key: "feature", header: "Feature" },
    { key: "speakeasy", header: "Speakeasy" },
    { key: "stainless", header: "Stainless" },
  ]}
  data={[
    { feature: "Retries", speakeasy: "✅", stainless: "✅" },
    { feature: "Pagination", speakeasy: "✅", stainless: "✅" },
    { feature: "Async functions", speakeasy: "✅", stainless: "✅" },
    { feature: "Streaming uploads", speakeasy: "✅", stainless: "✅" },
    { feature: "Custom SDK naming", speakeasy: "✅", stainless: "✅" },
    { feature: "Union types", speakeasy: "✅", stainless: "✅" },
    { feature: "Discriminated union types", speakeasy: "✅", stainless: "❌" },
    {
      feature: "Custom dependency injection",
      speakeasy: "✅",
      stainless: "✅",
    },
    {
      feature: "Server-sent events",
      speakeasy: "✅",
      stainless: "⚠️ non-OpenAPI standard",
    },
    { feature: "Webhooks support", speakeasy: "✅", stainless: "⚠️ incomplete" },
    { feature: "React Hooks", speakeasy: "✅", stainless: "❌" },
    { feature: "OAuth 2.0", speakeasy: "✅", stainless: "❌ (manual)" },
  ]}
  rowKeyField="feature"
/>

## Pricing

In terms of pricing, both Speakeasy and Stainless offer free plans, as well as paid plans for startups and enterprises. The most significant pricing difference is in the enterprise plan. Existing customers indicate that Stainless's enterprise pricing is ~20% higher than Speakeasy's. Of course, this can vary, and we recommend reaching out to both companies for a quote.

<Table
  title="Pricing Comparison"
  columns={[
    { key: "plan", header: "Plan" },
    { key: "speakeasy", header: "Speakeasy" },
    { key: "stainless", header: "Stainless" },
  ]}
  data={[
    {
      plan: "Free",
      speakeasy: "1 target; max 250 endpoints",
      stainless: "Up to 5 SDKs; max 25 endpoints",
    },
    {
      plan: "Starter",
      speakeasy: "N/A",
      stainless: "$250/mo per SDK (35% bulk discount)",
    },
    {
      plan: "Business",
      speakeasy: "$600/mo per SDK; max 250 endpoints",
      stainless: "$800/mo per SDK; max 200 methods",
    },
    { plan: "Enterprise", speakeasy: "Custom pricing", stainless: "Custom pricing; unlimited SDKs" },
  ]}
  rowKeyField="plan"
/>

## Speakeasy vs Stainless: TypeScript SDK comparison

<Callout title="NOTE" type="info">
  For this technical comparison, we'll examine the Cloudflare TypeScript SDK
  (generated by Stainless) and the Vercel SDK (generated by Speakeasy) to
  demonstrate real-world differences between these two approaches to SDK
  generation.
</Callout>

<Table
  title="TypeScript SDK Comparison Summary"
  columns={[
    { key: "aspect", header: "Aspect" },
    { key: "vercel", header: "Vercel SDK (Speakeasy)" },
    { key: "cloudflare", header: "Cloudflare SDK (Stainless)" },
  ]}
  data={[
    { aspect: "**Number of dependencies**", vercel: "1 (Zod only)", cloudflare: "25+ dependencies" },
    { aspect: "**HTTP client**", vercel: "Fetch API", cloudflare: "node-fetch" },
    { aspect: "**Supported runtimes**", vercel: "Node.js, Deno, Bun, browsers", cloudflare: "Node.js, Deno, Bun, browsers" },
    { aspect: "**Runtime validation**", vercel: "✅ Zod schemas", cloudflare: "❌ Compile-time only" },
    { aspect: "**React support**", vercel: "✅ Built-in hooks", cloudflare: "❌ Manual implementation" },
  ]}
  rowKeyField="aspect"
/>

## SDK structure

<div className="md:flex gap-10">
  <div className="md:w-1/2">

### Vercel SDK (Speakeasy)

    <div className="[&>div>div]:block">
    <FileTree>
        <FileTree.File name="README.md" />
        <FileTree.File name="package.json" />
        <FileTree.File name="tsconfig.json" />
        <FileTree.Folder name="docs">
          <FileTree.Folder name="models">
            <FileTree.File name="..." />
          </FileTree.Folder>
          <FileTree.Folder name="sdks">
            <FileTree.File name="..." />
          </FileTree.Folder>
        </FileTree.Folder>
        <FileTree.Folder name="src" defaultOpen>
          <FileTree.File name="index.ts" />
          <FileTree.File name="core.ts" />
          <FileTree.Folder name="hooks">
            <FileTree.File name="hooks.ts" />
            <FileTree.File name="index.ts" />
            <FileTree.File name="types.ts" />
          </FileTree.Folder>
          <FileTree.Folder name="lib">
            <FileTree.File name="config.ts" />
            <FileTree.File name="http.ts" />
            <FileTree.File name="retries.ts" />
            <FileTree.File name="schemas.ts" />
            <FileTree.File name="security.ts" />
          </FileTree.Folder>
          <FileTree.Folder name="models">
            <FileTree.Folder name="components">
              <FileTree.File name="deployment.ts" />
              <FileTree.File name="project.ts" />
              <FileTree.File name="team.ts" />
              <FileTree.File name="index.ts" />
            </FileTree.Folder>
            <FileTree.Folder name="errors">
              <FileTree.File name="sdkerror.ts" />
              <FileTree.File name="sdkvalidationerror.ts" />
            </FileTree.Folder>
            <FileTree.Folder name="operations">
              <FileTree.File name="createdeployment.ts" />
              <FileTree.File name="getproject.ts" />
              <FileTree.File name="updateproject.ts" />
              <FileTree.File name="index.ts" />
            </FileTree.Folder>
          </FileTree.Folder>
          <FileTree.Folder name="sdk">
            <FileTree.File name="deployments.ts" />
            <FileTree.File name="projects.ts" />
            <FileTree.File name="teams.ts" />
            <FileTree.File name="sdk.ts" />
          </FileTree.Folder>
          <FileTree.Folder name="types">
            <FileTree.File name="operations.ts" />
            <FileTree.File name="index.ts" />
          </FileTree.Folder>
        </FileTree.Folder>
      </FileTree>
    </div>

  </div>
  <div className="md:w-1/2">

### Cloudflare SDK (Stainless)

    <div className="[&>div>div]:block">
    <FileTree>
            <FileTree.File name="README.md" />
            <FileTree.File name="package.json" />
            <FileTree.File name="tsconfig.json" />
            <FileTree.Folder name="src" defaultOpen>
              <FileTree.File name="index.ts" />
              <FileTree.File name="core.ts" />
              <FileTree.File name="error.ts" />
              <FileTree.File name="resource.ts" />
              <FileTree.File name="pagination.ts" />
              <FileTree.Folder name="_shims">
                <FileTree.File name="index.ts" />
                <FileTree.File name="node-runtime.ts" />
                <FileTree.File name="web-runtime.ts" />
              </FileTree.Folder>
              <FileTree.Folder name="lib">
                <FileTree.File name="..." />
              </FileTree.Folder>
              <FileTree.Folder name="resources">
                <FileTree.File name="zones.ts" />
                <FileTree.File name="dns.ts" />
                <FileTree.File name="ssl.ts" />
                <FileTree.File name="workers.ts" />
                <FileTree.File name="pages.ts" />
                <FileTree.File name="ai.ts" />
                <FileTree.File name="accounts.ts" />
                <FileTree.File name="..." />
              </FileTree.Folder>
              <FileTree.Folder name="shims">
                <FileTree.File name="node.ts" />
                <FileTree.File name="web.ts" />
              </FileTree.Folder>
            </FileTree.Folder>
          </FileTree>
    </div>

  </div>
</div>

The Vercel SDK (generated by Speakeasy) maintains a clear separation of concerns with dedicated folders for models, operations, hooks, and comprehensive documentation. Each component and operation has its own file, making the codebase easy to navigate and understand.

The Cloudflare SDK (generated by Stainless) organizes code differently, with a flatter structure under `resources` and extensive runtime shims for cross-platform compatibility. While this approach supports multiple JavaScript runtimes, it results in more configuration files and less clear separation between different types of code.

Structure affects developer experience. SDK users form their first impressions from the high-level organization, and a well-structured SDK suggests quality and maintainability to developers evaluating whether to adopt it.

## Dependencies and bundle size

One key difference between Speakeasy and Stainless approaches is their dependency philosophy, which directly impacts bundle size and security surface area.

<div className="md:flex gap-10">
  <div className="md:w-1/2">

### Vercel SDK (Speakeasy) - Minimal dependencies

![Vercel SDK Dependencies](/assets/blog/speakeasy-vs-stainless/vercel-dependencies.png)

The Vercel SDK has an extremely minimal dependency graph with only **Zod** as its single runtime dependency. This focused approach provides:

- **Reduced attack surface**: Fewer dependencies mean fewer potential security vulnerabilities
- **Predictable updates**: Only one external dependency to monitor and maintain
- **Simplified auditing**: Security teams can easily review the entire dependency chain
- **Faster installation**: Minimal dependencies result in quicker `npm install` times

</div>
  <div className="md:w-1/2">

### Cloudflare SDK (Stainless) - Complex dependency web

![Cloudflare SDK Dependencies](/assets/blog/speakeasy-vs-stainless/cloudflare-dependencies.png)

The Cloudflare SDK includes a complex web of **25+ dependencies**, creating a more intricate dependency graph with:

- **Larger attack surface**: More dependencies increase potential security risks
- **Complex maintenance**: Multiple dependencies require ongoing monitoring for vulnerabilities
- **Update complexity**: Changes in any dependency can introduce breaking changes
- **Installation overhead**: More dependencies mean longer installation times

</div>
</div>

While the Cloudflare SDK's dependencies serve specific purposes (cross-platform compatibility, streaming support, etc.), all of that functionality can be achieved with minimal external dependencies as shown by the Speakeasy generated SDK.

## Generated types and type safety

Both Speakeasy and Stainless generate TypeScript types, but they differ significantly in their approach to type safety. Speakeasy generates types with runtime validation using Zod, while Stainless relies primarily on compile-time TypeScript checking.

Let's examine how each handles creating a deployment with project configuration:

<div className="md:flex gap-10">
  <div className="md:w-1/2">
    ```typescript filename="vercel-sdk-types.ts"
    // Vercel SDK (Speakeasy) - with runtime validation
    import { Vercel } from "@vercel/sdk";

    const vercel = new Vercel({
      bearerToken: "your-token"
    });

    async function createDeployment() {
      // Strong typing with runtime validation
      const deployment = await vercel.deployments.createDeployment({
        // !mark
        // !callout[/}/] Zod validates this at runtime and compile time
        name: "my-project",
        gitSource: {
          type: "github",
          repo: "user/repo",
          ref: "main"
        },
        // Missing required fields will cause both
        // TypeScript errors AND runtime validation errors
      });

      return deployment;
    }
    ```

  </div>
  <div className="md:w-1/2">
    ```typescript filename="cloudflare-sdk-types.ts"
    // Cloudflare SDK (Stainless) - compile-time only
    import Cloudflare from "cloudflare";

    const cf = new Cloudflare({
      apiToken: "your-token"
    });

    async function createZone() {
      // Strong compile-time typing, no runtime validation
      const params: Cloudflare.ZoneCreateParams = {
        // !mark
        // !callout[/}/] Only TypeScript compile-time checking
        name: "example.com",
        account: {
          id: "account-id"
        }
        // Invalid data might pass TypeScript but fail at runtime
      };

      const zone = await cf.zones.create(params);
      return zone;
    }
    ```

  </div>
</div>

The Vercel SDK generates Zod schemas that validate data both at compile time and runtime, catching type errors before they reach the server. The Cloudflare SDK provides excellent TypeScript types but relies on server-side validation to catch runtime errors.

This difference becomes critical when dealing with dynamic data or complex validation rules that can't be fully expressed in TypeScript's type system.

### Runtime type checking

Speakeasy creates SDKs that are type-safe from development to production. As our CEO wrote, [type safe is better than type faith](/post/type-safe-vs-type-faith).

The Vercel SDK uses [Zod](https://zod.dev/) to validate data sent to and received from the server. This provides safer runtime code execution and helps developers catch errors before making API calls.

Here's what happens when invalid data is passed to each SDK:

<div className="md:flex gap-10">
  <div className="md:w-1/2">
    ```typescript filename="vercel-runtime-validation.ts"
    // Vercel SDK (Speakeasy) - Runtime validation
    import { Vercel } from "@vercel/sdk";

    const vercel = new Vercel({
      bearerToken: "your-token"
    });

    async function updateProject() {
      try {
        await vercel.projects.updateProject({
          idOrName: "my-project",
          requestBody: {
            // !mark
            // !callout[/:/] Zod validation error: Expected string, received number
            name: 12345, // Invalid: should be string
            framework: "nextjs"
          }
        });
      } catch (error) {
        // Catches validation error immediately
        // before making the HTTP request
        console.log("Validation failed:", error.message);
      }
    }
    ```

  </div>
  <div className="md:w-1/2">
    ```typescript filename="cloudflare-no-validation.ts"  
    // Cloudflare SDK (Stainless) - No runtime validation
    import Cloudflare from "cloudflare";

    const cf = new Cloudflare({
      apiToken: "your-token"
    });

    async function updateZone() {
      try {
        const params: any = { // Bypassing TypeScript
          // !mark
          // !callout[/:/] No runtime validation - sent to server as-is
          name: 12345, // Invalid: should be string
          account: { id: "account-id" }
        };

        await cf.zones.create(params);
      } catch (error) {
        // Only catches server-side validation errors
        // after the HTTP request is made
        console.log("Server error:", error.message);
      }
    }
    ```

  </div>
</div>

The Vercel SDK catches type errors immediately with Zod validation, providing clear error messages before making HTTP requests. The Cloudflare SDK relies on server-side validation, meaning invalid data is sent over the network and errors are discovered later.

This difference is crucial when dealing with dynamic data, user input, or complex validation rules that can't be fully expressed in TypeScript's type system.

## Authentication handling

Both SDKs handle authentication, but with different approaches and levels of built-in sophistication.

The Vercel SDK (generated by Speakeasy) uses bearer token authentication with simple configuration. Authentication is handled automatically for all requests, with no additional setup required.

The Cloudflare SDK (generated by Stainless) uses API token authentication and provides comprehensive error handling with specific error classes for different authentication scenarios, including `AuthenticationError`, `PermissionDeniedError`, and other HTTP status-specific errors.

When it comes to OAuth 2.0 support, Speakeasy-generated SDKs include built-in OAuth 2.0 client credentials handling with automatic token lifecycle management, retries, and error handling. This means the SDK automatically fetches, stores, and refreshes tokens as needed without requiring additional code.

Stainless-generated SDKs typically require manual OAuth implementation when OAuth is needed, meaning developers must write additional code to handle token acquisition, storage, refresh logic, and error recovery.

## MCP server

Both Speakeasy and Stainless allow you to generate an MCP server, which reuses your SDK methods, based on your API. Stainless generates the MCP server by default in the SDK package, which makes the SDK package larger.

At Speakeasy, because we believe in the separation of concerns, we provide the MCP server and the SDK as separate options you can choose to generate or not. This keeps your SDK package lightweight if you don't need the server functionality. To generate an MCP server, select the `Model Context Protocol (MCP) Server` option when generating the SDK.

![Selecting MCP option](/assets/blog/speakeasy-vs-stainless/selecting-mcp-option.png)

Let's compare the generated MCP server for both SDKs.

<div className="md:flex gap-10">
  <div className="md:w-1/2">

### Speakeasy

<div className="[&>div>div]:block">
  <FileTree>
    <FileTree.File name="README.md" />
    <FileTree.File name="manifest.json" />
    <FileTree.File name="package.json" />
    <FileTree.File name="eslint.config.mjs" />
    <FileTree.File name="tsconfig.json" />
    <FileTree.Folder name="src">
      <FileTree.File name="core.ts" />
      <FileTree.Folder name="funcs">
        <FileTree.File name="booksAddBook.ts" />
        <FileTree.File name="booksGetAllBooks.ts" />
        <FileTree.File name="booksGetBookById.ts" />
        <FileTree.File name="booksUpdateBookCoverById.ts" />
        <FileTree.File name="ordersCreateOrder.ts" />
        <FileTree.File name="ordersGetAllOrders.ts" />
        <FileTree.File name="ordersGetOrderById.ts" />
      </FileTree.Folder>
      <FileTree.Folder name="hooks">
        <FileTree.File name="clientcredentials.ts" />
        <FileTree.File name="hooks.ts" />
        <FileTree.File name="registration.ts" />
        <FileTree.File name="types.ts" />
      </FileTree.Folder>
      <FileTree.Folder name="lib">
        <FileTree.File name="base64.ts" />
        <FileTree.File name="config.ts" />
        <FileTree.File name="dlv.ts" />
        <FileTree.File name="encodings.ts" />
        <FileTree.File name="files.ts" />
        <FileTree.File name="http.ts" />
        <FileTree.File name="is-plain-object.ts" />
        <FileTree.File name="logger.ts" />
        <FileTree.File name="matchers.ts" />
        <FileTree.File name="primitives.ts" />
        <FileTree.File name="result.ts" />
        <FileTree.File name="retries.ts" />
        <FileTree.File name="schemas.ts" />
        <FileTree.File name="sdks.ts" />
        <FileTree.File name="security.ts" />
        <FileTree.File name="url.ts" />
      </FileTree.Folder>
      <FileTree.Folder name="mcp-server">
        <FileTree.File name="build.mts" />
        <FileTree.Folder name="cli">
          <FileTree.Folder name="start">
            <FileTree.File name="command.ts" />
            <FileTree.File name="impl.ts" />
          </FileTree.Folder>
        </FileTree.Folder>
        <FileTree.File name="cli.ts" />
        <FileTree.File name="console-logger.ts" />
        <FileTree.File name="extensions.ts" />
        <FileTree.File name="mcp-server.ts" />
        <FileTree.File name="prompts.ts" />
        <FileTree.File name="resources.ts" />
        <FileTree.File name="scopes.ts" />
        <FileTree.File name="server.ts" />
        <FileTree.File name="shared.ts" />
        <FileTree.Folder name="tools">
          <FileTree.File name="booksAddBook.ts" />
          <FileTree.File name="booksGetBookById.ts" />
          <FileTree.File name="booksUpdateBookCoverById.ts" />
        </FileTree.Folder>
        <FileTree.File name="tools.ts" />
      </FileTree.Folder>
      <FileTree.Folder name="models">
        <FileTree.File name="addbookop.ts" />
        <FileTree.File name="author.ts" />
        <FileTree.File name="createorderop.ts" />
        <FileTree.Folder name="errors">
          <FileTree.File name="apierror.ts" />
          <FileTree.File name="httpclienterrors.ts" />
          <FileTree.File name="sdkvalidationerror.ts" />
        </FileTree.Folder>
        <FileTree.File name="fantasybook.ts" />
        <FileTree.File name="getallbooksop.ts" />
        <FileTree.File name="getallordersop.ts" />
        <FileTree.File name="getbookbyidop.ts" />
        <FileTree.File name="getorderbyidop.ts" />
        <FileTree.File name="neworder.ts" />
        <FileTree.File name="order.ts" />
        <FileTree.File name="programmingbook.ts" />
        <FileTree.File name="scifibook.ts" />
        <FileTree.File name="security.ts" />
        <FileTree.File name="updatebookcoverbyidop.ts" />
        <FileTree.File name="user.ts" />
      </FileTree.Folder>
      <FileTree.Folder name="types">
        <FileTree.File name="async.ts" />
        <FileTree.File name="blobs.ts" />
        <FileTree.File name="enums.ts" />
        <FileTree.File name="fp.ts" />
        <FileTree.File name="rfcdate.ts" />
        <FileTree.File name="streams.ts" />
      </FileTree.Folder>
    </FileTree.Folder>
  </FileTree>
</div>
</div>

<div className="md:w-1/2">

### Stainless

<div className="[&>div>div]:block">
  <FileTree>
    <FileTree.Folder name="build" />
    <FileTree.File name="jest.config.ts" />
    <FileTree.File name="manifest.json" />
    <FileTree.File name="package.json" />
    <FileTree.File name="README.md" />
    <FileTree.Folder name="scripts">
      <FileTree.File name="copy-bundle-files.cjs" />
      <FileTree.File name="postprocess-dist-package-json.cjs" />
    </FileTree.Folder>
    <FileTree.Folder name="src">
      <FileTree.File name="code-tool-paths.cts" />
      <FileTree.File name="code-tool-types.ts" />
      <FileTree.File name="code-tool-worker.ts" />
      <FileTree.File name="code-tool.ts" />
      <FileTree.File name="compat.ts" />
      <FileTree.File name="dynamic-tools.ts" />
      <FileTree.File name="filtering.ts" />
      <FileTree.File name="headers.ts" />
      <FileTree.File name="http.ts" />
      <FileTree.File name="index.ts" />
      <FileTree.File name="options.ts" />
      <FileTree.File name="server.ts" />
      <FileTree.File name="stdio.ts" />
      <FileTree.Folder name="tools">
        <FileTree.Folder name="books">
          <FileTree.File name="create-books.ts" />
          <FileTree.File name="list-books.ts" />
          <FileTree.File name="retrieve-books.ts" />
          <FileTree.File name="update-cover-books.ts" />
        </FileTree.Folder>
        <FileTree.File name="index.ts" />
        <FileTree.Folder name="orders">
          <FileTree.File name="create-orders.ts" />
          <FileTree.File name="list-orders.ts" />
          <FileTree.File name="retrieve-orders.ts" />
        </FileTree.Folder>
        <FileTree.Folder name="orderstream">
          <FileTree.File name="retrieve-orderstream.ts" />
        </FileTree.Folder>
        <FileTree.File name="types.ts" />
      </FileTree.Folder>
      <FileTree.File name="tools.ts" />
    </FileTree.Folder>
    <FileTree.Folder name="tests">
      <FileTree.File name="compat.test.ts" />
      <FileTree.File name="dynamic-tools.test.ts" />
      <FileTree.File name="options.test.ts" />
      <FileTree.File name="tools.test.ts" />
    </FileTree.Folder>
    <FileTree.File name="tsc-multi.json" />
    <FileTree.File name="tsconfig.build.json" />
    <FileTree.File name="tsconfig.dist-src.json" />
    <FileTree.File name="tsconfig.json" />
    <FileTree.File name="yarn.lock" />
  </FileTree>
</div>
</div>
</div>

Speakeasy again focuses on simplicity, giving you a simpler file structure with only the parts you need for an MCP server.

Stainless has a more complicated file structure, with more bloat.

### Usage

Both platforms let you run MCP servers locally using the setup instructions in the READMEs.

Speakeasy makes distribution easier than Stainless. It creates DXT files that users can drag and drop into Claude Desktop without having to edit configuration files manually. Via [Gram](https://getgram.ai) you also get a hosted page for your MCP server that makes it easy to integrate into other MCP clients.

Speakeasy (also through Gram) further provides advanced functionality like curating toolsets for specific use cases, custom tools, prompt refining and production-grade hosting in one click.

## Webhooks support

Speakeasy-generated SDKs provide built-in support for webhook validation, payload parsing, and delivery with automatic signature verification using HMAC or other signing algorithms. The generated SDK includes strongly-typed webhook event handlers based on the OpenAPI specification.

The Vercel SDK includes webhook handling capabilities as part of its comprehensive feature set, allowing developers to validate and process webhook events with minimal setup.

Stainless-generated SDKs typically don't provide out-of-the-box webhook functionality. The Cloudflare SDK focuses primarily on API calls rather than webhook handling, requiring developers to implement their own logic for verifying event signatures, defining event payload types, and managing webhook delivery mechanisms.

This difference means that APIs requiring webhook support benefit significantly from Speakeasy's built-in webhook validation and type-safe event handling, while Stainless users must build these features manually.

You can read more about how Speakeasy handles webhooks in our [webhooks release post](/post/release-webhooks-support).

## React Hooks

React Hooks simplify state and data management in React apps, enabling developers to consume APIs more efficiently. Speakeasy generates built-in React Hooks using [TanStack Query](https://tanstack.com/query/latest). These hooks provide features like intelligent caching, type safety, pagination, and integration with modern React patterns like SSR and Suspense. Stainless does not generate React Hooks.

```tsx filename="speakeasy-example/booksView.tsx"
import { useQuery } from "@tanstack/react-query";

function BookShelf() {
  // loads books from an API
  const { data, status, error } = useQuery(
    [
      "books", // Cache key for the query
    ],
    async () => {
      const response = await fetch("https://api.example.com/books");
      return response.json();
    },
  );

  if (status === "loading") return <p>Loading books...</p>;
  if (status === "error") return <p>Error: {error?.message}</p>;

  return (
    <ul>
      {data.map((book) => (
        <li key={book.id}>{book.title}</li>
      ))}
    </ul>
  );
}
```

For example, in this basic implementation, the `useQuery` hook fetches data from an API endpoint. The cache key ensures unique identification of the query. The `status` variable provides the current state of the query: `loading`, `error`, or `success`. Depending on the query status, the component renders `loading`, `error`, or the fetched data as a list.

For an in-depth look at how Speakeasy uses React Hooks, see our [official release article](/post/release-react-hooks).

## Summary

We've all experienced bad SDKs that make integration with the API harder, not easier. Speakeasy is building a generator to make poorly written, poorly maintained SDKs a thing of the past. To do so, our team has put an extraordinary level of thought into getting the details of SDK generation right. We think that the effort has earned us the position to compare favorably with any other generator.

If you are interested in seeing how Speakeasy stacks up against some of the popular open-source SDK-generation tools, check out [this post](/post/compare-speakeasy-open-source).