OpenAPI Overview and Best Practices

OpenAPI is a standard for describing RESTful APIs. Developers use OpenAPI to define an API's core elements, like endpoints, request and response data formats, authentication methods, and so on.

Several versions of the OpenAPI specification are in circulation: 2.0 (known as Swagger), 3.0, and 3.1.

Speakeasy supports OpenAPI versions 3.0 and 3.1 and recommends OpenAPI version 3.1 for all projects. The advantage of OpenAPI version 3.1 is that it's fully compatible with JSON Schema (opens in a new tab), which gives you access to a large ecosystem of tools and libraries.

OpenAPI Best Practices

OpenAPI has a lot of baked-in flexibility and can describe any HTTP API, including REST APIs and even RPC-based calls. The OpenAPI Specification has many ways to achieve the same result that are equally valid according to the OpenAPI Specification.

With so much flexibility, it isn't always obvious how to construct an OpenAPI spec that's sufficient for code generation. Speakeasy recommends a set of best practices to follow when writing OpenAPI documents. The following sections outline key points to consider as you create your OpenAPI description.

servers

Add multiple servers to define different environments or versions. This is especially useful for separating production and testing environments.

openapi.yaml

openapi: 3.1.0
info:
title: The Speakeasy Bar
version: 1.0.0
servers:
- url: https://speakeasy.bar
description: The production server
- url: https://speakeasy.bar/testing
description: The testing server
security:
- apiKey: []
paths:
/drinks:
get:
tags:
- drinks
operationId: listDrinks
summary: Get a list of drinks
responses:
"200":
description: A list of drinks
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Drink"
"400":
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/BadRequest'
components:
schemas:
Drink:
type: object
title: Drink
properties:
name:
type: string
price:
type: number
description: A response containing a list of drinks.
examples:
exampleResponse:
summary: Example response for a list of drinks
value:
drinks:
- name: "Coffee"
price: 2.5
- name: "Tea"
price: 1.8
BadRequest:
type: object
title: BadRequest
properties:
error:
type: string
message:
type: string
securitySchemes:
apiKey:
type: apiKey
name: Authorization
in: header

tags

The tags property contains an optional list values used to group or categorize a set of operations. We strongly recommend that you always define tags for operations but it's not required.

In code generation

Tags are used to namespace methods in the SDK. For example, if you have a tag called drinks, then all the methods for that tag will be namespaced under drinks.listDrinks(). You can create multi-level namespaces by using a . in the tag name, for example, menu.drinks will become menu.drinks.listDrinks().

openapi.yaml

openapi: 3.1.0
info:
title: The Speakeasy Bar
version: 1.0.0
servers:
- url: https://speakeasy.bar
description: The production server
- url: https://speakeasy.bar/testing
description: The testing server
security:
- apiKey: []
paths:
/drinks:
get:
tags:
- drinks
operationId: listDrinks
summary: Get a list of drinks
responses:
"200":
description: A list of drinks
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Drink"
"400":
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/BadRequest'
components:
schemas:
Drink:
type: object
title: Drink
properties:
name:
type: string
price:
type: number
description: A response containing a list of drinks.
examples:
exampleResponse:
summary: Example response for a list of drinks
value:
drinks:
- name: "Coffee"
price: 2.5
- name: "Tea"
price: 1.8
BadRequest:
type: object
title: BadRequest
properties:
error:
type: string
message:
type: string
securitySchemes:
apiKey:
type: apiKey
name: Authorization
in: header

operationId

The operationId value is a unique identifier for the operation and must be unique in the document and is case sensitive. We strongly recommend that you always define an operationId, but it's not required.

In code generation

The operationId value is used to create the name of the method that will be generated for the operation. We recommend you follow a consistent pattern for naming your operations, for example, listDrinks, createDrink, updateDrink, and deleteDrink.

If you are generating your spec from an API framework, ensure that operationId values are human-readable. Some frameworks, like FastAPI, create long operationId identifiers that result in method names that are not idiomatic.

openapi.yaml

