How to create OpenAPI and SDKs with TypeSpec
TypeSpec is a brand-new domain-specific language (DSL) used to design APIs and generate API artifacts such as documentation, client SDKs, and server stubs.
Some consider TypeSpec to be a replacement for OpenAPI, but the goal of TypeSpec is to be used earlier in the planning process. OpenAPI can be used to design an API that does not yet exist (the API design-first workflow), or describe an API that already exists (API code-first workflow). TypeSpec focuses on the design-first workflow, providing a lightweight language for rapidly designing APIs in a TypeScript-like way, which can then be used to generate OpenAPI documents and other handy artifacts from a single source of truth.
TypeSpec has high level language constructs such as model for the structure or schema of an API’s data, or op for operations in an API.
import "@typespec/http";
using Http;
model Store {
name: string;
address: Address;
}
model Address {
street: string;
city: string;
}
@route("/stores")
interface Stores {
list(@query filter: string): Store[];
read(@path id: string): Store;
}For those familiar with OpenAPI, these concepts translate to schema and operation, respectively.
openapi: 3.2.0
info:
title: (title)
version: 0.0.0
tags: []
paths:
/stores:
get:
operationId: Stores_list
parameters:
- name: filter
in: query
required: true
schema:
type: string
responses:
"200":
description: The request has succeeded.
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Store"
/stores/{id}:
get:
operationId: Stores_read
parameters:
- name: id
in: path
required: true
schema:
$ref: "#/components/schemas/Store"
responses:
"200":
description: The request has succeeded.
content:
application/json:
schema:
$ref: "#/components/schemas/Store"
components:
schemas:
Address:
type: object
required:
- street
- city
properties:
street:
type: string
city:
type: string
Store:
type: object
required:
- name
- address
properties:
name:
type: string
address:
$ref: "#/components/schemas/Address"The end goal for this guide is to create a high-quality TypeScript SDK. However, before creating an SDK, this guide covers how to generate an OpenAPI document based on a TypeSpec document. This requires understanding TypeSpec, and there is no better way to get started learning a new language than by asking why it exists in the first place.
The Problem TypeSpec Solves
Code generation is a force multiplier in API design and development. When an executive unironically asks, “How do we 10x API creation?”, the unironic answer is: “API design-first + Code generation.”
API design-first means specifying exactly what an application’s programming interface will look like before anything gets built, getting stakeholders to approve these designs and mock servers before diving into coding. Code generation means using those approved designs to generate server stubs and client libraries (SDKs), rapidly reducing the amount of code that needs to be manually written and cutting down on human error in the process.
As mentioned previously, OpenAPI is widely used for exactly this reason - it provides a human-readable (as YAML) description format for APIs, and comes with a thriving ecosystem of tools and code generators. So if OpenAPI exists, what can TypeSpec add?
The fundamental problem TypeSpec aims to solve is that writing OpenAPI documents by hand is complex, tedious, and error-prone. The complexity often leads to teams to abandon an design-first approach and instead start by coding their APIs without any of the usual review and feedback processes early on that can advert disaster.
OpenAPI documents can become large and unwieldy, making them difficult to read and maintain. The YAML syntax can be verbose and repetitive, leading to duplication and inconsistencies. This confusion is because OpenAPI can be used to both design an API that does not yet exist, and describe an API that already exists, but TypeSpec focuses on the former: TypeSpec helps teams rapidly design APIs with a simple TypeScript language that’s a lot more friendly on the fingertips, without relying on GUI editors to wrestle OpenAPI’s YAML for you.
The workflow is writing TypeSpec early on in the planning process, rapidly prototyping APIs, and evolving them over time. All the while, CI/CD can convert TypeSpec to OpenAPI documents, powering API documentation, SDK generation, mock servers, and other handy artifacts from a tidy single source of truth.
Perhaps TypeSpec continues to be the source of truth forever, or perhaps it’s ditched after the initial design phase. Regardless of the approach, TypeSpec aims to make API design-first easier, faster, and more enjoyable for those familiar with TypeScript and similar languages.
A Brief Introduction to TypeSpec Syntax
To get started with TypeSpec, this section covers the basic syntax and concepts needed to read and write TypeSpec specifications.
Modularity in TypeSpec
The main entry point in TypeSpec is the main.tsp file. This file has the same role as the index.ts file in a TypeScript project.
Just like in TypeScript, code can be organized into files, folders, and modules, then imported using the import statement. This helps split large API designs into smaller, more manageable parts. The difference between TypeScript and TypeSpec in this regard is that TypeSpec imports files, not code.
Here’s an example of how to import files, folders, and modules in TypeSpec:
import "./books.tsp"; // Import a file
import "./books"; // Import main.tsp in a folder
import "/books"; // Import a TypeSpec module's main.tsp fileModules can be installed using npm, and the import statement imports them into a TypeSpec project.
Namespaces , another TypeScript feature that TypeSpec borrows, allow grouping types and avoiding naming conflicts. This is especially useful when importing multiple files that define types with the same name. Just like with TypeScript, namespaces may be nested and span multiple files.
Namespaces are defined using the namespace keyword, followed by the namespace name and a block of type definitions. Here’s an example:
namespace MyNamespace {
model User {
id: string;
name: string;
}
}They may also be defined at the file level, using the namespace keyword followed by the namespace name and a block of type definitions. Here’s an example:
namespace MyNamespace;
model User {
id: string;
name: string;
}
model Post {
id: string;
title: string;
content: string;
}Models in TypeSpec
Models in TypeSpec are similar to OpenAPI’s schema objects. They define the structure of the data that will be sent and received by an API. Models are defined using the model keyword, followed by the model name and a block of properties. Here’s an example:
model User {
id: string;
name: string;
email: string;
}Models are composable and extensible. Models can reference other models within a definition, extend a model with additional properties, and compose multiple models into a single model. Here’s an example of model composition:
namespace WithComposition {
model User {
id: string;
name: string;
email: string;
}
model HasRole {
role: string;
}
model Admin is User { // Copies the properties and decorators from User
...HasRole; // Extends the User model with the properties from the HasRole model
level: number; // Adds a new property to the Admin model
}
}
// The Admin model above will have the following properties:
namespace WithoutComposition {
model Admin {
id: string;
name: string;
email: string;
role: string;
level: number;
}
}The equivalent OpenAPI document for the User model above would look like this:
components:
schemas:
User:
type: object
properties:
id:
type: string
name:
type: string
email:
type: string
HasRole:
type: object
properties:
role:
type: string
Admin:
allOf:
- $ref: "#/components/schemas/User"
- $ref: "#/components/schemas/HasRole"
- type: object
properties:
level:
type: integerOperations in TypeSpec
Operations in TypeSpec are similar to OpenAPI operations. They describe the methods that users can call in an API. Operations are defined using the op keyword, followed by the operation name. Here’s an example:
op list(): User[]; // Defaults to GET
op read(id: string): User; // Defaults to GET
op create(@body user: User): User; // Defaults to POST with a body parameterInterfaces in TypeSpec
Interfaces in TypeSpec group related operations together, similar to OpenAPI’s paths object. Interfaces are defined using the interface keyword, followed by the interface name and a block of operations. Here’s an example:
@route("/users")
interface Users {
op list(): User[]; // Defaults to GET /users
op read(id: string): User; // Defaults to GET /users/{id}
op create(@body user: User): User; // Defaults to POST /users
}The equivalent OpenAPI for the Users interface above would look like this:
paths:
/users:
get:
operationId: listUsers
responses:
200:
description: OK
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/User"
post:
operationId: createUser
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/User"
responses:
200:
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/User"
/users/{id}:
get:
operationId: getUser
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
200:
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/User"Decorators in TypeSpec
Decorators in TypeSpec add metadata to models, operations, and interfaces. They start with the @ symbol followed by the decorator name. Here’s an example of the @doc decorator:
@doc("A user in the system")
model User {
@doc("The unique identifier of the user")
id: string;
@doc("The name of the user")
name: string;
@doc("The email address of the user")
email: string;
}Decorators allow adding custom behavior to TypeSpec definitions using JavaScript functions. Developers can define their own decorators or use built-in decorators provided by TypeSpec or third-party libraries.
Learn More About TypeSpec
The language features above should be enough to navigate a TypeSpec specification.
Those interested in learning more about the TypeSpec language can refer to the official documentation .
More detailed examples of TypeSpec syntax are covered in the full example below.
Generating an OpenAPI Document from TypeSpec
With a basic understanding of TypeSpec syntax, the next step is generating OpenAPI from TypeSpec.
All of the code examples in this section can be found in the Speakeasy examples repo under frameworks-typespec/.
Step 1: Install the TypeSpec Compiler CLI
Install tsp globally using npm:
npm install -g @typespec/compilerStep 2: Create a TypeSpec Project
Create a new directory for the TypeSpec project and navigate into it:
mkdir typespec-example
cd typespec-exampleRun the following command to initialize a new TypeSpec project:
tsp initThis will prompt for a template selection. Choose the Generic REST API template and press enter.
✔ Select a project template: Generic REST API
✔ Enter a project name: typespec-example-speakeasy
? What emitters do you want to use?:
❯ ◉ OpenAPI 3.2 document [@typespec/openapi3]
◯ C# client [@typespec/http-client-csharp]
◯ Java client [@typespec/http-client-java]
◯ JavaScript client [@typespec/http-client-js]
◯ Python client [@typespec/http-client-python]
◯ C# server stubs [@typespec/http-server-csharp]
◯ JavaScript server stubs [@typespec/http-server-js]Depending on the selected emitters, tsp init will install the necessary dependencies and create a main.tsp file in the project directory.
Step 3: Write the TypeSpec Specification
Open the main.tsp file in a text editor and write the TypeSpec specification. Here’s an example of a simple TypeSpec document:
Example TypeSpec File
Here’s an example of a complete TypeSpec file for a Train Travel API:
import "@typespec/http";
import "@typespec/openapi";
import "@typespec/openapi3";
using Http;
using OpenAPI;
/**
* API for finding and booking train trips across Europe.
*
*/
@service(#{ title: "Train Travel API" })
@info(#{
version: "1.2.1",
contact: #{
name: "Train Support",
url: "https://example.com/support",
email: "support@example.com",
},
license: #{
name: "Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International",
},
})
@server("https://api.example.com", "Production")
@tagMetadata(
"Stations",
#{
description: "Find and filter train stations across Europe, including their location and local timezone.",
}
)
@tagMetadata(
"Trips",
#{
description: "Timetables and routes for train trips between stations, including pricing and availability.",
}
)
@tagMetadata(
"Bookings",
#{
description: "Create and manage bookings for train trips, including passenger details and optional extras.",
}
)
@tagMetadata(
"Payments",
#{
description: "Pay for bookings using a card or bank account, and view payment status and history. **Warning:** Bookings usually expire within 1 hour so you'll need to make your payment before the expiry date.",
}
)
namespace TrainTravelAPI;
/** A train station. */
model Station {
/** Unique identifier for the station. */
@format("uuid") id: string;
/** The name of the station */
name: string;
/** The address of the station. */
address: string;
/** The country code of the station. */
@format("iso-country-code") country_code: string;
/** The timezone of the station in the [IANA Time Zone Database format](https://www.iana.org/time-zones). */
timezone?: string;
}
/** A train trip. */
model Trip {
/** Unique identifier for the trip */
@format("uuid") id?: string;
/** The starting station of the trip */
origin?: string;
/** The destination station of the trip */
destination?: string;
/** The date and time when the trip departs */
departure_time?: utcDateTime;
/** The date and time when the trip arrives */
arrival_time?: utcDateTime;
/** The name of the operator of the trip */
operator?: string;
/** The cost of the trip */
price?: numeric;
/** Indicates whether bicycles are allowed on the trip */
bicycles_allowed?: boolean;
/** Indicates whether dogs are allowed on the trip */
dogs_allowed?: boolean;
}
/** A booking for a train trip. */
model Booking {
/** Unique identifier for the booking */
@format("uuid") id?: string;
/** Identifier of the booked trip */
@format("uuid") trip_id?: string;
/** Name of the passenger */
passenger_name?: string;
/** Indicates whether the passenger has a bicycle. */
has_bicycle?: boolean;
/** Indicates whether the passenger has a dog. */
has_dog?: boolean;
}
/** A problem detail object as defined in RFC 7807. */
model Problem {
/** A URI reference that identifies the problem type */
type?: string;
/** A short, human-readable summary of the problem type */
title?: string;
/** A human-readable explanation specific to this occurrence of the problem */
detail?: string;
/** A URI reference that identifies the specific occurrence of the problem */
instance?: string;
/** The HTTP status code */
status?: integer;
}
/** Returns a paginated and searchable list of all train stations. */
@tag("Stations")
@route("/stations")
@get
@summary("Get a list of train stations")
op `get-stations`(
...Parameters.page,
...Parameters.limit,
/**
* The latitude and longitude of the user's location, to narrow down the search results to sites within a proximity of this location.
*/
@query() coordinates?: string,
/**
* A search term to filter the list of stations by name or address.
*/
@query() search?: string,
/** Filter stations by country code */
@format("iso-country-code") @query() country?: string,
):
| {
@body body: {
data?: Station[];
links?: Links.Self & Links.Pagination;
};
}
| {
@statusCode statusCode: 400;
@header contentType: "application/problem+json";
@body body: Problem;
};
/**
* Returns a list of available train trips between the specified origin and destination stations on the given date.
*
*/
@tag("Trips")
@route("/trips")
@get
@summary("Get available train trips")
op `get-trips`(
...Parameters.page,
...Parameters.limit,
/** The ID of the origin station */
@format("uuid") @query() origin: string,
/** The ID of the destination station */
@format("uuid") @query() destination: string,
/** The date and time of the trip in ISO 8601 format in origin station's timezone. */
@query() date: utcDateTime,
/** Only return trips where bicycles are known to be allowed */
@query() bicycles?: boolean,
/** Only return trips where dogs are known to be allowed */
@query() dogs?: boolean,
):
| {
@body body: {
data?: Trip[];
links?: Links.Self & Links.Pagination;
};
}
| {
@statusCode statusCode: 400;
@header contentType: "application/problem+json";
@body body: Problem;
};
/** A booking is a temporary hold on a trip. It is not confirmed until the payment is processed. */
@tag("Bookings")
@route("/bookings")
@post
@summary("Create a booking")
op `create-booking`(
/** Booking details */
@body body: Booking,
):
| {
@statusCode statusCode: 201;
@body body: Booking & {
links?: { self?: url };
};
}
| {
@statusCode statusCode: 400;
@header contentType: "application/problem+json";
@body body: Problem;
};
namespace Parameters {
model page {
/** The page number to return */
@minValue(1) @query() page?: integer = 1;
}
model limit {
/** The number of items to return per page */
@minValue(1)
@maxValue(100)
@query()
limit?: integer = 10;
}
}Let’s break down some of the key features of this TypeSpec file:
Importing and Using Modules
The file starts by importing necessary TypeSpec modules:
import "@typespec/http";
import "@typespec/openapi";
import "@typespec/openapi3";
using Http;
using OpenAPI;These modules extend TypeSpec’s capabilities for HTTP APIs and OpenAPI generation.
Namespace and Service Definition
The TrainTravelAPI namespace is decorated with several metadata decorators:
@service(#{ title: "Train Travel API" })
@info(#{
version: "1.2.1",
contact: #{
name: "Train Support",
url: "https://example.com/support",
email: "support@example.com",
},
license: #{
name: "Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International",
},
})
@server("https://api.example.com", "Production")
namespace TrainTravelAPI;@servicemarks this namespace as a service and provides its title@infoprovides additional information for the OpenAPI document@serverdefines the base URL for the API@tagMetadataprovides descriptions for operation tags
Models with Validation
TypeSpec supports various validation decorators for model properties:
/** A train station. */
model Station {
/** Unique identifier for the station. */
@format("uuid")
id: string;
/** The name of the station */
name: string;
/** The address of the station. */
address: string;
/** The country code of the station. */
@format("iso-country-code") country_code: string;
/** The timezone of the station */
timezone?: string;
}The @format decorator adds format annotations (which some OpenAPI tools may choose to validate), while the ? makes properties optional.
Operations with Multiple Response Types
Operations can return different response types using union types:
op `get-stations`(...params):
| {
@body body: {
data?: Station[];
links?: { /* ... */ };
};
}
| {
@statusCode statusCode: 400;
@body body: Problem;
};This creates both successful (200) and error (400) responses in the generated OpenAPI.
Examples
Properties within models can have examples added with the @example decorator. These will be used to show an example for that particular property in most documentation tools, and can be collected to make larger examples for whole requests and responses.
model BankAccount {
@example("fr")
@example("de")
country?: string
}BankAccount:
country:
type: string
examples:
- fr
- deUsing multiple examples like this requires OpenAPI v3.1 or later.
Reusable Parameters
The Parameters namespace defines reusable parameter models:
namespace Parameters {
model page {
/** The page number to return */
@minValue(1) @query() page?: integer = 1;
}
model limit {
/** The number of items to return per page */
@minValue(1)
@maxValue(100)
@query()
limit?: integer = 10;
}
}These can be spread into operations using ...Parameters.page and ...Parameters.limit.
Polymorphism
TypeSpec supports polymorphism through unions with the @oneOf decorator, which generates unions in OpenAPI.
Here’s an example from the Train Travel API showing how to model payment sources that can be either a card or a bank account:
/** Card payment source details. */
model CardPaymentSource {
object?: "card";
name: string;
number: string;
cvc: string;
exp_month: int64;
exp_year: int64;
address_line1?: string;
address_line2?: string;
address_city?: string;
address_country: string;
address_post_code?: string;
}
/** Bank account payment source details. */
model BankAccountPaymentSource {
object?: "bank_account";
name: string;
number: string;
sort_code?: string;
account_type: "individual" | "company";
bank_name: string;
country: string;
}
/** A payment source used for booking payments. Can be either a card or a bank account. */
@oneOf
union BookingPaymentSource {
CardPaymentSource,
BankAccountPaymentSource,
}
/** A payment for a booking. */
model BookingPayment {
id?: string;
amount?: numeric;
source?: BookingPaymentSource;
status?: "pending" | "succeeded" | "failed";
}The @oneOf decorator on the union tells TypeSpec to generate an OpenAPI oneOf schema, which means the value must match exactly one of the union members. The generated OpenAPI looks like this:
components:
schemas:
CardPaymentSource:
type: object
required:
- name
- number
- cvc
- exp_month
- exp_year
- address_country
properties:
object:
type: string
enum:
- card
name:
type: string
number:
type: string
cvc:
type: string
minLength: 3
maxLength: 4
exp_month:
type: integer
format: int64
exp_year:
type: integer
format: int64
address_line1:
type: string
address_line2:
type: string
address_city:
type: string
address_country:
type: string
address_post_code:
type: string
description: Card payment source details.
BankAccountPaymentSource:
type: object
required:
- name
- number
- account_type
- bank_name
- country
properties:
object:
type: string
enum:
- bank_account
name:
type: string
number:
type: string
sort_code:
type: string
account_type:
type: string
enum:
- individual
- company
bank_name:
type: string
country:
type: string
description: Bank account payment source details.
BookingPaymentSource:
oneOf:
- $ref: "#/components/schemas/CardPaymentSource"
- $ref: "#/components/schemas/BankAccountPaymentSource"
description: A payment source used for booking payments. Can be either a card or a bank account.
BookingPayment:
type: object
properties:
id:
type: string
amount:
type: number
source:
$ref: "#/components/schemas/BookingPaymentSource"
status:
type: string
enum:
- pending
- succeeded
- failed
description: A payment for a booking.Working with polymorphic types in TypeSpec is straightforward, and the generated OpenAPI document accurately represents the intended structure.
Step 4: Generate the OpenAPI Document
To generate an OpenAPI document using the TypeSpec compiler, TypeSpec must be configured with a tspconfig.yaml file in the project root.
emit:
- "@typespec/openapi3"
options:
"@typespec/openapi3":
emitter-output-dir: "{output-dir}/schema"
openapi-versions:
- 3.1.0
- 3.2.0This will configure TypeSpec to output both the more compatible OpenAPI v3.1, as well as the latest and greatest OpenAPI v3.2. This newer version is supported by TypeSpec and Speakeasy, and if you have older tools that need the more compatible version why not use both.
Now run the TypeSpec compiler in the project root to generate the OpenAPI document:
tsp compile .This will create a document in the ./schema folder (or the folder specified in the emitter-output-dir option).
Step 5: View the Generated OpenAPI Document
Open the generated OpenAPI document in a text editor to view its contents, or reach for a handy OpenAPI documentation tool like our friends at Scalar to visualize and explore the document.
npx @scalar/cli document serve tsp-output/schema/openapi.yamlUnderstanding the generated OpenAPI document
When the TypeSpec compiler processes the TypeSpec document, it generates an OpenAPI document. Here’s what the structure of the generated OpenAPI document looks like:
OpenAPI Version
The document starts with the OpenAPI version:
openapi: 3.2.0This is determined by the @typespec/openapi3 emitter used, which generates OpenAPI 3.x documents.
API Information
The info section contains metadata from our @service and @info decorators:
info:
title: Train Travel API
version: 1.2.1
contact:
name: Train Support
url: https://example.com/support
email: support@example.com
license:
name: Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International
description: API for finding and booking train trips across Europe.Tags
The tags section contains descriptions from our @tagMetadata decorators:
tags:
- name: Stations
description: Find and filter train stations across Europe, including their location and local timezone.
- name: Trips
description: Timetables and routes for train trips between stations, including pricing and availability.
- name: Bookings
description: Create and manage bookings for train trips, including passenger details and optional extras.Server Information
The server URL from our @server decorator:
servers:
- url: https://api.example.com
description: ProductionPaths and Operations
The operations from our TypeSpec file become paths in the OpenAPI document:
paths:
/stations:
get:
operationId: get-stations
summary: Get a list of train stations
description: Returns a paginated and searchable list of all train stations.
tags:
- Stations
parameters:
- $ref: "#/components/parameters/Parameters.page"
- $ref: "#/components/parameters/Parameters.limit"
- name: coordinates
in: query
required: false
description: The latitude and longitude of the user's location
style: form
explode: true
schema:
type: string
responses:
"200":
description: The request has succeeded.
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: "#/components/schemas/Station"
links:
type: object
properties:
self:
type: string
format: uri
next:
type: string
format: uriComponents and Schemas
Our models become schemas in the components section:
components:
schemas:
Station:
type: object
required:
- id
- name
- address
- country_code
properties:
id:
type: string
format: uuid
description: Unique identifier for the station.
name:
type: string
description: The name of the station
address:
type: string
description: The address of the station.
country_code:
type: string
format: iso-country-code
description: The country code of the station.
timezone:
type: string
description: The timezone of the station
description: A train station.Reusable Parameters
Our spread parameter models become reusable parameter components:
components:
parameters:
Parameters.page:
name: page
in: query
required: false
description: The page number to return
style: form
explode: true
schema:
type: integer
minimum: 1
default: 1
Parameters.limit:
name: limit
in: query
required: false
description: The number of items to return per page
style: form
explode: true
schema:
type: integer
minimum: 1
maximum: 100
default: 10Step 7: Use the OpenAPI Document With Speakeasy
With a valid OpenAPI document, Speakeasy can generate SDKs, documentation, and more. Refer to the Speakeasy documentation for more information on how to use the generated OpenAPI document.
Adding OpenAPI Extensions for SDK Generation
TypeSpec allows adding OpenAPI extensions using the @extension decorator. This is particularly useful for adding Speakeasy-specific extensions to customize SDK generation behavior.
Adding Retries with OpenAPI Extensions
To add retry logic to operations, add the Speakeasy x-speakeasy-retries extension to the TypeSpec specification:
@tag("Stations")
@route("/stations")
@get
@summary("Get a list of train stations")
@extension("x-speakeasy-retries", #{
strategy: "backoff",
backoff: #{
initialInterval: 500,
maxInterval: 60000,
maxElapsedTime: 3600000,
exponent: 1.5,
},
statusCodes: ["5XX"],
retryConnectionErrors: true
})
op `get-stations`(
...Parameters.page,
...Parameters.limit,
@query() coordinates?: string,
@query() search?: string,
): /* ... responses ... */;This generates the following extension in the OpenAPI document:
paths:
/stations:
get:
operationId: get-stations
summary: Get a list of train stations
x-speakeasy-retries:
strategy: backoff
backoff:
initialInterval: 500
maxInterval: 60000
maxElapsedTime: 3600000
exponent: 1.5
statusCodes:
- 5XX
retryConnectionErrors: trueOr, the extension can be added at the namespace level:
@extension("x-speakeasy-retries", #{
strategy: "backoff",
backoff: #{
initialInterval: 500,
maxInterval: 60000,
maxElapsedTime: 3600000,
exponent: 1.5,
},
statusCodes: ["5XX"],
retryConnectionErrors: true
})
namespace TrainTravelAPI;Similar extensions can be added for other Speakeasy features like pagination, error handling, and more.
Step 8: Generate an SDK from the OpenAPI Document
Now that a brilliant OpenAPI document exists, Speakeasy can step in and easily generate an SDK in any one of the most popular programming languages around.
First, verify that Speakeasy is installed:
speakeasy --versionThen, generate a TypeScript SDK using the following command:
speakeasy quickstartThis command will:
- Detect the OpenAPI document
- Generate a TypeScript SDK in the
sdks/train-travel-tsdirectory (or chosen directory) - Set up proper retry logic, pagination, and other SDK features based on
x-speakeasy-*extensions
After making changes to the TypeSpec document, both the OpenAPI document and SDK can be regenerated:
# Regenerate OpenAPI document
tsp compile .
# Regenerate SDK
speakeasy runThe speakeasy run command uses the existing Speakeasy configuration to regenerate the SDK with the latest changes.
Limitations of TypeSpec
An earlier version of this guide had a handful of concerns, but TypeSpec has progressed a long way in a short time, and now there is just the one problem we currently have.
No Support for Webhooks or Callbacks
TypeSpec does not yet support webhooks or callbacks , which are common in modern APIs. This means webhook operations or callback URLs cannot be defined in a TypeSpec specification and OpenAPI documents cannot be generated for them.
To work around this limitation, webhooks and callbacks can be defined directly in the OpenAPI document using an overlay , or by adding them to the OpenAPI document manually.
The TypeSpec Playground
To help developers experiment with TypeSpec and see how it translates to OpenAPI, the Microsoft team created a TypeSpec Playground .
We added our TypeSpec specification to the playground. You can view the generated OpenAPI document and SDK, or browse a generated Swagger UI for the API.
Further Reading
This guide barely scratches the surface of what TypeSpec can do. This small language is evolving rapidly, and new features are being added all the time.
Here are some resources to learn more about TypeSpec and how to use it effectively:
- TypeSpec Documentation : The official TypeSpec documentation provides detailed information on the TypeSpec language, standard library, and emitters.
- TypeSpec Releases : Keep up with the latest TypeSpec releases and updates on GitHub.
- TypeSpec Playground : Worth mentioning again: experiment with TypeSpec in the browser, generate OpenAPI documents, and view the resulting Swagger UI.
- Speakeasy Documentation: Speakeasy has extensive documentation on how to generate SDKs from OpenAPI documents, customize SDKs, and more.
- Speakeasy OpenAPI Reference: For a detailed reference on the OpenAPI specification.
Last updated on