Background image

In Depth: Speakeasy vs Stainless

Nolan Sullivan

Nolan Sullivan

June 5, 2024

Featured blog post image

This comparison of Speakeasy & Stainless is based on a snapshot of two developing companies as of May 2024. We welcome requests for updates to this post, so that users can make informed decisions about SDK generation in the future.

Besides comparing generated SDKs, we'll also investigate how these services differ in a broader sense: their development histories, their commitments to open standards, and how they fit into the API-first ecosystem.

We acknowledge that our views may be biased, but we'll show our work along with our conclusions so that readers can decide for themselves.

In Short: How Do Speakeasy and Stainless Differ?

  1. OpenAPI Integration: Speakeasy is built for OpenAPI (opens in a new tab), supporting advanced features of OpenAPI 3.0 and 3.1, while testing and implementing upcoming OpenAPI features like OpenAPI Overlays (opens in a new tab) and Workflows (opens in a new tab). Stainless, is built on a custom DSL (opens in a new tab) that is currently OpenAPI compatible. This carries a risk of lock-in if the DSL diverges significantly from OpenAPI in the future.

  2. Compatibility: Speakeasy is compatible with the REST ecosystem, aiming to support any API that is accurately represented by an OpenAPI document, without the need for dedicated API proxies, server-side code, or specific web frameworks. Stainless is vertically integrated, and are currently working on a TypeScript framework (opens in a new tab). While promising a streamlined solution, this vertical integration can also lead to lock-in and a loss of flexibility.

  3. Velocity and Language Support: Stainless was founded in 2021 (opens in a new tab) and gradually expanded its language support and features. However, its progress has been slower compared to its competitors, releasing support for five languages in this time. In comparison, Speakeasy was founded in May 2022, found market traction in early 2023, and released support for ten languages within 12 months. Speakeasy meets the diverse needs of users, while supporting their existing stacks.

  4. SDK Generator Maturity: Speakeasy creates SDKs that are idiomatic to each target language (opens in a new tab), type safe during development and production, human-readable, and fault-tolerant. Our comparison found some room for improvement in Stainless' type safety, fault tolerance and SDK project structure. Both products are under active development, and improvement should be expected.

Comparing Speakeasy and Stainless

Cautions about biases and a summary alone don't make for a good comparison, so let's take a closer look.

SDK Generation Targets

Tech stacks are diverse, and as your user base grows, so will their needs. We've found it vitally important to meet users where they already are, and supporting a wide range of generation targets is a priority at Speakeasy.

The table below shows the current SDK language support for Speakeasy and Stainless. Keep in mind that both lists are bound to change, so check the official documentation for updated lists.

LanguageSpeakeasyStainless
Go
Python
Typescript
Java
Kotlin⚠ Java is Kotlin-compatible
Terraform provider
C#
PHP
Ruby
Swift
Unity
Postman Collection

Everyone has that one odd language that is critical to their business. In our first year, we've made a dent, but we've got further to go. See a language that you require that we don't support? Let us know (opens in a new tab).

SDK Features

This table shows the current feature support for Speakeasy and Stainless. Keep in mind that both lists are bound to change, so check the official documentation for updated lists.

FeatureSpeakeasyStainless
Union types
Discriminated union types
Server sent events⚠ non-OpenAPI standard
Retries
Pagination
Async support
Streaming uploads
OAuth 2.0
Custom SDK Naming
Customize SDK Structure
Custom dependency injection

Speakeasy creates SDKs that handle advanced authentication. For example, Speakeasy can generate SDKs that handle OAuth 2.0 with client credentials - handling the token lifecycle, retries, and error handling for you.

Stainless leaves some of these features to be implemented by the user. For example, Stainless generates SDKs that handle OAuth 2.0, but the user must implement the token lifecycle, retries, and error handling themselves.

Stainless also lacks support for custom dependency injection, advanced union types, and streaming uploads.

Platform Features

Speakeasy is designed to be used locally, with a CLI that allows for local experimentation and iteration. This makes it easier to test and iterate on your SDKs, and allows for custom CI/CD workflows.

Conversely, Stainless is a web-based tool that allows you to upload your OpenAPI document and generate an SDK. This makes it easier to visualize spec changes and see how they impact your SDKs.

FeatureSpeakeasyStainless
GitHub CI/CD
CLI
Web Interface
OpenAPI GUI
OpenAPI overlays
Package Publishing
Product Documentation
OpenAPI Linting
Change Detection

Enterprise Support

Both Speakeasy and Stainless offer support for Enterprise customers. This includes features like concierge onboarding, private Slack channels, and enterprise SLAs.

FeatureSpeakeasyStainless
Concierge onboarding
Private Slack channel
Enterprise SLAs
User issues triage

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 the enterprise plan. Existing customers indicate that Stainless' custom pricing is pricier than Speakeasy's, with Stainless averaging 20% higher. Of course this can vary, and we recommend reaching out to both companies for a quote.

PlanSpeakeasyStainless
Free1 free Published SDK1 free local SDK; max 50 endpoints
Startup1 free + $250/mo/SDK; max 50 endpoints$250/mo/SDK; max 50 endpoints
BusinessN/A$2,500/mo; max 5 SDKs; 150 endpoints
EnterpriseCustomCustom

Speakeasy vs. Stainless Technical Walkthrough

To start our technical comparison, let's create an SDK using Speakeasy and Stainless. We'll create an OpenAPI document for a fictional bookstore API, that covers a broad range of OpenAPI functionality. You can find the complete OpenAPI document in the example repository (opens in a new tab), but let's take a look at what's included.

Our bookstore OpenAPI document is compliant with t OpenAPI 3.1, which is supported by both Speakeasy and Stainless. We define a basic info section and add a single development server.

openapi.yaml

openapi: 3.1.0
info:
title: Bookstore API
description: API for a bookstore with categories Programming, Fantasy, and Sci-fi
version: 1.0.0
contact:
name: John Doe
email: john@example.com
url: https://example.com
license:
name: MIT
url: https://opensource.org/licenses/MIT
servers:
- url: http://127.0.0.1:4010
description: Local Prism server
tags:
- name: Books
description: Operations related to books
- name: Orders
description: Operations related to orders
security:
- apiKey: []
paths:
/books:
get:
summary: Get all books
operationId: getAllBooks
description: Returns a list of books
security:
- clientCredentials:
- books.read
tags:
- Books
responses:
"200":
description: A list of books
content:
application/json:
schema:
type: array
items:
oneOf:
- $ref: "#/components/schemas/ProgrammingBook"
- $ref: "#/components/schemas/FantasyBook"
- $ref: "#/components/schemas/SciFiBook"
discriminator:
propertyName: category
mapping:
Programming: "#/components/schemas/ProgrammingBook"
Fantasy: "#/components/schemas/FantasyBook"
Sci-fi: "#/components/schemas/SciFiBook"
examples:
example1:
summary: Programming book example
value:
- id: 1
title: Clean Code
description: A Handbook of Agile Software Craftsmanship
price: 2999
category: Programming
author:
id: 1
name: Robert C. Martin
photo: https://example.com/photos/robert.jpg
biography: Robert Cecil Martin, colloquially known as "Uncle Bob", is an American software engineer...
cover_image: https://example.com/covers/cleancode.jpg
- id: 2
title: The Hobbit
description: A fantasy novel by J.R.R. Tolkien
price: 1599
category: Fantasy
author:
id: 2
name: J.R.R. Tolkien
photo: https://example.com/photos/tolkien.jpg
biography: John Ronald Reuel Tolkien was an English writer, poet, philologist, and academic...
cover_image: https://example.com/covers/thehobbit.jpg
post:
summary: Add a new book
operationId: addBook
description: Adds a new book to the bookstore
security:
- apiKey: []
tags:
- Books
requestBody:
description: Book object to be added
required: true
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/ProgrammingBook"
- $ref: "#/components/schemas/FantasyBook"
- $ref: "#/components/schemas/SciFiBook"
discriminator:
propertyName: category
mapping:
Programming: "#/components/schemas/ProgrammingBook"
Fantasy: "#/components/schemas/FantasyBook"
Sci-fi: "#/components/schemas/SciFiBook"
examples:
example:
summary: Example book
value:
title: New Sci-Fi Book
description: A new Sci-Fi book description
price: 1999
category: Sci-fi
author:
name: New Author
photo: https://example.com/photos/newauthor.jpg
biography: New Author is an upcoming writer in the Sci-Fi genre...
cover_image: https://example.com/covers/newbook.jpg
responses:
"201":
description: Book created successfully
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/ProgrammingBook"
- $ref: "#/components/schemas/FantasyBook"
- $ref: "#/components/schemas/SciFiBook"
discriminator:
propertyName: category
mapping:
Programming: "#/components/schemas/ProgrammingBook"
Fantasy: "#/components/schemas/FantasyBook"
Sci-fi: "#/components/schemas/SciFiBook"
examples:
example:
summary: Example book
value:
id: 3
title: New Sci-Fi Book
description: A new Sci-Fi book description
price: 1999
category: Sci-fi
author:
name: New Author
photo: https://example.com/photos/newauthor.jpg
biography: New Author is an upcoming writer in the Sci-Fi genre...
cover_image: https://example.com/covers/newbook.jpg
/books/{bookId}:
get:
summary: Get a book by ID
operationId: getBookById
description: Returns a single book
security: []
tags:
- Books
parameters:
- name: bookId
description: ID of the book to return
in: path
required: true
schema:
type: integer
example: 1
responses:
"200":
description: A single book
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/ProgrammingBook"
- $ref: "#/components/schemas/FantasyBook"
- $ref: "#/components/schemas/SciFiBook"
discriminator:
propertyName: category
mapping:
Programming: "#/components/schemas/ProgrammingBook"
Fantasy: "#/components/schemas/FantasyBook"
Sci-fi: "#/components/schemas/SciFiBook"
examples:
example1:
summary: Programming book example
value:
id: 1
title: Clean Code
description: A Handbook of Agile Software Craftsmanship
price: 2999
category: Programming
author:
id: 1
name: Robert C. Martin
photo: https://example.com/photos/robert.jpg
biography: Robert Cecil Martin, colloquially known as "Uncle Bob", is an American software engineer...
cover_image: https://example.com/covers/cleancode.jpg
example2:
summary: Fantasy book example
value:
id: 2
title: The Hobbit
description: A fantasy novel by J.R.R. Tolkien
price: 1599
category: Fantasy
author:
id: 2
name: J.R.R. Tolkien
photo: https://example.com/photos/tolkien.jpg
biography: John Ronald Reuel Tolkien was an English writer, poet, philologist, and academic...
cover_image: https://example.com/covers/thehobbit.jpg
/books/{bookId}/cover:
put:
summary: Update a book cover by ID
operationId: updateBookCoverById
description: Updates a single book cover
security:
- apiKey: []
tags:
- Books
parameters:
- name: bookId
description: ID of the book to update
in: path
required: true
schema:
type: integer
example: 1
requestBody:
description: Book cover
required: true
content:
multipart/form-data:
schema:
type: object
properties:
cover:
type: string
format: binary
responses:
"200":
description: Book cover updated successfully
/orders:
get:
summary: Get all orders
operationId: getAllOrders
description: Returns a list of orders
tags:
- Orders
security:
- clientCredentials:
- orders.read
responses:
"200":
description: A list of orders
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Order"
examples:
example:
summary: Example response
value:
- id: 1
date: 2023-05-17T09:24:00Z
status: pending
user:
id: 1
email: test@example.com
name: John Doe
products:
- id: 1
title: Clean Code
description: A Handbook of Agile Software Craftsmanship
price: 2999
category: Programming
author:
id: 1
name: Robert C. Martin
photo: https://example.com/photos/robert.jpg
biography: Robert Cecil Martin, colloquially known as "Uncle Bob", is an American software engineer...
cover_image: https://example.com/covers/cleancode.jpg
post:
summary: Create a new order
operationId: createOrder
description: Creates a new order
tags:
- Orders
security:
- clientCredentials:
- orders.write
requestBody:
description: Order object to be created
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/NewOrder"
examples:
example:
summary: Example order
value:
user: 1
products:
- 1
- 3
status: pending
responses:
"201":
description: Order created successfully
content:
application/json:
schema:
$ref: "#/components/schemas/Order"
/orders/{orderId}:
get:
summary: Get an order by ID
operationId: getOrderById
description: Returns a single order
tags:
- Orders
security:
- clientCredentials:
- orders.read
parameters:
- name: orderId
description: ID of the order to return
in: path
required: true
schema:
type: integer
example: 1
responses:
"200":
description: A single order
content:
application/json:
schema:
$ref: "#/components/schemas/Order"
examples:
example:
summary: Example response
value:
id: 1
date: 2023-05-17T09:24:00Z
status: pending
user:
id: 1
email: user@example.com
products:
- id: 1
title: Clean Code
description: A Handbook of Agile Software Craftsmanship
price: 2999
category: Programming
author:
id: 1
name: Robert C. Martin
photo: https://example.com/photos/robert.jpg
biography: Robert Cecil Martin, colloquially known as "Uncle Bob", is an American software engineer...
cover_image: https://example.com/covers/cleancode.jpg
/orderstream:
get:
summary: Get a stream of orders
operationId: getOrderStream
description: Returns a stream of orders
tags:
- Orders
security:
- apiKey: []
responses:
"200":
description: A stream of orders
content:
text/event-stream:
schema:
$ref: "#/components/schemas/OrderStreamMessage"
components:
schemas:
ProductId:
type: integer
example: 1
Book:
type: object
required:
- title
- description
- price
- category
- author
properties:
id:
$ref: "#/components/schemas/ProductId"
title:
type: string
example: Clean Code
description:
type: string
example: A Handbook of Agile Software Craftsmanship
price:
type: integer
description: Price in USD cents
example: 2999
category:
type: string
enum:
- Sci-fi
- Fantasy
- Programming
example: Programming
author:
$ref: "#/components/schemas/Author"
cover_image:
type: string
example: https://example.com/covers/cleancode.jpg
example:
id: 1
title: Clean Code
description: A Handbook of Agile Software Craftsmanship
price: 2999
author:
id: 1
name: Robert C. Martin
photo: https://example.com/photos/robert.jpg
biography: Robert Cecil Martin, colloquially known as "Uncle Bob", is an American software engineer...
category: Programming
SciFiBook:
allOf:
- $ref: "#/components/schemas/Book"
- type: object
properties:
category:
type: string
const: Sci-fi
example: Sci-fi
example:
id: 3
title: New Sci-Fi Book
description: A new Sci-Fi book description
price: 1999
category: Sci-fi
cover_image: https://example.com/covers/newbook.jpg
author:
name: New Author
photo: https://example.com/photos/newauthor.jpg
biography: New Author is an upcoming writer in the Sci-Fi genre...
FantasyBook:
allOf:
- $ref: "#/components/schemas/Book"
- type: object
properties:
category:
type: string
const: Fantasy
example: Fantasy
example:
id: 2
title: The Hobbit
description: A fantasy novel by J.R.R. Tolkien
price: 1599
category: Fantasy
ProgrammingBook:
allOf:
- $ref: "#/components/schemas/Book"
- type: object
properties:
category:
type: string
const: Programming
example: Programming
example:
id: 1
title: Clean Code
description: A Handbook of Agile Software Craftsmanship
price: 2999
category: Programming
AuthorId:
type: integer
example: 1
Author:
type: object
properties:
id:
$ref: "#/components/schemas/AuthorId"
name:
type: string
example: Robert C. Martin
photo:
type: string
example: https://example.com/photos/robert.jpg
biography:
type: string
example: Robert Cecil Martin, colloquially known as "Uncle Bob", is an American software engineer...
anyOf:
- required:
- name
title: Author with name
- required:
- id
title: Author with ID
example:
id: 1
name: Robert C. Martin
photo: https://example.com/photos/robert.jpg
biography: Robert Cecil Martin, colloquially known as "Uncle Bob", is an American software engineer...
OrderId:
type: integer
example: 1
Order:
type: object
required:
- id
- date
- status
- user
- products
properties:
id:
$ref: "#/components/schemas/OrderId"
date:
type: string
format: date-time
example: 2023-05-17T09:24:00Z
status:
type: string
enum:
- pending
- shipped
- delivered
example: pending
user:
$ref: "#/components/schemas/User"
products:
type: array
items:
oneOf:
- $ref: "#/components/schemas/FantasyBook"
- $ref: "#/components/schemas/ProgrammingBook"
- $ref: "#/components/schemas/SciFiBook"
discriminator:
propertyName: category
mapping:
Programming: "#/components/schemas/ProgrammingBook"
Fantasy: "#/components/schemas/FantasyBook"
Sci-fi: "#/components/schemas/SciFiBook"
example:
id: 1
date: 2023-05-17T09:24:00Z
status: pending
user:
id: 1
email: user@example.com
name: John Doe
products:
- id: 1
title: Clean Code
description: A Handbook of Agile Software Craftsmanship
price: 2999
category: Programming
author:
id: 1
name: Robert C. Martin
photo: https://example.com/photos/robert.jpg
biography: Robert Cecil Martin, colloquially known as "Uncle Bob", is an American software engineer...
- id: 2
title: The Hobbit
description: A fantasy novel by J.R.R. Tolkien
price: 1599
category: Fantasy
author:
id: 2
name: J.R.R. Tolkien
photo: https://example.com/photos/tolkien.jpg
biography: John Ronald Reuel Tolkien was an English writer, poet, philologist, and academic...
OrderStreamMessage:
type: object
description: A message in the order stream
required:
- id
- event
- data
properties:
id:
type: string
title: Message ID
format: UUID
example: 123e4567-e89b-12d3-a456-426614174000
event:
title: Event type
type: string
example: order_created
data:
$ref: "#/components/schemas/Order"
NewOrder:
type: object
required:
- user
- products
- status
properties:
user:
$ref: "#/components/schemas/UserId"
products:
type: array
items:
$ref: "#/components/schemas/ProductId"
example:
user: 1
products:
- 1
- 3
- 5
status: pending
UserId:
type: integer
example: 1
User:
type: object
properties:
id:
$ref: "#/components/schemas/UserId"
email:
type: string
example: user@example.com
name:
type: string
example: John Doe
example:
id: 1
email: user@example.com
name: John Doe
securitySchemes:
apiKey:
type: apiKey
in: header
name: X-API-Key
clientCredentials:
type: oauth2
flows:
clientCredentials:
tokenUrl: https://api.bookstore.com/oauth/token
refreshUrl: https://api.bookstore.com/oauth/refresh
scopes: {}