openapi: 3.1.0
info:
title: The Speakeasy Bar
version: 1.0.0
servers:
- url: https://speakeasy.bar
description: The production server
- url: https://speakeasy.bar/testing
description: The testing server
security:
- apiKey: []
paths:
/drinks:
get:
tags:
- drinks
operationId: listDrinks
summary: Get a list of drinks
responses:
"200":
description: A list of drinks
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Drink"
"400":
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/BadRequest'
components:
schemas:
Drink:
type: object
title: Drink
properties:
name:
type: string
price:
type: number
description: A response containing a list of drinks.
examples:
exampleResponse:
summary: Example response for a list of drinks
value:
drinks:
- name: "Coffee"
price: 2.5
- name: "Tea"
price: 1.8
BadRequest:
type: object
title: BadRequest
properties:
error:
type: string
message:
type: string
securitySchemes:
apiKey:
type: apiKey
name: Authorization
in: header

$ref

The $ref keyword in OpenAPI references components defined in the Components Object, which are commonly used for reusable elements like schemas, parameters, responses, and examples.

In code generation

Component schemas describe the request and response bodies of operations, serving as the basis for generating SDK types. Using components prevents issues where multiple types are defined for the same data structure.

openapi.yaml

openapi: 3.1.0
info:
title: The Speakeasy Bar
version: 1.0.0
servers:
- url: https://speakeasy.bar
description: The production server
- url: https://speakeasy.bar/testing
description: The testing server
security:
- apiKey: []
paths:
/drinks:
get:
tags:
- drinks
operationId: listDrinks
summary: Get a list of drinks
responses:
"200":
description: A list of drinks
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Drink"
"400":
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/BadRequest'
components:
schemas:
Drink:
type: object
title: Drink
properties:
name:
type: string
price:
type: number
description: A response containing a list of drinks.
examples:
exampleResponse:
summary: Example response for a list of drinks
value:
drinks:
- name: "Coffee"
price: 2.5
- name: "Tea"
price: 1.8
BadRequest:
type: object
title: BadRequest
properties:
error:
type: string
message:
type: string
securitySchemes:
apiKey:
type: apiKey
name: Authorization
in: header

Dedicated Error Classes

Create dedicated error classes by defining response objects with specific HTTP status codes, such as 400 Bad Request or 404 Not Found, accompanied by clear descriptions and structured schemas to convey detailed error information. If the name of the error class does not clearly incidate the error type, consider using the x-speakeasy-name-override extension to rename it.

openapi.yaml

openapi: 3.1.0
info:
title: The Speakeasy Bar
version: 1.0.0
servers:
- url: https://speakeasy.bar
description: The production server
- url: https://speakeasy.bar/testing
description: The testing server
security:
- apiKey: []
paths:
/drinks:
get:
tags:
- drinks
operationId: listDrinks
summary: Get a list of drinks
responses:
"200":
description: A list of drinks
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Drink"
"400":
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/BadRequest'
components:
schemas:
Drink:
type: object
title: Drink
properties:
name:
type: string
price:
type: number
description: A response containing a list of drinks.
examples:
exampleResponse:
summary: Example response for a list of drinks
value:
drinks:
- name: "Coffee"
price: 2.5
- name: "Tea"
price: 1.8
BadRequest:
type: object
title: BadRequest
properties:
error:
type: string
message:
type: string
securitySchemes:
apiKey:
type: apiKey
name: Authorization
in: header

title

The title property provides a human-readable title for each schema improving the readability of your OpenAPI specification.

openapi.yaml

openapi: 3.1.0
info:
title: The Speakeasy Bar
version: 1.0.0
servers:
- url: https://speakeasy.bar
description: The production server
- url: https://speakeasy.bar/testing
description: The testing server
security:
- apiKey: []
paths:
/drinks:
get:
tags:
- drinks
operationId: listDrinks
summary: Get a list of drinks
responses:
"200":
description: A list of drinks
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Drink"
"400":
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/BadRequest'
components:
schemas:
Drink:
type: object
title: Drink
properties:
name:
type: string
price:
type: number
description: A response containing a list of drinks.
examples:
exampleResponse:
summary: Example response for a list of drinks
value:
drinks:
- name: "Coffee"
price: 2.5
- name: "Tea"
price: 1.8
BadRequest:
type: object
title: BadRequest
properties:
error:
type: string
message:
type: string
securitySchemes:
apiKey:
type: apiKey
name: Authorization
in: header

