← All articles
API DEVELOPMENT TypeSpec: API Design Before Code 2026-03-04 · 3 min read · typespec · openapi · api design

TypeSpec: API Design Before Code

API Development 2026-03-04 · 3 min read typespec openapi api design code generation microsoft rest api protobuf

Most teams design APIs one of two ways: write code first and extract OpenAPI from annotations, or write OpenAPI YAML directly and generate types from it. Both have the same problem — you end up maintaining two things (schema + code) that need to stay in sync.

TypeSpec is Microsoft's answer: a dedicated language for expressing API shapes, from which you generate OpenAPI, JSON Schema, Protobuf, client SDKs, and server scaffolding. One source of truth, multiple outputs.

Why a Dedicated API Language

OpenAPI YAML gets verbose fast. A simple CRUD endpoint with proper schemas, error responses, and pagination can run to hundreds of lines. Maintaining it by hand is error-prone; generating it from code annotations couples your API contract to your implementation choices.

TypeSpec treats API design as a first-class programming task:

import "@typespec/http";
using TypeSpec.Http;

model User {
  id: string;
  email: string;
  name: string;
  createdAt: utcDateTime;
}

@route("/users")
interface Users {
  @get list(): User[];
  @post create(@body user: OmitProperties<User, "id" | "createdAt">): User;
  @get read(@path id: string): User | NotFoundResponse;
  @patch update(@path id: string, @body patch: UpdateableProperties<User>): User;
  @delete remove(@path id: string): void;
}

This generates a complete OpenAPI 3.0 spec, including proper response schemas and HTTP status codes.

Installation

npm install -g @typespec/compiler

# Initialize a new TypeSpec project
mkdir my-api && cd my-api
tsp init --template rest

# Or add to existing project
npm install --save-dev @typespec/compiler @typespec/http @typespec/rest @typespec/openapi3

Project structure:

my-api/
├── main.tsp         # Main TypeSpec file
├── tspconfig.yaml   # Compiler configuration
└── package.json

Basic TypeSpec Syntax

Models (Schemas)

// Simple model
model Product {
  id: string;
  name: string;
  price: float64;
  category: string;
  inStock: boolean;
}

// Extend a model
model DigitalProduct extends Product {
  downloadUrl: string;
  fileSize: int64;
}

// Template models
model Page<T> {
  items: T[];
  total: int64;
  page: int32;
  pageSize: int32;
}

// Utility types
model CreateProductRequest is OmitProperties<Product, "id">;
model UpdateProductRequest is UpdateableProperties<Product>;

Enums and Unions

enum OrderStatus {
  Pending: "pending",
  Processing: "processing",
  Shipped: "shipped",
  Delivered: "delivered",
  Cancelled: "cancelled",
}

// Union types
alias StringOrNumber = string | int32;
alias Response<T> = T | NotFoundResponse | UnauthorizedResponse;

Decorators

TypeSpec uses decorators to add HTTP semantics, validation, and documentation:

model User {
  @format("email")
  email: string;

  @minLength(8)
  @maxLength(72)
  password: string;

  @minValue(0)
  @maxValue(120)
  age?: int32;

  @doc("ISO 3166-1 alpha-2 country code")
  country?: string;
}

HTTP Operations

import "@typespec/http";
using TypeSpec.Http;

@service({
  title: "Product API",
  version: "1.0.0",
})
@server("https://api.example.com", "Production")
namespace ProductAPI;

@route("/products")
@tag("Products")
interface ProductOperations {
  @doc("List all products with optional filtering")
  @get
  list(
    @query category?: string,
    @query page?: int32 = 1,
    @query pageSize?: int32 = 20,
  ): Page<Product>;

  @doc("Create a new product")
  @post
  create(@body product: CreateProductRequest): {
    @statusCode statusCode: 201;
    @body product: Product;
  };

  @doc("Get a specific product")
  @get
  read(@path id: string): Product | NotFoundResponse;
}

Authentication

import "@typespec/http";
using TypeSpec.Http;

// Define security scheme
@useAuth(BearerAuth)
namespace SecureAPI;

// Or per-operation
@useAuth(BearerAuth | ApiKeyAuth<ApiKeyLocation.header, "X-API-Key">)
interface AdminOperations {
  @get
  dashboard(): DashboardData;
}

Generating Output

# Compile to OpenAPI 3.0
tsp compile main.tsp

# Output goes to tsp-output/ by default
ls tsp-output/@typespec/openapi3/
# openapi.yaml

Configure output in tspconfig.yaml:

emit:
  - "@typespec/openapi3"
  - "@typespec/json-schema"

options:
  "@typespec/openapi3":
    output-file: "openapi.yaml"
    openapi-versions:
      - "3.0.0"
  "@typespec/json-schema":
    output-dir: "json-schemas"

Generating Clients

With the generated OpenAPI spec, use any OpenAPI client generator:

# TypeScript client with openapi-typescript
npx openapi-typescript tsp-output/@typespec/openapi3/openapi.yaml -o src/api.ts

# Python client
openapi-generator-cli generate \
  -i tsp-output/@typespec/openapi3/openapi.yaml \
  -g python \
  -o clients/python

# Go client
openapi-generator-cli generate \
  -i tsp-output/@typespec/openapi3/openapi.yaml \
  -g go \
  -o clients/go

CI Integration

Check that generated specs don't drift from the source:

# .github/workflows/api-check.yml
name: API Spec Check
on: [push, pull_request]

jobs:
  check-spec:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20

      - run: npm ci
      - run: npx tsp compile main.tsp

      - name: Check for spec drift
        run: |
          if ! git diff --quiet tsp-output/; then
            echo "Generated spec has changed. Run 'tsp compile' and commit."
            git diff tsp-output/
            exit 1
          fi

TypeSpec vs. Alternatives

Tool Approach Output
TypeSpec Design-first language OpenAPI, Protobuf, JSON Schema
OpenAPI YAML Write spec directly Docs, clients
tRPC Code-first (TypeScript) TypeScript clients only
Protobuf IDL for binary protocols gRPC, binary
Zod + openapi-zod-client Code-first validation OpenAPI from Zod schemas

TypeSpec fills the gap between "write OpenAPI YAML" (verbose, no reuse) and "code-first with annotations" (couples spec to implementation). It's most valuable for teams with multiple consumers of an API, or where the contract needs to exist independently of any implementation.


Subscribe to DevTools Guide for more developer tool deep dives.