Here we define two tags to organize our operations with: Books and Orders.

openapi.yaml

openapi: 3.1.0
info:
title: Bookstore API
description: API for a bookstore with categories Programming, Fantasy, and Sci-fi
version: 1.0.0
contact:
name: John Doe
email: john@example.com
url: https://example.com
license:
name: MIT
url: https://opensource.org/licenses/MIT
servers:
- url: http://127.0.0.1:4010
description: Local Prism server
tags:
- name: Books
description: Operations related to books
- name: Orders
description: Operations related to orders
security:
- apiKey: []
paths:
/books:
get:
summary: Get all books
operationId: getAllBooks
description: Returns a list of books
security:
- clientCredentials:
- books.read
tags:
- Books
responses:
"200":
description: A list of books
content:
application/json:
schema:
type: array
items:
oneOf:
- $ref: "#/components/schemas/ProgrammingBook"
- $ref: "#/components/schemas/FantasyBook"
- $ref: "#/components/schemas/SciFiBook"
discriminator:
propertyName: category
mapping:
Programming: "#/components/schemas/ProgrammingBook"
Fantasy: "#/components/schemas/FantasyBook"
Sci-fi: "#/components/schemas/SciFiBook"
examples:
example1:
summary: Programming book example
value:
- id: 1
title: Clean Code
description: A Handbook of Agile Software Craftsmanship
price: 2999
category: Programming
author:
id: 1
name: Robert C. Martin
photo: https://example.com/photos/robert.jpg
biography: Robert Cecil Martin, colloquially known as "Uncle Bob", is an American software engineer...
cover_image: https://example.com/covers/cleancode.jpg
- id: 2
title: The Hobbit
description: A fantasy novel by J.R.R. Tolkien
price: 1599
category: Fantasy
author:
id: 2
name: J.R.R. Tolkien
photo: https://example.com/photos/tolkien.jpg
biography: John Ronald Reuel Tolkien was an English writer, poet, philologist, and academic...
cover_image: https://example.com/covers/thehobbit.jpg
post:
summary: Add a new book
operationId: addBook
description: Adds a new book to the bookstore
security:
- apiKey: []
tags:
- Books
requestBody:
description: Book object to be added
required: true
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/ProgrammingBook"
- $ref: "#/components/schemas/FantasyBook"
- $ref: "#/components/schemas/SciFiBook"
discriminator:
propertyName: category
mapping:
Programming: "#/components/schemas/ProgrammingBook"
Fantasy: "#/components/schemas/FantasyBook"
Sci-fi: "#/components/schemas/SciFiBook"
examples:
example:
summary: Example book
value:
title: New Sci-Fi Book
description: A new Sci-Fi book description
price: 1999
category: Sci-fi
author:
name: New Author
photo: https://example.com/photos/newauthor.jpg
biography: New Author is an upcoming writer in the Sci-Fi genre...
cover_image: https://example.com/covers/newbook.jpg
responses:
"201":
description: Book created successfully
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/ProgrammingBook"
- $ref: "#/components/schemas/FantasyBook"
- $ref: "#/components/schemas/SciFiBook"
discriminator:
propertyName: category
mapping:
Programming: "#/components/schemas/ProgrammingBook"
Fantasy: "#/components/schemas/FantasyBook"
Sci-fi: "#/components/schemas/SciFiBook"
examples:
example:
summary: Example book
value:
id: 3
title: New Sci-Fi Book
description: A new Sci-Fi book description
price: 1999
category: Sci-fi
author:
name: New Author
photo: https://example.com/photos/newauthor.jpg
biography: New Author is an upcoming writer in the Sci-Fi genre...
cover_image: https://example.com/covers/newbook.jpg
/books/{bookId}:
get:
summary: Get a book by ID
operationId: getBookById
description: Returns a single book
security: []
tags:
- Books
parameters:
- name: bookId
description: ID of the book to return
in: path
required: true
schema:
type: integer
example: 1
responses:
"200":
description: A single book
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/ProgrammingBook"
- $ref: "#/components/schemas/FantasyBook"
- $ref: "#/components/schemas/SciFiBook"
discriminator:
propertyName: category
mapping:
Programming: "#/components/schemas/ProgrammingBook"
Fantasy: "#/components/schemas/FantasyBook"
Sci-fi: "#/components/schemas/SciFiBook"
examples:
example1:
summary: Programming book example
value:
id: 1
title: Clean Code
description: A Handbook of Agile Software Craftsmanship
price: 2999
category: Programming
author:
id: 1
name: Robert C. Martin
photo: https://example.com/photos/robert.jpg
biography: Robert Cecil Martin, colloquially known as "Uncle Bob", is an American software engineer...
cover_image: https://example.com/covers/cleancode.jpg
example2:
summary: Fantasy book example
value:
id: 2
title: The Hobbit
description: A fantasy novel by J.R.R. Tolkien
price: 1599
category: Fantasy
author:
id: 2
name: J.R.R. Tolkien
photo: https://example.com/photos/tolkien.jpg
biography: John Ronald Reuel Tolkien was an English writer, poet, philologist, and academic...
cover_image: https://example.com/covers/thehobbit.jpg
/books/{bookId}/cover:
put:
summary: Update a book cover by ID
operationId: updateBookCoverById
description: Updates a single book cover
security:
- apiKey: []
tags:
- Books
parameters:
- name: bookId
description: ID of the book to update
in: path
required: true
schema:
type: integer
example: 1
requestBody:
description: Book cover
required: true
content:
multipart/form-data:
schema:
type: object
properties:
cover:
type: string
format: binary
responses:
"200":
description: Book cover updated successfully
/orders:
get:
summary: Get all orders
operationId: getAllOrders
description: Returns a list of orders
tags:
- Orders
security:
- clientCredentials:
- orders.read
responses:
"200":
description: A list of orders
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Order"
examples:
example:
summary: Example response
value:
- id: 1
date: 2023-05-17T09:24:00Z
status: pending
user:
id: 1
email: test@example.com
name: John Doe
products:
- id: 1
title: Clean Code
description: A Handbook of Agile Software Craftsmanship
price: 2999
category: Programming
author:
id: 1
name: Robert C. Martin
photo: https://example.com/photos/robert.jpg
biography: Robert Cecil Martin, colloquially known as "Uncle Bob", is an American software engineer...
cover_image: https://example.com/covers/cleancode.jpg
post:
summary: Create a new order
operationId: createOrder
description: Creates a new order
tags:
- Orders
security:
- clientCredentials:
- orders.write
requestBody:
description: Order object to be created
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/NewOrder"
examples:
example:
summary: Example order
value:
user: 1
products:
- 1
- 3
status: pending
responses:
"201":
description: Order created successfully
content:
application/json:
schema:
$ref: "#/components/schemas/Order"
/orders/{orderId}:
get:
summary: Get an order by ID
operationId: getOrderById
description: Returns a single order
tags:
- Orders
security:
- clientCredentials:
- orders.read
parameters:
- name: orderId
description: ID of the order to return
in: path
required: true
schema:
type: integer
example: 1
responses:
"200":
description: A single order
content:
application/json:
schema:
$ref: "#/components/schemas/Order"
examples:
example:
summary: Example response
value:
id: 1
date: 2023-05-17T09:24:00Z
status: pending
user:
id: 1
email: user@example.com
products:
- id: 1
title: Clean Code
description: A Handbook of Agile Software Craftsmanship
price: 2999
category: Programming
author:
id: 1
name: Robert C. Martin
photo: https://example.com/photos/robert.jpg
biography: Robert Cecil Martin, colloquially known as "Uncle Bob", is an American software engineer...
cover_image: https://example.com/covers/cleancode.jpg
/orderstream:
get:
summary: Get a stream of orders
operationId: getOrderStream
description: Returns a stream of orders
tags:
- Orders
security:
- apiKey: []
responses:
"200":
description: A stream of orders
content:
text/event-stream:
schema:
$ref: "#/components/schemas/OrderStreamMessage"
components:
schemas:
ProductId:
type: integer
example: 1
Book:
type: object
required:
- title
- description
- price
- category
- author
properties:
id:
$ref: "#/components/schemas/ProductId"
title:
type: string
example: Clean Code
description:
type: string
example: A Handbook of Agile Software Craftsmanship
price:
type: integer
description: Price in USD cents
example: 2999
category:
type: string
enum:
- Sci-fi
- Fantasy
- Programming
example: Programming
author:
$ref: "#/components/schemas/Author"
cover_image:
type: string
example: https://example.com/covers/cleancode.jpg
example:
id: 1
title: Clean Code
description: A Handbook of Agile Software Craftsmanship
price: 2999
author:
id: 1
name: Robert C. Martin
photo: https://example.com/photos/robert.jpg
biography: Robert Cecil Martin, colloquially known as "Uncle Bob", is an American software engineer...
category: Programming
SciFiBook:
allOf:
- $ref: "#/components/schemas/Book"
- type: object
properties:
category:
type: string
const: Sci-fi
example: Sci-fi
example:
id: 3
title: New Sci-Fi Book
description: A new Sci-Fi book description
price: 1999
category: Sci-fi
cover_image: https://example.com/covers/newbook.jpg
author:
name: New Author
photo: https://example.com/photos/newauthor.jpg
biography: New Author is an upcoming writer in the Sci-Fi genre...
FantasyBook:
allOf:
- $ref: "#/components/schemas/Book"
- type: object
properties:
category:
type: string
const: Fantasy
example: Fantasy
example:
id: 2
title: The Hobbit
description: A fantasy novel by J.R.R. Tolkien
price: 1599
category: Fantasy
ProgrammingBook:
allOf:
- $ref: "#/components/schemas/Book"
- type: object
properties:
category:
type: string
const: Programming
example: Programming
example:
id: 1
title: Clean Code
description: A Handbook of Agile Software Craftsmanship
price: 2999
category: Programming
AuthorId:
type: integer
example: 1
Author:
type: object
properties:
id:
$ref: "#/components/schemas/AuthorId"
name:
type: string
example: Robert C. Martin
photo:
type: string
example: https://example.com/photos/robert.jpg
biography:
type: string
example: Robert Cecil Martin, colloquially known as "Uncle Bob", is an American software engineer...
anyOf:
- required:
- name
title: Author with name
- required:
- id
title: Author with ID
example:
id: 1
name: Robert C. Martin
photo: https://example.com/photos/robert.jpg
biography: Robert Cecil Martin, colloquially known as "Uncle Bob", is an American software engineer...
OrderId:
type: integer
example: 1
Order:
type: object
required:
- id
- date
- status
- user
- products
properties:
id:
$ref: "#/components/schemas/OrderId"
date:
type: string
format: date-time
example: 2023-05-17T09:24:00Z
status:
type: string
enum:
- pending
- shipped
- delivered
example: pending
user:
$ref: "#/components/schemas/User"
products:
type: array
items:
oneOf:
- $ref: "#/components/schemas/FantasyBook"
- $ref: "#/components/schemas/ProgrammingBook"
- $ref: "#/components/schemas/SciFiBook"
discriminator:
propertyName: category
mapping:
Programming: "#/components/schemas/ProgrammingBook"
Fantasy: "#/components/schemas/FantasyBook"
Sci-fi: "#/components/schemas/SciFiBook"
example:
id: 1
date: 2023-05-17T09:24:00Z
status: pending
user:
id: 1
email: user@example.com
name: John Doe
products:
- id: 1
title: Clean Code
description: A Handbook of Agile Software Craftsmanship
price: 2999
category: Programming
author:
id: 1
name: Robert C. Martin
photo: https://example.com/photos/robert.jpg
biography: Robert Cecil Martin, colloquially known as "Uncle Bob", is an American software engineer...
- id: 2
title: The Hobbit
description: A fantasy novel by J.R.R. Tolkien
price: 1599
category: Fantasy
author:
id: 2
name: J.R.R. Tolkien
photo: https://example.com/photos/tolkien.jpg
biography: John Ronald Reuel Tolkien was an English writer, poet, philologist, and academic...
OrderStreamMessage:
type: object
description: A message in the order stream
required:
- id
- event
- data
properties:
id:
type: string
title: Message ID
format: UUID
example: 123e4567-e89b-12d3-a456-426614174000
event:
title: Event type
type: string
example: order_created
data:
$ref: "#/components/schemas/Order"
NewOrder:
type: object
required:
- user
- products
- status
properties:
user:
$ref: "#/components/schemas/UserId"
products:
type: array
items:
$ref: "#/components/schemas/ProductId"
example:
user: 1
products:
- 1
- 3
- 5
status: pending
UserId:
type: integer
example: 1
User:
type: object
properties:
id:
$ref: "#/components/schemas/UserId"
email:
type: string
example: user@example.com
name:
type: string
example: John Doe
example:
id: 1
email: user@example.com
name: John Doe
securitySchemes:
apiKey:
type: apiKey
in: header
name: X-API-Key
clientCredentials:
type: oauth2
flows:
clientCredentials:
tokenUrl: https://api.bookstore.com/oauth/token
refreshUrl: https://api.bookstore.com/oauth/refresh
scopes: {}