Description

Use the description field to provide clear and concise information about the purpose, behavior, and expected usage of API elements.

openapi.yaml

openapi: 3.1.0
info:
title: The Speakeasy Bar
version: 1.0.0
servers:
- url: https://speakeasy.bar
description: The production server
- url: https://speakeasy.bar/testing
description: The testing server
security:
- apiKey: []
paths:
/drinks:
get:
tags:
- drinks
operationId: listDrinks
summary: Get a list of drinks
responses:
"200":
description: A list of drinks
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Drink"
"400":
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/BadRequest'
components:
schemas:
Drink:
type: object
title: Drink
properties:
name:
type: string
price:
type: number
description: A response containing a list of drinks.
examples:
exampleResponse:
summary: Example response for a list of drinks
value:
drinks:
- name: "Coffee"
price: 2.5
- name: "Tea"
price: 1.8
BadRequest:
type: object
title: BadRequest
properties:
error:
type: string
message:
type: string
securitySchemes:
apiKey:
type: apiKey
name: Authorization
in: header

Examples

Adding Examples improve the usability of your OpenAPI specification by providing examples that illustrate expected request and response structures.

openapi.yaml

openapi: 3.1.0
info:
title: The Speakeasy Bar
version: 1.0.0
servers:
- url: https://speakeasy.bar
description: The production server
- url: https://speakeasy.bar/testing
description: The testing server
security:
- apiKey: []
paths:
/drinks:
get:
tags:
- drinks
operationId: listDrinks
summary: Get a list of drinks
responses:
"200":
description: A list of drinks
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Drink"
"400":
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/BadRequest'
components:
schemas:
Drink:
type: object
title: Drink
properties:
name:
type: string
price:
type: number
description: A response containing a list of drinks.
examples:
exampleResponse:
summary: Example response for a list of drinks
value:
drinks:
- name: "Coffee"
price: 2.5
- name: "Tea"
price: 1.8
BadRequest:
type: object
title: BadRequest
properties:
error:
type: string
message:
type: string
securitySchemes:
apiKey:
type: apiKey
name: Authorization
in: header

servers

Add multiple servers to define different environments or versions. This is especially useful for separating production and testing environments.

tags

The tags property contains an optional list values used to group or categorize a set of operations. We strongly recommend that you always define tags for operations but it's not required.

In code generation

Tags are used to namespace methods in the SDK. For example, if you have a tag called drinks, then all the methods for that tag will be namespaced under drinks.listDrinks(). You can create multi-level namespaces by using a . in the tag name, for example, menu.drinks will become menu.drinks.listDrinks().

operationId

The operationId value is a unique identifier for the operation and must be unique in the document and is case sensitive. We strongly recommend that you always define an operationId, but it's not required.

In code generation

The operationId value is used to create the name of the method that will be generated for the operation. We recommend you follow a consistent pattern for naming your operations, for example, listDrinks, createDrink, updateDrink, and deleteDrink.

If you are generating your spec from an API framework, ensure that operationId values are human-readable. Some frameworks, like FastAPI, create long operationId identifiers that result in method names that are not idiomatic.

$ref

The $ref keyword in OpenAPI references components defined in the Components Object, which are commonly used for reusable elements like schemas, parameters, responses, and examples.

In code generation

Component schemas describe the request and response bodies of operations, serving as the basis for generating SDK types. Using components prevents issues where multiple types are defined for the same data structure.

Dedicated Error Classes

Create dedicated error classes by defining response objects with specific HTTP status codes, such as 400 Bad Request or 404 Not Found, accompanied by clear descriptions and structured schemas to convey detailed error information. If the name of the error class does not clearly incidate the error type, consider using the x-speakeasy-name-override extension to rename it.

title

