Skip to Content

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:

main.tsp
import "./books.tsp"; // Import a file import "./books"; // Import main.tsp in a folder import "/books"; // Import a TypeSpec module's main.tsp file

Modules 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:

main.tsp
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:

main.tsp
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:

openapi.yaml
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: integer

Operations 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:

main.tsp
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 parameter

Interfaces 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:

main.tsp
@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:

openapi.yaml
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:

main.tsp
@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:

Terminal
npm install -g @typespec/compiler

Step 2: Create a TypeSpec Project

Create a new directory for the TypeSpec project and navigate into it:

Terminal
mkdir typespec-example cd typespec-example

Run the following command to initialize a new TypeSpec project:

Terminal
tsp init

This 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;
  • @service marks this namespace as a service and provides its title
  • @info provides additional information for the OpenAPI document
  • @server defines the base URL for the API
  • @tagMetadata provides 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 - de

Using 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.0

This 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.

Terminal
npx @scalar/cli document serve tsp-output/schema/openapi.yaml

Understanding 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.0

This 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: Production

Paths 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: uri

Components 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: 10

Step 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: true

Or, 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:

Terminal
speakeasy --version

Then, generate a TypeScript SDK using the following command:

Terminal
speakeasy quickstart

This command will:

  1. Detect the OpenAPI document
  2. Generate a TypeScript SDK in the sdks/train-travel-ts directory (or chosen directory)
  3. 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 run

The 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