We define one global authentication method, apiKey.

openapi.yaml

openapi: 3.1.0
info:
title: Bookstore API
description: API for a bookstore with categories Programming, Fantasy, and Sci-fi
version: 1.0.0
contact:
name: John Doe
email: john@example.com
url: https://example.com
license:
name: MIT
url: https://opensource.org/licenses/MIT
servers:
- url: http://127.0.0.1:4010
description: Local Prism server
tags:
- name: Books
description: Operations related to books
- name: Orders
description: Operations related to orders
security:
- apiKey: []
paths:
/books:
get:
summary: Get all books
operationId: getAllBooks
description: Returns a list of books
security:
- clientCredentials:
- books.read
tags:
- Books
responses:
"200":
description: A list of books
content:
application/json:
schema:
type: array
items:
oneOf:
- $ref: "#/components/schemas/ProgrammingBook"
- $ref: "#/components/schemas/FantasyBook"
- $ref: "#/components/schemas/SciFiBook"
discriminator:
propertyName: category
mapping:
Programming: "#/components/schemas/ProgrammingBook"
Fantasy: "#/components/schemas/FantasyBook"
Sci-fi: "#/components/schemas/SciFiBook"
examples:
example1:
summary: Programming book example
value:
- id: 1
title: Clean Code
description: A Handbook of Agile Software Craftsmanship
price: 2999
category: Programming
author:
id: 1
name: Robert C. Martin
photo: https://example.com/photos/robert.jpg
biography: Robert Cecil Martin, colloquially known as "Uncle Bob", is an American software engineer...
cover_image: https://example.com/covers/cleancode.jpg
- id: 2
title: The Hobbit
description: A fantasy novel by J.R.R. Tolkien
price: 1599
category: Fantasy
author:
id: 2
name: J.R.R. Tolkien
photo: https://example.com/photos/tolkien.jpg
biography: John Ronald Reuel Tolkien was an English writer, poet, philologist, and academic...
cover_image: https://example.com/covers/thehobbit.jpg
post:
summary: Add a new book
operationId: addBook
description: Adds a new book to the bookstore
security:
- apiKey: []
tags:
- Books
requestBody:
description: Book object to be added
required: true
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/ProgrammingBook"
- $ref: "#/components/schemas/FantasyBook"
- $ref: "#/components/schemas/SciFiBook"
discriminator:
propertyName: category
mapping:
Programming: "#/components/schemas/ProgrammingBook"
Fantasy: "#/components/schemas/FantasyBook"
Sci-fi: "#/components/schemas/SciFiBook"
examples:
example:
summary: Example book
value:
title: New Sci-Fi Book
description: A new Sci-Fi book description
price: 1999
category: Sci-fi
author:
name: New Author
photo: https://example.com/photos/newauthor.jpg
biography: New Author is an upcoming writer in the Sci-Fi genre...
cover_image: https://example.com/covers/newbook.jpg
responses:
"201":
description: Book created successfully
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/ProgrammingBook"
- $ref: "#/components/schemas/FantasyBook"
- $ref: "#/components/schemas/SciFiBook"
discriminator:
propertyName: category
mapping:
Programming: "#/components/schemas/ProgrammingBook"
Fantasy: "#/components/schemas/FantasyBook"
Sci-fi: "#/components/schemas/SciFiBook"
examples:
example:
summary: Example book
value:
id: 3
title: New Sci-Fi Book
description: A new Sci-Fi book description
price: 1999
category: Sci-fi
author:
name: New Author
photo: https://example.com/photos/newauthor.jpg
biography: New Author is an upcoming writer in the Sci-Fi genre...
cover_image: https://example.com/covers/newbook.jpg
/books/{bookId}:
get:
summary: Get a book by ID
operationId: getBookById
description: Returns a single book
security: []
tags:
- Books
parameters:
- name: bookId
description: ID of the book to return
in: path
required: true
schema:
type: integer
example: 1
responses:
"200":
description: A single book
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/ProgrammingBook"
- $ref: "#/components/schemas/FantasyBook"
- $ref: "#/components/schemas/SciFiBook"
discriminator:
propertyName: category
mapping:
Programming: "#/components/schemas/ProgrammingBook"
Fantasy: "#/components/schemas/FantasyBook"
Sci-fi: "#/components/schemas/SciFiBook"
examples:
example1:
summary: Programming book example
value:
id: 1
title: Clean Code
description: A Handbook of Agile Software Craftsmanship
price: 2999
category: Programming
author:
id: 1
name: Robert C. Martin
photo: https://example.com/photos/robert.jpg
biography: Robert Cecil Martin, colloquially known as "Uncle Bob", is an American software engineer...
cover_image: https://example.com/covers/cleancode.jpg
example2:
summary: Fantasy book example
value:
id: 2
title: The Hobbit
description: A fantasy novel by J.R.R. Tolkien
price: 1599
category: Fantasy
author:
id: 2
name: J.R.R. Tolkien
photo: https://example.com/photos/tolkien.jpg
biography: John Ronald Reuel Tolkien was an English writer, poet, philologist, and academic...
cover_image: https://example.com/covers/thehobbit.jpg
/books/{bookId}/cover:
put:
summary: Update a book cover by ID
operationId: updateBookCoverById
description: Updates a single book cover
security:
- apiKey: []
tags:
- Books
parameters:
- name: bookId
description: ID of the book to update
in: path
required: true
schema:
type: integer
example: 1
requestBody:
description: Book cover
required: true
content:
multipart/form-data:
schema:
type: object
properties:
cover:
type: string
format: binary
responses:
"200":
description: Book cover updated successfully
/orders:
get:
summary: Get all orders
operationId: getAllOrders
description: Returns a list of orders
tags:
- Orders
security:
- clientCredentials:
- orders.read
responses:
"200":
description: A list of orders
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Order"
examples:
example:
summary: Example response
value:
- id: 1
date: 2023-05-17T09:24:00Z
status: pending
user:
id: 1
email: test@example.com
name: John Doe
products:
- id: 1
title: Clean Code
description: A Handbook of Agile Software Craftsmanship
price: 2999
category: Programming
author:
id: 1
name: Robert C. Martin
photo: https://example.com/photos/robert.jpg
biography: Robert Cecil Martin, colloquially known as "Uncle Bob", is an American software engineer...
cover_image: https://example.com/covers/cleancode.jpg
post:
summary: Create a new order
operationId: createOrder
description: Creates a new order
tags:
- Orders
security:
- clientCredentials:
- orders.write
requestBody:
description: Order object to be created
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/NewOrder"
examples:
example:
summary: Example order
value:
user: 1
products:
- 1
- 3
status: pending
responses:
"201":
description: Order created successfully
content:
application/json:
schema:
$ref: "#/components/schemas/Order"
/orders/{orderId}:
get:
summary: Get an order by ID
operationId: getOrderById
description: Returns a single order
tags:
- Orders
security:
- clientCredentials:
- orders.read
parameters:
- name: orderId
description: ID of the order to return
in: path
required: true
schema:
type: integer
example: 1
responses:
"200":
description: A single order
content:
application/json:
schema:
$ref: "#/components/schemas/Order"
examples:
example:
summary: Example response
value:
id: 1
date: 2023-05-17T09:24:00Z
status: pending
user:
id: 1
email: user@example.com
products:
- id: 1
title: Clean Code
description: A Handbook of Agile Software Craftsmanship
price: 2999
category: Programming
author:
id: 1
name: Robert C. Martin
photo: https://example.com/photos/robert.jpg
biography: Robert Cecil Martin, colloquially known as "Uncle Bob", is an American software engineer...
cover_image: https://example.com/covers/cleancode.jpg
/orderstream:
get:
summary: Get a stream of orders
operationId: getOrderStream
description: Returns a stream of orders
tags:
- Orders
security:
- apiKey: []
responses:
"200":
description: A stream of orders
content:
text/event-stream:
schema:
$ref: "#/components/schemas/OrderStreamMessage"
components:
schemas:
ProductId:
type: integer
example: 1
Book:
type: object
required:
- title
- description
- price
- category
- author
properties:
id:
$ref: "#/components/schemas/ProductId"
title:
type: string
example: Clean Code
description:
type: string
example: A Handbook of Agile Software Craftsmanship
price:
type: integer
description: Price in USD cents
example: 2999
category:
type: string
enum:
- Sci-fi
- Fantasy
- Programming
example: Programming
author:
$ref: "#/components/schemas/Author"
cover_image:
type: string
example: https://example.com/covers/cleancode.jpg
example:
id: 1
title: Clean Code
description: A Handbook of Agile Software Craftsmanship
price: 2999
author:
id: 1
name: Robert C. Martin
photo: https://example.com/photos/robert.jpg
biography: Robert Cecil Martin, colloquially known as "Uncle Bob", is an American software engineer...
category: Programming
SciFiBook:
allOf:
- $ref: "#/components/schemas/Book"
- type: object
properties:
category:
type: string
const: Sci-fi
example: Sci-fi
example:
id: 3
title: New Sci-Fi Book
description: A new Sci-Fi book description
price: 1999
category: Sci-fi
cover_image: https://example.com/covers/newbook.jpg
author:
name: New Author
photo: https://example.com/photos/newauthor.jpg
biography: New Author is an upcoming writer in the Sci-Fi genre...
FantasyBook:
allOf:
- $ref: "#/components/schemas/Book"
- type: object
properties:
category:
type: string
const: Fantasy
example: Fantasy
example:
id: 2
title: The Hobbit
description: A fantasy novel by J.R.R. Tolkien
price: 1599
category: Fantasy
ProgrammingBook:
allOf:
- $ref: "#/components/schemas/Book"
- type: object
properties:
category:
type: string
const: Programming
example: Programming
example:
id: 1
title: Clean Code
description: A Handbook of Agile Software Craftsmanship
price: 2999
category: Programming
AuthorId:
type: integer
example: 1
Author:
type: object
properties:
id:
$ref: "#/components/schemas/AuthorId"
name:
type: string
example: Robert C. Martin
photo:
type: string
example: https://example.com/photos/robert.jpg
biography:
type: string
example: Robert Cecil Martin, colloquially known as "Uncle Bob", is an American software engineer...
anyOf:
- required:
- name
title: Author with name
- required:
- id
title: Author with ID
example:
id: 1
name: Robert C. Martin
photo: https://example.com/photos/robert.jpg
biography: Robert Cecil Martin, colloquially known as "Uncle Bob", is an American software engineer...
OrderId:
type: integer
example: 1
Order:
type: object
required:
- id
- date
- status
- user
- products
properties:
id:
$ref: "#/components/schemas/OrderId"
date:
type: string
format: date-time
example: 2023-05-17T09:24:00Z
status:
type: string
enum:
- pending
- shipped
- delivered
example: pending
user:
$ref: "#/components/schemas/User"
products:
type: array
items:
oneOf:
- $ref: "#/components/schemas/FantasyBook"
- $ref: "#/components/schemas/ProgrammingBook"
- $ref: "#/components/schemas/SciFiBook"
discriminator:
propertyName: category
mapping:
Programming: "#/components/schemas/ProgrammingBook"
Fantasy: "#/components/schemas/FantasyBook"
Sci-fi: "#/components/schemas/SciFiBook"
example:
id: 1
date: 2023-05-17T09:24:00Z
status: pending
user:
id: 1
email: user@example.com
name: John Doe
products:
- id: 1
title: Clean Code
description: A Handbook of Agile Software Craftsmanship
price: 2999
category: Programming
author:
id: 1
name: Robert C. Martin
photo: https://example.com/photos/robert.jpg
biography: Robert Cecil Martin, colloquially known as "Uncle Bob", is an American software engineer...
- id: 2
title: The Hobbit
description: A fantasy novel by J.R.R. Tolkien
price: 1599
category: Fantasy
author:
id: 2
name: J.R.R. Tolkien
photo: https://example.com/photos/tolkien.jpg
biography: John Ronald Reuel Tolkien was an English writer, poet, philologist, and academic...
OrderStreamMessage:
type: object
description: A message in the order stream
required:
- id
- event
- data
properties:
id:
type: string
title: Message ID
format: UUID
example: 123e4567-e89b-12d3-a456-426614174000
event:
title: Event type
type: string
example: order_created
data:
$ref: "#/components/schemas/Order"
NewOrder:
type: object
required:
- user
- products
- status
properties:
user:
$ref: "#/components/schemas/UserId"
products:
type: array
items:
$ref: "#/components/schemas/ProductId"
example:
user: 1
products:
- 1
- 3
- 5
status: pending
UserId:
type: integer
example: 1
User:
type: object
properties:
id:
$ref: "#/components/schemas/UserId"
email:
type: string
example: user@example.com
name:
type: string
example: John Doe
example:
id: 1
email: user@example.com
name: John Doe
securitySchemes:
apiKey:
type: apiKey
in: header
name: X-API-Key
clientCredentials:
type: oauth2
flows:
clientCredentials:
tokenUrl: https://api.bookstore.com/oauth/token
refreshUrl: https://api.bookstore.com/oauth/refresh
scopes: {}