The title property provides a human-readable title for each schema improving the readability of your OpenAPI specification.

Description

Use the description field to provide clear and concise information about the purpose, behavior, and expected usage of API elements.

Examples

Adding Examples improve the usability of your OpenAPI specification by providing examples that illustrate expected request and response structures.

openapi.yaml

openapi: 3.1.0
info:
title: The Speakeasy Bar
version: 1.0.0
servers:
- url: https://speakeasy.bar
description: The production server
- url: https://speakeasy.bar/testing
description: The testing server
security:
- apiKey: []
paths:
/drinks:
get:
tags:
- drinks
operationId: listDrinks
summary: Get a list of drinks
responses:
"200":
description: A list of drinks
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Drink"
"400":
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/BadRequest'
components:
schemas:
Drink:
type: object
title: Drink
properties:
name:
type: string
price:
type: number
description: A response containing a list of drinks.
examples:
exampleResponse:
summary: Example response for a list of drinks
value:
drinks:
- name: "Coffee"
price: 2.5
- name: "Tea"
price: 1.8
BadRequest:
type: object
title: BadRequest
properties:
error:
type: string
message:
type: string
securitySchemes:
apiKey:
type: apiKey
name: Authorization
in: header

Extending OpenAPI

The OpenAPI Specification does not have an exhaustive vocabulary for describing API functionality. To overcome gaps in the specification, you can add several extension fields to an OpenAPI document that describe additional metadata and functionality.

Extensions typically follow a naming format of x-<vendor>-<function>, where <vendor> is the name of the vendor or tool that created the extension and <function> is the goal accomplished by the extension.

A range of Speakeasy extensions are available to help you prepare a spec for code generation. Some of the most commonly used extensions are described below.

x-speakeasy-name-override

Use this extension to override the name of a class, operation, or parameter. The most common use case is to override operationId values in your spec to simplify the created SDK method names.

If your operationId identifiers follow a consistent pattern, you can define the name override globally using a regular expression to match the operationId and replace it with a new name.

In this instance, the SDK will contain a method menu.drinks.list() rather than the longer menu.drinks.list_drinks_v2_get().

openapi.yaml

openapi: 3.1.0
info:
title: The Speakeasy Bar
version: 1.0.0
servers:
- url: https://speakeasy.bar
description: The production server
security:
- apiKey: []
x-speakeasy-name-override:
- operationId: ^list_.*
methodNameOverride: list
paths:
/drinks:
get:
tags:
- menu
x-speakeasy-group: menu.drinks
operationId: list_drinks_v2_get
x-speakeasy-usage-example: true
summary: Get a list of drinks
responses:
"200":
description: A list of drinks
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Drink"
components:
schemas:
Drink:
type: object
properties:
name:
type: string
price:
type: number
securitySchemes:
apiKey:
type: apiKey
name: Authorization
in: header
x-speakeasy-example: "<YOUR_API_KEY>"

x-speakeasy-group

Sometimes the tags in an OpenAPI spec may already be used for an unrelated purpose, for example, autogenerating labels in documentation. In this scenario, you may want to use something other than tags to organize and group your methods.

You can add the x-speakeasy-group field to any operation in your OpenAPI spec to define custom namespaces and override any tags associated with the method.

In this case, the listDrinks operation is added to a menu.drinks namespace rather than a menu namespace.

openapi.yaml

openapi: 3.1.0
info:
title: The Speakeasy Bar
version: 1.0.0
servers:
- url: https://speakeasy.bar
description: The production server
security:
- apiKey: []
x-speakeasy-name-override:
- operationId: ^list_.*
methodNameOverride: list
paths:
/drinks:
get:
tags:
- menu
x-speakeasy-group: menu.drinks
operationId: list_drinks_v2_get
x-speakeasy-usage-example: true
summary: Get a list of drinks
responses:
"200":
description: A list of drinks
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Drink"
components:
schemas:
Drink:
type: object
properties:
name:
type: string
price:
type: number
securitySchemes:
apiKey:
type: apiKey
name: Authorization
in: header
x-speakeasy-example: "<YOUR_API_KEY>"