Let's take a look at the operations we'll need an SDK for, starting with getAllBooks.

This operation takes no input.

openapi.yaml

paths:
/books:
get:
summary: Get all books
operationId: getAllBooks
description: Returns a list of books
security:
- clientCredentials:
- books.read
tags:
- Books
responses:
"200":
description: A list of books
content:
application/json:
schema:
type: array
items:
oneOf:
- $ref: "#/components/schemas/ProgrammingBook"
- $ref: "#/components/schemas/FantasyBook"
- $ref: "#/components/schemas/SciFiBook"
discriminator:
propertyName: category
mapping:
Programming: "#/components/schemas/ProgrammingBook"
Fantasy: "#/components/schemas/FantasyBook"
Sci-fi: "#/components/schemas/SciFiBook"
examples:
example1:
summary: Programming book example
value:
- id: 1
title: Clean Code
description: A Handbook of Agile Software Craftsmanship
price: 2999
category: Programming
author:
id: 1
name: Robert C. Martin
photo: https://example.com/photos/robert.jpg
biography: Robert Cecil Martin, colloquially known as "Uncle Bob", is an American software engineer...
cover_image: https://example.com/covers/cleancode.jpg
- id: 2
title: The Hobbit
description: A fantasy novel by J.R.R. Tolkien
price: 1599
category: Fantasy
author:
id: 2
name: J.R.R. Tolkien
photo: https://example.com/photos/tolkien.jpg
biography: John Ronald Reuel Tolkien was an English writer, poet, philologist, and academic...
cover_image: https://example.com/covers/thehobbit.jpg

What makes this operation interesting is that it returns an array of objects of three types: ProgrammingBook, FantasyBook, and SciFiBook. Each object's type is determined by the book's category.

This example allows us to test how our SDK generators handle discriminated unions in OpenAPI.

openapi.yaml

paths:
/books:
get:
summary: Get all books
operationId: getAllBooks
description: Returns a list of books
security:
- clientCredentials:
- books.read
tags:
- Books
responses:
"200":
description: A list of books
content:
application/json:
schema:
type: array
items:
oneOf:
- $ref: "#/components/schemas/ProgrammingBook"
- $ref: "#/components/schemas/FantasyBook"
- $ref: "#/components/schemas/SciFiBook"
discriminator:
propertyName: category
mapping:
Programming: "#/components/schemas/ProgrammingBook"
Fantasy: "#/components/schemas/FantasyBook"
Sci-fi: "#/components/schemas/SciFiBook"
examples:
example1:
summary: Programming book example
value:
- id: 1
title: Clean Code
description: A Handbook of Agile Software Craftsmanship
price: 2999
category: Programming
author:
id: 1
name: Robert C. Martin
photo: https://example.com/photos/robert.jpg
biography: Robert Cecil Martin, colloquially known as "Uncle Bob", is an American software engineer...
cover_image: https://example.com/covers/cleancode.jpg
- id: 2
title: The Hobbit
description: A fantasy novel by J.R.R. Tolkien
price: 1599
category: Fantasy
author:
id: 2
name: J.R.R. Tolkien
photo: https://example.com/photos/tolkien.jpg
biography: John Ronald Reuel Tolkien was an English writer, poet, philologist, and academic...
cover_image: https://example.com/covers/thehobbit.jpg

Next up, we have an operation that adds a book to the database, called addBook.

This operation takes one object of type ProgrammingBook, FantasyBook, or SciFiBook as input.

openapi.yaml

post:
summary: Add a new book
operationId: addBook
description: Adds a new book to the bookstore
security:
- apiKey: []
tags:
- Books
requestBody:
description: Book object to be added
required: true
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/ProgrammingBook"
- $ref: "#/components/schemas/FantasyBook"
- $ref: "#/components/schemas/SciFiBook"
discriminator:
propertyName: category
mapping:
Programming: "#/components/schemas/ProgrammingBook"
Fantasy: "#/components/schemas/FantasyBook"
Sci-fi: "#/components/schemas/SciFiBook"
examples:
example:
summary: Example book
value:
title: New Sci-Fi Book
description: A new Sci-Fi book description
price: 1999
category: Sci-fi
author:
name: New Author
photo: https://example.com/photos/newauthor.jpg
biography: New Author is an upcoming writer in the Sci-Fi genre...
cover_image: https://example.com/covers/newbook.jpg
responses:
"201":
description: Book created successfully
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/ProgrammingBook"
- $ref: "#/components/schemas/FantasyBook"
- $ref: "#/components/schemas/SciFiBook"
discriminator:
propertyName: category
mapping:
Programming: "#/components/schemas/ProgrammingBook"
Fantasy: "#/components/schemas/FantasyBook"
Sci-fi: "#/components/schemas/SciFiBook"
examples:
example:
summary: Example book
value:
id: 3
title: New Sci-Fi Book
description: A new Sci-Fi book description
price: 1999
category: Sci-fi
author:
name: New Author
photo: https://example.com/photos/newauthor.jpg
biography: New Author is an upcoming writer in the Sci-Fi genre...
cover_image: https://example.com/covers/newbook.jpg

Our next book-related operation, updateBookCoverById, takes a book ID as a path variable, and an image as a binary payload.

We include this operation to test how our SDK generators handle binary payloads.

openapi.yaml

/books/{bookId}/cover:
put:
summary: Update a book cover by ID
operationId: updateBookCoverById
description: Updates a single book cover
security:
- apiKey: []
tags:
- Books
parameters:
- name: bookId
description: ID of the book to update
in: path
required: true
schema:
type: integer
example: 1
requestBody:
description: Book cover
required: true
content:
multipart/form-data:
schema:
type: object
properties:
cover:
type: string
format: binary

Our final book-related operation, getBookById, takes a book ID as a path variable, and returns one of our book objects.

openapi.yaml

/books/{bookId}:
get:
summary: Get a book by ID
operationId: getBookById
description: Returns a single book
security: []
tags:
- Books
parameters:
- name: bookId
description: ID of the book to return
in: path
required: true
schema:
type: integer
example: 1
responses:
"200":
description: A single book
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/ProgrammingBook"
- $ref: "#/components/schemas/FantasyBook"
- $ref: "#/components/schemas/SciFiBook"
discriminator:
propertyName: category
mapping:
Programming: "#/components/schemas/ProgrammingBook"
Fantasy: "#/components/schemas/FantasyBook"
Sci-fi: "#/components/schemas/SciFiBook"
examples:
example1:
summary: Programming book example
value:
id: 1
title: Clean Code
description: A Handbook of Agile Software Craftsmanship
price: 2999
category: Programming
author:
id: 1
name: Robert C. Martin
photo: https://example.com/photos/robert.jpg
biography: Robert Cecil Martin, colloquially known as "Uncle Bob", is an American software engineer...
cover_image: https://example.com/covers/cleancode.jpg
example2:
summary: Fantasy book example
value:
id: 2
title: The Hobbit
description: A fantasy novel by J.R.R. Tolkien
price: 1599
category: Fantasy
author:
id: 2
name: J.R.R. Tolkien
photo: https://example.com/photos/tolkien.jpg
biography: John Ronald Reuel Tolkien was an English writer, poet, philologist, and academic...
cover_image: https://example.com/covers/thehobbit.jpg

Next up, we have an operation that returns a list of all orders in the database, called getAllOrders.

This operation returns an array of Order objects, so that we can test an array of nested objects.

openapi.yaml

/orders:
get:
summary: Get all orders
operationId: getAllOrders
description: Returns a list of orders
tags:
- Orders
security:
- clientCredentials:
- orders.read
responses:
"200":
description: A list of orders
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Order"
examples:
example:
summary: Example response
value:
- id: 1
date: 2023-05-17T09:24:00Z
status: pending
user:
id: 1
email: test@example.com
name: John Doe
products:
- id: 1
title: Clean Code
description: A Handbook of Agile Software Craftsmanship
price: 2999
category: Programming
author:
id: 1
name: Robert C. Martin
photo: https://example.com/photos/robert.jpg
biography: Robert Cecil Martin, colloquially known as "Uncle Bob", is an American software engineer...
cover_image: https://example.com/covers/cleancode.jpg

Our next order-related operation, createOrder, takes an object of type NewOrder as input, and returns an object of type Order.

We include this one to test how our SDK generators help users avoid common mistakes, like passing the wrong type to an operation.

openapi.yaml

post:
summary: Create a new order
operationId: createOrder
description: Creates a new order
tags:
- Orders
security:
- clientCredentials:
- orders.write
requestBody:
description: Order object to be created
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/NewOrder"
examples:
example:
summary: Example order
value:
user: 1
products:
- 1
- 3
status: pending
responses:
"201":
description: Order created successfully
content:
application/json:
schema:
$ref: "#/components/schemas/Order"

Finally, we have an operation that returns a stream of order events, called getOrderStream.

We include this operation to test how our SDK generators handle server-sent events.

openapi.yaml

/orderstream:
get:
summary: Get a stream of orders
operationId: getOrderStream
description: Returns a stream of orders
tags:
- Orders
security:
- apiKey: []
responses:
"200":
description: A stream of orders
content:
text/event-stream:
schema:
$ref: "#/components/schemas/OrderStreamMessage"

The remainder of the OpenAPI document defines the components used in the operations above.

openapi.yaml

components:
schemas:
ProductId:
type: integer
example: 1
Book:
type: object
required:
- title
- description
- price
- category
- author
properties:
id:
$ref: "#/components/schemas/ProductId"
title:
type: string
example: Clean Code
description:
type: string
example: A Handbook of Agile Software Craftsmanship
price:
type: integer
description: Price in USD cents
example: 2999
category:
type: string
enum:
- Sci-fi
- Fantasy
- Programming
example: Programming
author:
$ref: "#/components/schemas/Author"
cover_image:
type: string
example: https://example.com/covers/cleancode.jpg
example:
id: 1
title: Clean Code
description: A Handbook of Agile Software Craftsmanship
price: 2999
author:
id: 1
name: Robert C. Martin
photo: https://example.com/photos/robert.jpg
biography: Robert Cecil Martin, colloquially known as "Uncle Bob", is an American software engineer...
category: Programming
SciFiBook:
allOf:
- $ref: "#/components/schemas/Book"
- type: object
properties:
category:
type: string
const: Sci-fi
example: Sci-fi
example:
id: 3
title: New Sci-Fi Book
description: A new Sci-Fi book description
price: 1999
category: Sci-fi
cover_image: https://example.com/covers/newbook.jpg
author:
name: New Author
photo: https://example.com/photos/newauthor.jpg
biography: New Author is an upcoming writer in the Sci-Fi genre...
FantasyBook:
allOf:
- $ref: "#/components/schemas/Book"
- type: object
properties:
category:
type: string
const: Fantasy
example: Fantasy
example:
id: 2
title: The Hobbit
description: A fantasy novel by J.R.R. Tolkien
price: 1599
category: Fantasy
ProgrammingBook:
allOf:
- $ref: "#/components/schemas/Book"
- type: object
properties:
category:
type: string
const: Programming
example: Programming
example:
id: 1
title: Clean Code
description: A Handbook of Agile Software Craftsmanship
price: 2999
category: Programming
AuthorId:
type: integer
example: 1
Author:
type: object
properties:
id:
$ref: "#/components/schemas/AuthorId"
name:
type: string
example: Robert C. Martin
photo:
type: string
example: https://example.com/photos/robert.jpg
biography:
type: string
example: Robert Cecil Martin, colloquially known as "Uncle Bob", is an American software engineer...
anyOf:
- required:
- name
title: Author with name
- required:
- id
title: Author with ID
example:
id: 1
name: Robert C. Martin
photo: https://example.com/photos/robert.jpg
biography: Robert Cecil Martin, colloquially known as "Uncle Bob", is an American software engineer...
OrderId:
type: integer
example: 1
Order:
type: object
required:
- id
- date
- status
- user
- products
properties:
id:
$ref: "#/components/schemas/OrderId"
date:
type: string
format: date-time
example: 2023-05-17T09:24:00Z
status:
type: string
enum:
- pending
- shipped
- delivered
example: pending
user:
$ref: "#/components/schemas/User"
products:
type: array
items:
oneOf:
- $ref: "#/components/schemas/FantasyBook"
- $ref: "#/components/schemas/ProgrammingBook"
- $ref: "#/components/schemas/SciFiBook"
discriminator:
propertyName: category
mapping:
Programming: "#/components/schemas/ProgrammingBook"
Fantasy: "#/components/schemas/FantasyBook"
Sci-fi: "#/components/schemas/SciFiBook"
example:
id: 1
date: 2023-05-17T09:24:00Z
status: pending
user:
id: 1
email: user@example.com
name: John Doe
products:
- id: 1
title: Clean Code
description: A Handbook of Agile Software Craftsmanship
price: 2999
category: Programming
author:
id: 1
name: Robert C. Martin
photo: https://example.com/photos/robert.jpg
biography: Robert Cecil Martin, colloquially known as "Uncle Bob", is an American software engineer...
- id: 2
title: The Hobbit
description: A fantasy novel by J.R.R. Tolkien
price: 1599
category: Fantasy
author:
id: 2
name: J.R.R. Tolkien
photo: https://example.com/photos/tolkien.jpg
biography: John Ronald Reuel Tolkien was an English writer, poet, philologist, and academic...
OrderStreamMessage:
type: object
description: A message in the order stream
required:
- id
- event
- data
properties:
id:
type: string
title: Message ID
format: UUID
example: 123e4567-e89b-12d3-a456-426614174000
event:
title: Event type
type: string
example: order_created
data:
$ref: "#/components/schemas/Order"
NewOrder:
type: object
required:
- user
- products
- status
properties:
user:
$ref: "#/components/schemas/UserId"
products:
type: array
items:
$ref: "#/components/schemas/ProductId"
example:
user: 1
products:
- 1
- 3
- 5
status: pending
UserId:
type: integer
example: 1
User:
type: object
properties:
id:
$ref: "#/components/schemas/UserId"
email:
type: string
example: user@example.com
name:
type: string
example: John Doe
example:
id: 1
email: user@example.com
name: John Doe
securitySchemes:
apiKey:
type: apiKey
in: header
name: X-API-Key
clientCredentials:
type: oauth2
flows:
clientCredentials:
tokenUrl: https://api.bookstore.com/oauth/token
refreshUrl: https://api.bookstore.com/oauth/refresh
scopes: {}

Our bookstore OpenAPI document is compliant with t OpenAPI 3.1, which is supported by both Speakeasy and Stainless. We define a basic info section and add a single development server.

Here we define two tags to organize our operations with: Books and Orders.

We define one global authentication method, apiKey.

Let's take a look at the operations we'll need an SDK for, starting with getAllBooks.

This operation takes no input.

What makes this operation interesting is that it returns an array of objects of three types: ProgrammingBook, FantasyBook, and SciFiBook. Each object's type is determined by the book's category.

This example allows us to test how our SDK generators handle discriminated unions in OpenAPI.

Next up, we have an operation that adds a book to the database, called addBook.

This operation takes one object of type ProgrammingBook, FantasyBook, or SciFiBook as input.

Our next book-related operation, updateBookCoverById, takes a book ID as a path variable, and an image as a binary payload.

We include this operation to test how our SDK generators handle binary payloads.

Our final book-related operation, getBookById, takes a book ID as a path variable, and returns one of our book objects.

Next up, we have an operation that returns a list of all orders in the database, called getAllOrders.

This operation returns an array of Order objects, so that we can test an array of nested objects.

Our next order-related operation, createOrder, takes an object of type NewOrder as input, and returns an object of type Order.

We include this one to test how our SDK generators help users avoid common mistakes, like passing the wrong type to an operation.

Finally, we have an operation that returns a stream of order events, called getOrderStream.

We include this operation to test how our SDK generators handle server-sent events.

The remainder of the OpenAPI document defines the components used in the operations above.

openapi.yaml