x-speakeasy-usage-example

Documentation is an important part of any SDK. This extension allows you to choose which operation is featured at the top of your README.md.

We recommend that you pick the API operation that your users frequently use. At a Speakeasy, that would likely be getting the list of drinks on offer.

openapi.yaml

openapi: 3.1.0
info:
title: The Speakeasy Bar
version: 1.0.0
servers:
- url: https://speakeasy.bar
description: The production server
security:
- apiKey: []
x-speakeasy-name-override:
- operationId: ^list_.*
methodNameOverride: list
paths:
/drinks:
get:
tags:
- menu
x-speakeasy-group: menu.drinks
operationId: list_drinks_v2_get
x-speakeasy-usage-example: true
summary: Get a list of drinks
responses:
"200":
description: A list of drinks
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Drink"
components:
schemas:
Drink:
type: object
properties:
name:
type: string
price:
type: number
securitySchemes:
apiKey:
type: apiKey
name: Authorization
in: header
x-speakeasy-example: "<YOUR_API_KEY>"

x-speakeasy-example

Another useful documentation extension is x-speakeasy-example, which allows you to provide an example value to be used in the Authentication section of your SDK README.md. This example signals to users that they should instantiate the SDK with their security token.

openapi.yaml

openapi: 3.1.0
info:
title: The Speakeasy Bar
version: 1.0.0
servers:
- url: https://speakeasy.bar
description: The production server
security:
- apiKey: []
x-speakeasy-name-override:
- operationId: ^list_.*
methodNameOverride: list
paths:
/drinks:
get:
tags:
- menu
x-speakeasy-group: menu.drinks
operationId: list_drinks_v2_get
x-speakeasy-usage-example: true
summary: Get a list of drinks
responses:
"200":
description: A list of drinks
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Drink"
components:
schemas:
Drink:
type: object
properties:
name:
type: string
price:
type: number
securitySchemes:
apiKey:
type: apiKey
name: Authorization
in: header
x-speakeasy-example: "<YOUR_API_KEY>"

x-speakeasy-name-override

Use this extension to override the name of a class, operation, or parameter. The most common use case is to override operationId values in your spec to simplify the created SDK method names.

If your operationId identifiers follow a consistent pattern, you can define the name override globally using a regular expression to match the operationId and replace it with a new name.

In this instance, the SDK will contain a method menu.drinks.list() rather than the longer menu.drinks.list_drinks_v2_get().

x-speakeasy-group

Sometimes the tags in an OpenAPI spec may already be used for an unrelated purpose, for example, autogenerating labels in documentation. In this scenario, you may want to use something other than tags to organize and group your methods.

You can add the x-speakeasy-group field to any operation in your OpenAPI spec to define custom namespaces and override any tags associated with the method.

In this case, the listDrinks operation is added to a menu.drinks namespace rather than a menu namespace.

x-speakeasy-usage-example

Documentation is an important part of any SDK. This extension allows you to choose which operation is featured at the top of your README.md.

We recommend that you pick the API operation that your users frequently use. At a Speakeasy, that would likely be getting the list of drinks on offer.

x-speakeasy-example

Another useful documentation extension is x-speakeasy-example, which allows you to provide an example value to be used in the Authentication section of your SDK README.md. This example signals to users that they should instantiate the SDK with their security token.

openapi.yaml

openapi: 3.1.0
info:
title: The Speakeasy Bar
version: 1.0.0
servers:
- url: https://speakeasy.bar
description: The production server
security:
- apiKey: []
x-speakeasy-name-override:
- operationId: ^list_.*
methodNameOverride: list
paths:
/drinks:
get:
tags:
- menu
x-speakeasy-group: menu.drinks
operationId: list_drinks_v2_get
x-speakeasy-usage-example: true
summary: Get a list of drinks
responses:
"200":
description: A list of drinks
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Drink"
components:
schemas:
Drink:
type: object
properties:
name:
type: string
price:
type: number
securitySchemes:
apiKey:
type: apiKey
name: Authorization
in: header
x-speakeasy-example: "<YOUR_API_KEY>"