openapi: 3.1.0
info:
title: Bookstore API
description: API for a bookstore with categories Programming, Fantasy, and Sci-fi
version: 1.0.0
contact:
name: John Doe
email: john@example.com
url: https://example.com
license:
name: MIT
url: https://opensource.org/licenses/MIT
servers:
- url: http://127.0.0.1:4010
description: Local Prism server
tags:
- name: Books
description: Operations related to books
- name: Orders
description: Operations related to orders
security:
- apiKey: []
paths:
/books:
get:
summary: Get all books
operationId: getAllBooks
description: Returns a list of books
security:
- clientCredentials:
- books.read
tags:
- Books
responses:
"200":
description: A list of books
content:
application/json:
schema:
type: array
items:
oneOf:
- $ref: "#/components/schemas/ProgrammingBook"
- $ref: "#/components/schemas/FantasyBook"
- $ref: "#/components/schemas/SciFiBook"
discriminator:
propertyName: category
mapping:
Programming: "#/components/schemas/ProgrammingBook"
Fantasy: "#/components/schemas/FantasyBook"
Sci-fi: "#/components/schemas/SciFiBook"
examples:
example1:
summary: Programming book example
value:
- id: 1
title: Clean Code
description: A Handbook of Agile Software Craftsmanship
price: 2999
category: Programming
author:
id: 1
name: Robert C. Martin
photo: https://example.com/photos/robert.jpg
biography: Robert Cecil Martin, colloquially known as "Uncle Bob", is an American software engineer...
cover_image: https://example.com/covers/cleancode.jpg
- id: 2
title: The Hobbit
description: A fantasy novel by J.R.R. Tolkien
price: 1599
category: Fantasy
author:
id: 2
name: J.R.R. Tolkien
photo: https://example.com/photos/tolkien.jpg
biography: John Ronald Reuel Tolkien was an English writer, poet, philologist, and academic...
cover_image: https://example.com/covers/thehobbit.jpg
post:
summary: Add a new book
operationId: addBook
description: Adds a new book to the bookstore
security:
- apiKey: []
tags:
- Books
requestBody:
description: Book object to be added
required: true
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/ProgrammingBook"
- $ref: "#/components/schemas/FantasyBook"
- $ref: "#/components/schemas/SciFiBook"
discriminator:
propertyName: category
mapping:
Programming: "#/components/schemas/ProgrammingBook"
Fantasy: "#/components/schemas/FantasyBook"
Sci-fi: "#/components/schemas/SciFiBook"
examples:
example:
summary: Example book
value:
title: New Sci-Fi Book
description: A new Sci-Fi book description
price: 1999
category: Sci-fi
author:
name: New Author
photo: https://example.com/photos/newauthor.jpg
biography: New Author is an upcoming writer in the Sci-Fi genre...
cover_image: https://example.com/covers/newbook.jpg
responses:
"201":
description: Book created successfully
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/ProgrammingBook"
- $ref: "#/components/schemas/FantasyBook"
- $ref: "#/components/schemas/SciFiBook"
discriminator:
propertyName: category
mapping:
Programming: "#/components/schemas/ProgrammingBook"
Fantasy: "#/components/schemas/FantasyBook"
Sci-fi: "#/components/schemas/SciFiBook"
examples:
example:
summary: Example book
value:
id: 3
title: New Sci-Fi Book
description: A new Sci-Fi book description
price: 1999
category: Sci-fi
author:
name: New Author
photo: https://example.com/photos/newauthor.jpg
biography: New Author is an upcoming writer in the Sci-Fi genre...
cover_image: https://example.com/covers/newbook.jpg
/books/{bookId}:
get:
summary: Get a book by ID
operationId: getBookById
description: Returns a single book
security: []
tags:
- Books
parameters:
- name: bookId
description: ID of the book to return
in: path
required: true
schema:
type: integer
example: 1
responses:
"200":
description: A single book
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/ProgrammingBook"
- $ref: "#/components/schemas/FantasyBook"
- $ref: "#/components/schemas/SciFiBook"
discriminator:
propertyName: category
mapping:
Programming: "#/components/schemas/ProgrammingBook"
Fantasy: "#/components/schemas/FantasyBook"
Sci-fi: "#/components/schemas/SciFiBook"
examples:
example1:
summary: Programming book example
value:
id: 1
title: Clean Code
description: A Handbook of Agile Software Craftsmanship
price: 2999
category: Programming
author:
id: 1
name: Robert C. Martin
photo: https://example.com/photos/robert.jpg
biography: Robert Cecil Martin, colloquially known as "Uncle Bob", is an American software engineer...
cover_image: https://example.com/covers/cleancode.jpg
example2:
summary: Fantasy book example
value:
id: 2
title: The Hobbit
description: A fantasy novel by J.R.R. Tolkien
price: 1599
category: Fantasy
author:
id: 2
name: J.R.R. Tolkien
photo: https://example.com/photos/tolkien.jpg
biography: John Ronald Reuel Tolkien was an English writer, poet, philologist, and academic...
cover_image: https://example.com/covers/thehobbit.jpg
/books/{bookId}/cover:
put:
summary: Update a book cover by ID
operationId: updateBookCoverById
description: Updates a single book cover
security:
- apiKey: []
tags:
- Books
parameters:
- name: bookId
description: ID of the book to update
in: path
required: true
schema:
type: integer
example: 1
requestBody:
description: Book cover
required: true
content:
multipart/form-data:
schema:
type: object
properties:
cover:
type: string
format: binary
responses:
"200":
description: Book cover updated successfully
/orders:
get:
summary: Get all orders
operationId: getAllOrders
description: Returns a list of orders
tags:
- Orders
security:
- clientCredentials:
- orders.read
responses:
"200":
description: A list of orders
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Order"
examples:
example:
summary: Example response
value:
- id: 1
date: 2023-05-17T09:24:00Z
status: pending
user:
id: 1
email: test@example.com
name: John Doe
products:
- id: 1
title: Clean Code
description: A Handbook of Agile Software Craftsmanship
price: 2999
category: Programming
author:
id: 1
name: Robert C. Martin
photo: https://example.com/photos/robert.jpg
biography: Robert Cecil Martin, colloquially known as "Uncle Bob", is an American software engineer...
cover_image: https://example.com/covers/cleancode.jpg
post:
summary: Create a new order
operationId: createOrder
description: Creates a new order
tags:
- Orders
security:
- clientCredentials:
- orders.write
requestBody:
description: Order object to be created
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/NewOrder"
examples:
example:
summary: Example order
value:
user: 1
products:
- 1
- 3
status: pending
responses:
"201":
description: Order created successfully
content:
application/json:
schema:
$ref: "#/components/schemas/Order"
/orders/{orderId}:
get:
summary: Get an order by ID
operationId: getOrderById
description: Returns a single order
tags:
- Orders
security:
- clientCredentials:
- orders.read
parameters:
- name: orderId
description: ID of the order to return
in: path
required: true
schema:
type: integer
example: 1
responses:
"200":
description: A single order
content:
application/json:
schema:
$ref: "#/components/schemas/Order"
examples:
example:
summary: Example response
value:
id: 1
date: 2023-05-17T09:24:00Z
status: pending
user:
id: 1
email: user@example.com
products:
- id: 1
title: Clean Code
description: A Handbook of Agile Software Craftsmanship
price: 2999
category: Programming
author:
id: 1
name: Robert C. Martin
photo: https://example.com/photos/robert.jpg
biography: Robert Cecil Martin, colloquially known as "Uncle Bob", is an American software engineer...
cover_image: https://example.com/covers/cleancode.jpg
/orderstream:
get:
summary: Get a stream of orders
operationId: getOrderStream
description: Returns a stream of orders
tags:
- Orders
security:
- apiKey: []
responses:
"200":
description: A stream of orders
content:
text/event-stream:
schema:
$ref: "#/components/schemas/OrderStreamMessage"
components:
schemas:
ProductId:
type: integer
example: 1
Book:
type: object
required:
- title
- description
- price
- category
- author
properties:
id:
$ref: "#/components/schemas/ProductId"
title:
type: string
example: Clean Code
description:
type: string
example: A Handbook of Agile Software Craftsmanship
price:
type: integer
description: Price in USD cents
example: 2999
category:
type: string
enum:
- Sci-fi
- Fantasy
- Programming
example: Programming
author:
$ref: "#/components/schemas/Author"
cover_image:
type: string
example: https://example.com/covers/cleancode.jpg
example:
id: 1
title: Clean Code
description: A Handbook of Agile Software Craftsmanship
price: 2999
author:
id: 1
name: Robert C. Martin
photo: https://example.com/photos/robert.jpg
biography: Robert Cecil Martin, colloquially known as "Uncle Bob", is an American software engineer...
category: Programming
SciFiBook:
allOf:
- $ref: "#/components/schemas/Book"
- type: object
properties:
category:
type: string
const: Sci-fi
example: Sci-fi
example:
id: 3
title: New Sci-Fi Book
description: A new Sci-Fi book description
price: 1999
category: Sci-fi
cover_image: https://example.com/covers/newbook.jpg
author:
name: New Author
photo: https://example.com/photos/newauthor.jpg
biography: New Author is an upcoming writer in the Sci-Fi genre...
FantasyBook:
allOf:
- $ref: "#/components/schemas/Book"
- type: object
properties:
category:
type: string
const: Fantasy
example: Fantasy
example:
id: 2
title: The Hobbit
description: A fantasy novel by J.R.R. Tolkien
price: 1599
category: Fantasy
ProgrammingBook:
allOf:
- $ref: "#/components/schemas/Book"
- type: object
properties:
category:
type: string
const: Programming
example: Programming
example:
id: 1
title: Clean Code
description: A Handbook of Agile Software Craftsmanship
price: 2999
category: Programming
AuthorId:
type: integer
example: 1
Author:
type: object
properties:
id:
$ref: "#/components/schemas/AuthorId"
name:
type: string
example: Robert C. Martin
photo:
type: string
example: https://example.com/photos/robert.jpg
biography:
type: string
example: Robert Cecil Martin, colloquially known as "Uncle Bob", is an American software engineer...
anyOf:
- required:
- name
title: Author with name
- required:
- id
title: Author with ID
example:
id: 1
name: Robert C. Martin
photo: https://example.com/photos/robert.jpg
biography: Robert Cecil Martin, colloquially known as "Uncle Bob", is an American software engineer...
OrderId:
type: integer
example: 1
Order:
type: object
required:
- id
- date
- status
- user
- products
properties:
id:
$ref: "#/components/schemas/OrderId"
date:
type: string
format: date-time
example: 2023-05-17T09:24:00Z
status:
type: string
enum:
- pending
- shipped
- delivered
example: pending
user:
$ref: "#/components/schemas/User"
products:
type: array
items:
oneOf:
- $ref: "#/components/schemas/FantasyBook"
- $ref: "#/components/schemas/ProgrammingBook"
- $ref: "#/components/schemas/SciFiBook"
discriminator:
propertyName: category
mapping:
Programming: "#/components/schemas/ProgrammingBook"
Fantasy: "#/components/schemas/FantasyBook"
Sci-fi: "#/components/schemas/SciFiBook"
example:
id: 1
date: 2023-05-17T09:24:00Z
status: pending
user:
id: 1
email: user@example.com
name: John Doe
products:
- id: 1
title: Clean Code
description: A Handbook of Agile Software Craftsmanship
price: 2999
category: Programming
author:
id: 1
name: Robert C. Martin
photo: https://example.com/photos/robert.jpg
biography: Robert Cecil Martin, colloquially known as "Uncle Bob", is an American software engineer...
- id: 2
title: The Hobbit
description: A fantasy novel by J.R.R. Tolkien
price: 1599
category: Fantasy
author:
id: 2
name: J.R.R. Tolkien
photo: https://example.com/photos/tolkien.jpg
biography: John Ronald Reuel Tolkien was an English writer, poet, philologist, and academic...
OrderStreamMessage:
type: object
description: A message in the order stream
required:
- id
- event
- data
properties:
id:
type: string
title: Message ID
format: UUID
example: 123e4567-e89b-12d3-a456-426614174000
event:
title: Event type
type: string
example: order_created
data:
$ref: "#/components/schemas/Order"
NewOrder:
type: object
required:
- user
- products
- status
properties:
user:
$ref: "#/components/schemas/UserId"
products:
type: array
items:
$ref: "#/components/schemas/ProductId"
example:
user: 1
products:
- 1
- 3
- 5
status: pending
UserId:
type: integer
example: 1
User:
type: object
properties:
id:
$ref: "#/components/schemas/UserId"
email:
type: string
example: user@example.com
name:
type: string
example: John Doe
example:
id: 1
email: user@example.com
name: John Doe
securitySchemes:
apiKey:
type: apiKey
in: header
name: X-API-Key
clientCredentials:
type: oauth2
flows:
clientCredentials:
tokenUrl: https://api.bookstore.com/oauth/token
refreshUrl: https://api.bookstore.com/oauth/refresh
scopes: {}

Linting OpenAPI Documents

Before generating an SDK, it's a good idea to lint your OpenAPI document. This ensures that your document is valid and that your SDK will be generated correctly.

Speakeasy's CLI includes a linter that checks your OpenAPI document for errors and warnings, and provides helpful hints on how to improve your document.

To lint your OpenAPI document, run the following command in the terminal:

Terminal

speakeasy lint openapi -s openapi.yaml

Stainless does not currently offer a linter for OpenAPI documents, so you'll need to use an external tool to lint your document locally.

Generating SDKs: Speakeasy CLI vs. Stainless Studio

Speakeasy and Stainless both offer a web interface, but Speakeasy also provides a CLI. The CLI enables easier local experimentation and iteration, custom CI/CD workflows, and consistent onboarding for our users and your team - from your first day with Speakeasy all the way to production.

Stainless Studio, on the other hand, is good for visualizing spec changes and seeing how the changes impact SDKs. Although, in our experience, this type of experimentation is better suited for local development.

Speakeasy's CLI creates sharable links when linting OpenAPI documents, so your team can collaborate remotely right from the terminal.

Let's generate an SDK using each method.

Creating an SDK using Speakeasy CLI

Speakeasy CLI is distributed as a single binary, which you can install directly from GitHub (opens in a new tab), or by using Homebrew on macOS:

Terminal

brew install speakeasy

To generate an SDK, locate your openapi.yaml file and run the following in the terminal:

Terminal

speakeasy generate sdk \
--schema openapi.yaml \
--lang typescript \
--out ./sdks/bookstore-ts/

Speakeasy lints the OpenAPI document, then creates a new folder with the generated SDK.

This happens locally, and you have immediate access to start testing your SDK. We'll explore the SDK shortly.

Generating an SDK using Stainless Studio

Stainless Studio is a web-based tool that allows you to upload your OpenAPI document and generate an SDK. To generate an SDK, navigate to Stainless Studio (opens in a new tab) and upload your OpenAPI document.

The free Stainless plan allows for one TypeScript SDK targeting Node.

Stainless creates a new repo under the stainless-sdks organization on GitHub, then invites your GitHub user as a contributor.

We cloned this repository to a local machine to start exploring the SDK.

Setting Up a Mock Server

We used Stoplight Prism (opens in a new tab) to generate a mock server to test our SDKs:

Terminal

npm install -g @stoplight/prism-cli
prism mock openapi.yaml

This command starts a mock server at http://localhost:4010.

TypeScript SDK comparison

Now that we have two TypeScript SDKs generated from a single OpenAPI document, let's see how they differ.

SDK Structure Overview

Before we dive into the detail, let's get an overall view of the default project structure for each SDK.

At a high level, the most obvious differences are:

  1. Speakeasy does not create SDKs with a particular package manager in mind, while Stainless generates for Yarn (currently, yarn@1.22.22), including a yarn.lock file.
  2. Speakeasy generates detailed documentation for each operation and component, while Stainless generates an empty examples folder.
  3. Stainless generates a tests folder, while Speakeasy does not. We'll take a closer look at this shortly.

In the comparison below, comparing the folder structure might seem superficial at first, but keep in mind that SDK users get the same kind of high-level glance as their first impression of your SDK. Some of this may be a matter of opinion, but at Speakeasy we aim to generate SDKs that are as organized as SDKs coded by hand.

Speakeasy SDK Structure

Speakeasy generates separate folders for models and operations, both in the documentation and in the source folder. This indicates a clear separation of concerns.

We also see separate files for each component and operation, indicating modularity and separation of concerns.

  • README.md
  • RUNTIMES.md
  • USAGE.md
  • jsr.json
  • package.json
  • tsconfig.json
  • Stainless SDK Structure

    Stainless generates an SDK that at a glance looks less organized, considering the greater number of configuration files at the root of the project, no separation of models and operations, and a larger number of shims scattered throughout.

  • Brewfile
  • CONTRIBUTING.md
  • LICENSE
  • README.md
  • SECURITY.md
  • api.md
  • jest.config.ts
  • package.json
  • tsc-multi.json
  • tsconfig.build.json
  • tsconfig.deno.json
  • tsconfig.dist-src.json
  • tsconfig.json
  • yarn.lock
  • SDK Code Comparison

    With the bird's-eye view out of the way, let's take a closer look at the code.

    Generated Types

    Both Speakeasy and Stainless generate TypeScript types, enabling developers to see errors and hints during development. However, Stainless does not generate types for complex OpenAPI component schemas.

    For example, consider the following Author component form our OpenAPI document.

    openapi.yaml

    Author:
    type: object
    properties:
    id:
    $ref: "#/components/schemas/AuthorId"
    name:
    type: string
    example: Robert C. Martin
    photo:
    type: string
    example: https://example.com/photos/robert.jpg
    biography:
    type: string
    example: Robert Cecil Martin, colloquially known as "Uncle Bob", is an American software engineer...
    anyOf:
    - required:
    - name
    title: Author with name
    - required:
    - id
    title: Author with ID
    example:
    id: 1
    name: Robert C. Martin
    photo: https://example.com/photos/robert.jpg
    biography: Robert Cecil Martin, colloquially known as "Uncle Bob", is an American software engineer...

    The highlighted anyOf list is of particular interest. This list states that a valid Author object must have a name or an id, or both. An author with neither a name nor an id should not validate against this schema.

    Let's take a look at the relevant types generated by each SDK generator, starting with Speakeasy:

    speakeasy/author.ts

    /*
    * Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT.
    */
    // ...
    export type AuthorWithID = {
    id: number;
    name?: string | undefined;
    photo?: string | undefined;
    biography?: string | undefined;
    };
    export type AuthorWithName = {
    id?: number | undefined;
    name: string;
    photo?: string | undefined;
    biography?: string | undefined;
    };
    export type Author = AuthorWithName | AuthorWithID;

    Here we see that Speakeasy generates three types for the Author schema: AuthorWithID, AuthorWithName, and a union of these, called Author. We've highlighted the required fields and the union above.

    These types and their names are generated from nothing more than the OpenAPI schema above.

    The equivalent type generated by Stainless looks as follows:

    stainless/books.ts

    // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
    // ...
    export namespace FantasyBook {
    export interface Author {
    id?: number;
    biography?: string;
    name?: string;
    photo?: string;
    }
    }
    // Repeated for ProgrammingBook and SciFiBook

    Stainless generates an Author interface for each book type from our OpenAPI document, most likely because the Author schema is only used in operations related to books, and is only ever referenced as a sub-schema of the Book schema and its children.

    The highlighted lines in the snippet above show how Stainless generates an Author type with both id and name marked as optional.

    As a result, the following TypeScript code based on the Stainless will compile without any warnings:

    stainless-example.ts

    // techbooks SDK generated by Stainless
    import { Techbooks } from "techbooks";
    const bookStore = new Techbooks({
    apiKey: "My API Key",
    clientId: "My Client ID",
    clientSecret: "My Client Secret",
    });
    async function run() {
    const params: Techbooks.BookCreateParams = {
    author: {},
    category: "Programming",
    description: "A Handbook of Agile Software Craftsmanship",
    price: 2999,
    title: "Clean Code",
    };
    const result: Techbooks.BookCreateResponse = await bookStore.books.create(
    params
    );
    }
    run();

    Let's use the same new book object to add a new book using the SDK created by Speakeasy:

    speakeasy-example.ts

    // techbooks-speakeasy SDK created by Speakeasy
    import { TechBooks } from "techbooks-speakeasy";
    const bookStore = new TechBooks({
    apiKey: "123",
    });
    async function run() {
    await bookStore.books.addBook({
    author: {},
    category: "Programming",
    description: "A Handbook of Agile Software Craftsmanship",
    price: 2999,
    title: "Clean Code",
    });
    }
    run();

    Compiling this TypeScript will fail with the following error:

    speakeasy-test.ts:11:5 - error TS2322: Type {} is not assignable to type Author.

    This is clearly the correct behavior, considering the Author schema's required fields.

    Runtime Type Checking

    This brings us to the next type error that should be caught: Runtime type errors.

    Speakeasy creates SDKs that are type safe from development to production. As our CEO recently wrote, Type Safe is better than Type Faith (opens in a new tab).

    If we add a valid Author object to our example code, both versions will compile, but only the example based on Speakeasy's SDK works as expected.

    The SDK created by Speakeasy uses Zod (opens in a new tab) to validate data at runtime. Data sent to the server and data received from the server are validated against Zod definitions in the client.

    This provides safer runtime code execution and helps developers who use your SDK to provide early feedback about data entered by their end users. Furthermore, trusting data validation on the client side allows developers more confidence to build optimistic UIs (opens in a new tab) that update as soon as an end user enters data, greatly improving end users' perception of your API's speed.

    Let's see how Speakeasy's runtime type checking works in an example.

    Consider the following Book component from our OpenAPI document:


    Book:
    type: object
    required:
    - title
    - description
    - price
    - category
    - author
    properties:
    id:
    $ref: "#/components/schemas/ProductId"
    title:
    type: string
    example: Clean Code
    description:
    type: string
    example: A Handbook of Agile Software Craftsmanship
    price:
    type: integer
    description: Price in USD cents
    example: 2999
    category:
    type: string
    enum:
    - Sci-fi
    - Fantasy
    - Programming
    example: Programming

    The highlighted price field above has type integer.

    speakeasy-example.ts

    // techbooks-speakeasy SDK created by Speakeasy
    import { TechBooks } from "techbooks-speakeasy";
    const bookStore = new TechBooks({
    apiKey: "123",
    });
    async function run() {
    await bookStore.books.addBook({
    author: {
    name: "Robert C. Martin",
    photo: "https://example.com/photos/robert.jpg",
    biography: 'Robert Cecil Martin, colloquially known as "Uncle Bob", is an American software engineer...',
    },
    category: "Programming",
    description: "A Handbook of Agile Software Craftsmanship",
    price: 29.99,
    title: "Clean Code",
    });
    }
    run();

    The price field in the Book object in our test code is set to 29.99, which is a floating-point number. This will cause a validation error before the data is sent to the server, as the price field is expected to be an integer.

    Handling Zod validation errors (opens in a new tab) is straightforward, and allows developers to provide meaningful feedback to their end users early in the process.

    The same book object in code using the SDK generated by Stainless will only be validated on the server. This means that the error will only be caught from the client's perspective after the data is sent to the server, and the server responds with an error message.

    If the server is not set up to validate the price field, the error will not be caught at all, leading to unexpected behavior in your developer-users' applications.

    As a result, developers using the SDK generated by Stainless may need to write additional client-side validation code to catch these errors before they are sent to the server.

    Dependency Injection: SDK Hooks vs. Custom Code

    While we're on the topic of adding custom code to generated SDKs: Speakeasy generates a clean mechanism for safely injecting custom code.

    The abridged code below is from the SDK generated by Speakeasy:

    speakeasy/hooks/types.ts

    /*
    * Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT.
    */
    // ...
    export interface BeforeCreateRequestHook {
    /**
    * A hook that is called before the SDK creates a `Request` object. The hook
    * can modify how a request is constructed since certain modifications, like
    * changing the request URL, cannot be done on a request object directly.
    */
    beforeCreateRequest: (hookCtx: BeforeCreateRequestContext, input: RequestInput) => RequestInput;
    }
    export interface BeforeRequestHook {
    /**
    * A hook that is called before the SDK sends a request. The hook can
    * introduce instrumentation code such as logging, tracing and metrics or
    * replace the request before it is sent or throw an error to stop the
    * request from being sent.
    */
    beforeRequest: (hookCtx: BeforeRequestContext, request: Request) => Awaitable<Request>;
    }
    // ...

    The types above are well documented, but you can read more about Speakeasy SDK Hooks (opens in a new tab) in Speakeasy's documentation.

    In short, hooks are typed and contain relevant context depending on when in the lifecycle they are applied. To add hooks, register hooks in the src/hooks/registration.ts file in your TypeScript SDK.

    Here's an example hook:

    src/hooks/registration.ts

    import { Hooks } from "./types";
    /*
    * This file is only ever generated once on the first generation and then is free to be modified.
    * Any hooks you wish to add should be registered in the initHooks function. Feel free to define them
    * in this file or in separate files in the hooks folder.
    */
    export function initHooks(hooks: Hooks) {
    // Add hooks by calling hooks.register{ClientInit/BeforeCreateRequest/BeforeRequest/AfterSuccess/AfterError}Hook
    // with an instance of a hook that implements that specific Hook interface
    // Hooks are registered per SDK instance, and are valid for the lifetime of the SDK instance
    hooks.registerBeforeCreateRequestHook(
    {
    beforeCreateRequest: (hookCtx, input) => {
    // Modify the request input here
    console.log("BeforeCreateRequestHook", input);
    console.log("HookContext", hookCtx);
    return input;
    },
    }
    );
    }

    Speakeasy also provides a clean abstraction to add dependencies to the SDK, by specifying dependencies in the SDK's gen.yaml file:

    gen.yaml

    typescript:
    additionalDependencies:
    dependencies:
    uuid: ^9.0.1
    devDependencies:
    "@types/uuid": "^9.0.8"
    peerDependencies: {}

    In contrast, Stainless documents the process of patching (opens in a new tab) generated SDKs with custom code, and provides strategies to avoid or resolve merge conflicts.

    Patching generated code with custom code is a recipe for maintenance headaches and delayed SDK releases. We much prefer the clean separation afforded by hooks.

    OAuth Client Credentials Handling

    Both Speakeasy and Stainless generate SDKs that handle OAuth 2.0 with client credentials. However, only Speakeasy's SDKs handle the token lifecycle, retries, and error handling without any additional code.

    Our bookstore API requires an OAuth 2.0 token with client credentials to access the API. Let's see how the SDKs handle this.

    Consider the following OAuth 2.0 configuration from our OpenAPI document:

    openapi.yaml

    clientCredentials:
    type: oauth2
    flows:
    clientCredentials:
    tokenUrl: https://api.bookstore.com/oauth/token
    refreshUrl: https://api.bookstore.com/oauth/refresh
    scopes: {}

    Speakeasy's generated SDK takes a clientID and clientSecret when instantiating the SDK. The SDK also includes ClientCredentialsHook class that implements BeforeRequestHook to check whether the token is expired and refresh it if necessary. The hook also checks whether the client has the necessary scopes to access the endpoint, and handles authentication errors.

    speakeasy-example.ts

    // techbooks-speakeasy SDK created by Speakeasy
    import { TechBooks } from "techbooks-speakeasy";
    const bookStore = new TechBooks({
    security: {
    // OAuth 2.0 client credentials
    clientID: "<YOUR_CLIENT_ID_HERE>",
    clientSecret: "<YOUR_CLIENT_SECRET_HERE>",
    },
    });
    async function run() {
    // The SDK handles the token lifecycle, retries, and error handling for you
    await bookStore.books.addBook({
    // Book object
    });
    }
    run();

    The SDK generated by Stainless includes the same authentication fields, but it does not handle the token lifecycle, retries, or error handling. This means that developers using the SDK generated by Stainless will need to write additional code to handle these scenarios.

    Server-Sent Events (SSE) and Streaming Responses

    Our bookstore API includes an operation that streams orders to the client using Server-Sent Events (SSE).


    paths:
    /orderstream:
    get:
    summary: Get a stream of orders
    operationId: getOrderStream
    description: Returns a stream of orders
    tags:
    - Orders
    security:
    - apiKey: []
    responses:
    '200':
    description: A stream of orders
    content:
    text/event-stream:
    schema:
    $ref: '#/components/schemas/OrderStreamMessage'

    Let's see how the SDKs handle this.

    Speakeasy generates types and methods for handling SSE (opens in a new tab) without any customization. Here's an example of how to use the SDK to listen for new orders:

    speakeasy-example.ts

    import { TechBooks } from "techbooks-speakeasy";
    const bookStore = new TechBooks({
    apiKey: 'KEY123',
    });
    async function run() {
    const result = await bookStore.orders.getOrderStream();
    if (result.orderStreamMessage == null) {
    throw new Error('Failed to create stream: received null value');
    }
    const stream = result.orderStreamMessage.stream;
    if (!stream || typeof stream.getReader !== 'function') {
    throw new Error('Invalid stream: expected a ReadableStream');
    }
    const reader = stream.getReader();
    try {
    while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    console.log(new TextDecoder().decode(value));
    }
    } catch (error) {
    console.error('Error reading stream', error);
    } finally {
    reader.releaseLock();
    }
    }
    run();

    (The example above does not run against a local Prism server, but you can test it against Stoplight's hosted Prism (opens in a new tab) server.)

    Stainless does not generate SSE handling code, but offers to configure SSE handling (opens in a new tab) in the SDK for enterprise customers.

    Streaming Uploads

    Both Speakeasy and Stainless support streaming uploads without any custom configuration. OpenAPI operations with multipart/form-data content types are automatically handled as streaming uploads.

    The following example illustrates how to use an SDK created by Speakeasy to upload a large file:

    speakeasy-example.ts

    import { openAsBlob } from "node:fs";
    import { SDK } from "@speakeasy/super-sdk";
    async function run() {
    const sdk = new SDK();
    const fileHandle = await openAsBlob("./src/sample.txt");
    const result = await sdk.upload({ file: fileHandle });
    console.log(result);
    }
    run();

    Discriminated Unions

    Our OpenAPI document includes a Book component with a category field that can be one of three values: Programming, Fantasy, or SciFi.

    This allows us to type the Book component in requests and responses as specific book types, such as ProgrammingBook, FantasyBook, and SciFiBook.

    OpenAPI supports discriminated unions using the discriminator field in the schema. Here's an example of a response that returns an array of books of different types:

    openapi.yaml

    schema:
    type: array
    items:
    oneOf:
    - $ref: '#/components/schemas/ProgrammingBook'
    - $ref: '#/components/schemas/FantasyBook'
    - $ref: '#/components/schemas/SciFiBook'
    discriminator:
    propertyName: category
    mapping:
    Programming: '#/components/schemas/ProgrammingBook'
    Fantasy: '#/components/schemas/FantasyBook'
    Sci-fi: '#/components/schemas/SciFiBook'

    Let's see how the SDKs handle this.

    Speakeasy generates TypeScript types for each book type, and uses a discriminated union to handle the different book types. This enables developers to use the correct type when working with books of different categories. This pattern could just as easily apply to payment methods or delivery options.

    The example below shows how Speakeasy defines the ProgrammingBook type. It also generates types for FantasyBook and SciFiBook.

    In this example, you'll notice that the category field is optional in the ProgrammingBook type, but is enforced by Zod validation in the SDK.

    speakeasy/books.ts

    /*
    * Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT.
    */
    import { Author, Author$ } from "./author";
    import * as z from "zod";
    export type ProgrammingBook = {
    id?: number | undefined;
    title: string;
    description: string;
    /**
    * Price in USD cents
    */
    price: number;
    category?: "Programming" | undefined;
    author: Author;
    coverImage?: string | undefined;
    };
    /** @internal */
    export namespace ProgrammingBook$ {
    export const inboundSchema: z.ZodType<ProgrammingBook, z.ZodTypeDef, unknown> = z
    .object({
    id: z.number().int().optional(),
    title: z.string(),
    description: z.string(),
    price: z.number().int(),
    category: z.literal("Programming").optional(),
    author: Author$.inboundSchema,
    cover_image: z.string().optional(),
    })
    .transform((v) => {
    return {
    ...(v.id === undefined ? null : { id: v.id }),
    title: v.title,
    description: v.description,
    price: v.price,
    ...(v.category === undefined ? null : { category: v.category }),
    author: v.author,
    ...(v.cover_image === undefined ? null : { coverImage: v.cover_image }),
    };
    });
    export type Outbound = {
    id?: number | undefined;
    title: string;
    description: string;
    price: number;
    category: "Programming";
    author: Author$.Outbound;
    cover_image?: string | undefined;
    };
    export const outboundSchema: z.ZodType<Outbound, z.ZodTypeDef, ProgrammingBook> = z
    .object({
    id: z.number().int().optional(),
    title: z.string(),
    description: z.string(),
    price: z.number().int(),
    category: z.literal("Programming").default("Programming" as const),
    author: Author$.outboundSchema,
    coverImage: z.string().optional(),
    })
    .transform((v) => {
    return {
    ...(v.id === undefined ? null : { id: v.id }),
    title: v.title,
    description: v.description,
    price: v.price,
    category: v.category,
    author: v.author,
    ...(v.coverImage === undefined ? null : { cover_image: v.coverImage }),
    };
    });
    }

    We can see how Speakeasy generates SDK code to handle the different book types in the response for the getgetAllBooks operation:

    speakeasy/getallbooks.ts

    /*
    * Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT.
    */
    import * as components from "../components";
    import * as z from "zod";
    export type ResponseBody =
    | (components.ProgrammingBook & { category: "Programming" })
    | (components.FantasyBook & { category: "Fantasy" })
    | (components.SciFiBook & { category: "Sci-fi" });
    export type GetAllBooksResponse = {
    httpMeta: components.HTTPMetadata;
    /**
    * A list of books
    */
    responseBodies?:
    | Array<
    | (components.ProgrammingBook & { category: "Programming" })
    | (components.FantasyBook & { category: "Fantasy" })
    | (components.SciFiBook & { category: "Sci-fi" })
    >
    | undefined;
    };
    // ...

    Note how the array elements in responseBodies are typed according to the book category.

    This may seem like a trivial example, but it illustrates how Speakeasy generates types that are more specific and easier to work with than the types generated by Stainless. This could, for instance, help developers correctly handle different book types in their applications.

    Stainless does not generate types for discriminated unions, and developers must manually handle the different book types in the response.

    Here is the equivalent type definition generated by Stainless:

    stainless/books.ts

    // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
    // ...
    export interface ProgrammingBook {
    author: ProgrammingBook.Author;
    category: 'Programming' | 'Sci-fi' | 'Fantasy';
    description: string;
    /**
    * Price in USD cents
    */
    price: number;
    title: string;
    id?: number;
    cover_image?: string;
    }
    // ...
    export type BookListResponse = Array<ProgrammingBook | FantasyBook | SciFiBook>;
    // ...

    Discriminating between different book types in the response is left to the developer using the SDK.

    OpenAPI Extensions and Overlays vs. Stainless Config

    Speakeasy embraces OpenAPI as the source of truth for generating SDKs. This means that Speakeasy does not require any additional configuration files to generate SDKs, apart from minimal configuration in the gen.yaml file.

    Any configuration related to individual operations or components is done in the OpenAPI document itself, using OpenAPI extensions. Speakeasy provides a list of supported OpenAPI extensions (opens in a new tab) in its documentation.

    If editing your OpenAPI document is not an option, Speakeasy also supports the OpenAPI Overlays (opens in a new tab) specification, which allows you to add or override parts of an OpenAPI document without modifying the original document.

    This step can form part of your CI/CD pipeline, ensuring that your SDKs are always up-to-date with your API, even if your OpenAPI document is generated from code.

    Speakeasy's CLI can also generate OpenAPI overlays for you, based on the differences between two OpenAPI documents.

    Instead of using OpenAPI extensions, Stainless uses a configuration DSL (opens in a new tab) to customize SDKs. This configuration overrides many of the aspects Speakeasy allows you to configure in the OpenAPI document itself.

    Linting and Change Detection

    Speakeasy's CLI includes a detailed and accurate linter that checks your OpenAPI document and provides feedback. This is especially useful during development, but can also catch errors in your CI/CD pipeline.

    Speakeasy also keeps track of changes in your OpenAPI document, and versions the SDKs it creates based on changes.

    Automation and CI/CD

    Speakeasy's CLI, a single binary, is designed to be used in CI/CD pipelines, and can be used to automate the generation of SDKs based on changes in your OpenAPI document.

    While Stainless also supports CI/CD, the lack of a CLI means that you'll need to use the web interface to generate SDKs, which may not be as convenient for automation - especially if you want to test generated SDKs locally before committing changes.

    Speakeasy Compared to Open Source Generators

    If you are interested in seeing how Speakeasy stacks up against other SDK generation tools, check out our post.

    CTA background illustrations

    Speakeasy Changelog

    Subscribe to stay up-to-date on Speakeasy news and feature releases.