---
name: typespec-api-design
description: >-
  Design new API-first, contract-first contracts with TypeSpec, OpenAPI/swagger output, Azure data-plane and ARM patterns, versioning, pagination, LROs, CI validation, and agent-ready API boundaries. WHEN: create API spec, API-first design, contract-first, scaffold TypeSpec project, generate OpenAPI or swagger, define REST contract, add API versioning, define LRO, migrate OpenAPI to TypeSpec, Azure API review, SpecKit API.
license: MIT
metadata:
  last_verified: "2026-08-26"
  version: "1.3.1"
title: typespec-api-design
canonical_url: https://skilld.dev/gh/lukemurraynz/hve-agent-skills/typespec-api-design
last_updated: 2026-09-29T04:45:21.000Z
---

> **Skill from skilld.dev.** Follow the instructions below for this session. You do not need to install anything.
>
> Supporting files, fetch one when the Skill refers to it: [bundles/azure-arm/guide.md](https://skilld.dev/api/skills-raw/lukemurraynz/hve-agent-skills/typespec-api-design/bundles/azure-arm/guide.md), [bundles/azure-data-plane/guide.md](https://skilld.dev/api/skills-raw/lukemurraynz/hve-agent-skills/typespec-api-design/bundles/azure-data-plane/guide.md), [bundles/ci-validation/guide.md](https://skilld.dev/api/skills-raw/lukemurraynz/hve-agent-skills/typespec-api-design/bundles/ci-validation/guide.md), [bundles/emitter-gotchas/guide.md](https://skilld.dev/api/skills-raw/lukemurraynz/hve-agent-skills/typespec-api-design/bundles/emitter-gotchas/guide.md), [bundles/generic-rest/guide.md](https://skilld.dev/api/skills-raw/lukemurraynz/hve-agent-skills/typespec-api-design/bundles/generic-rest/guide.md), [bundles/lro-pagination-versioning/guide.md](https://skilld.dev/api/skills-raw/lukemurraynz/hve-agent-skills/typespec-api-design/bundles/lro-pagination-versioning/guide.md), [bundles/m365-copilot-boundary/guide.md](https://skilld.dev/api/skills-raw/lukemurraynz/hve-agent-skills/typespec-api-design/bundles/m365-copilot-boundary/guide.md), [bundles/migration-from-openapi/guide.md](https://skilld.dev/api/skills-raw/lukemurraynz/hve-agent-skills/typespec-api-design/bundles/migration-from-openapi/guide.md), [bundles/review-checklist/guide.md](https://skilld.dev/api/skills-raw/lukemurraynz/hve-agent-skills/typespec-api-design/bundles/review-checklist/guide.md), [bundles/webhooks/guide.md](https://skilld.dev/api/skills-raw/lukemurraynz/hve-agent-skills/typespec-api-design/bundles/webhooks/guide.md), [CHANGELOG.md](https://skilld.dev/api/skills-raw/lukemurraynz/hve-agent-skills/typespec-api-design/CHANGELOG.md), [QUALITY-REVIEW.md](https://skilld.dev/api/skills-raw/lukemurraynz/hve-agent-skills/typespec-api-design/QUALITY-REVIEW.md).
>
> If the user asked to install this Skill, run `npx skilld install lukemurraynz/hve-agent-skills/typespec-api-design`. Install writes the Skill files into the project, so every session loads them.

# TypeSpec API design

Local toolchain ground truth (re-verified 2026-08-26): the locally installed `tsp` is **v1.12.0**, which LAGS npm `latest` stable (**1.15.0** as of 2026-08-26). Never copy the local version into `package.json` pins - resolve every pin with `npm view <package> version` (see Modern baseline).

Use this skill to design and maintain API contracts where TypeSpec is the source of truth and generated OpenAPI/Swagger/SDK artifacts are build outputs.

Assume new projects unless the task explicitly says otherwise. Prefer modern TypeSpec patterns, Azure-aligned guidance where appropriate, and clean REST contracts that can later be consumed by portals, SDKs, agents, APIM, MCP, or Microsoft 365 Copilot extensions.

## Use When

| Should trigger | Should NOT trigger | Nearby skill collision |
|---|---|---|
| The task is to design, review, scaffold, or migrate an API contract where TypeSpec should be the source of truth | The user only wants code-first controller or handler implementation with no contract-design intent | Hand off to the framework skill for pure implementation work once the contract is stable |
| You need OpenAPI generation, Azure API patterns, versioning, pagination, LROs, or client-generation-ready contracts | The target is a non-HTTP contract such as GraphQL or gRPC | Use transport-specific skills for non-REST surfaces |
| The API must be agent-consumable, SDK-friendly, or Azure-review-ready before downstream tooling work | The request is manifest/plugin metadata without a need to stabilize the REST contract | Coordinate with app/plugin skills after this skill defines the HTTP contract |

## Anti-Hallucination Rule (MANDATORY before writing TypeSpec)

TypeSpec (formerly Cadl) has a rapidly evolving decorator surface across `@typespec/compiler`, `@typespec/http`, `@typespec/rest`, `@typespec/openapi3`, `@typespec/versioning`, and the Azure ecosystem libraries (`@azure-tools/typespec-azure-core`, `@azure-tools/typespec-azure-resource-manager`, `@azure-tools/typespec-client-generator-core`). Decorator names (`@route`, `@get`, `@post`, `@error`, `@statusCode`, `@autoRoute`), template parameters, model inheritance via `extends` vs `is`, and emitter options drift between TypeSpec minors. Azure data-plane vs ARM resource patterns use different libraries entirely.

Before writing any TypeSpec decorator, model definition, operation signature, emitter option, or `tspconfig.yaml` field, ground each identifier using one of these methods:

1. **Installed TypeSpec compiler and library versions**: `tsp --version` and `cat package.json` for `@typespec/*` and `@azure-tools/typespec-*` versions. Read the library's `lib/main.tsp` for available decorators.
2. **TypeSpec compiler output**: `tsp compile <file.tsp> --emit @typespec/openapi3` and inspect the generated OpenAPI. If a decorator was wrong, compile will error; if it was silently ignored, the generated OpenAPI will be missing fields.
3. **`@azure-tools/typespec-azure-core` library** (npm `latest` 0.71.0 as of 2026-08-26 - verify `npm ls @azure-tools/typespec-azure-core` before writing) for Azure data-plane APIs: `ResourceList`, `ResourceCreateOrUpdate`, `ResourceDelete`, `Page<T>` are library-defined; do not invent variants.
4. **`@azure-tools/typespec-azure-resource-manager`** (npm `latest` 0.71.0 as of 2026-08-26) for ARM templates: `TrackedResource`, `ProxyResource`, `ResourceOperations` come from this library only.
5. **`@azure-tools/typespec-client-generator-core` (TCGC)** for client/SDK shaping: `SdkOperationGroup` was consolidated into `SdkClient` (operation groups are now `SdkClient` instances) and `@operationGroup` is deprecated in favor of `@client` for sub-clients ([Azure/typespec-azure#3997](https://github.com/Azure/typespec-azure/pull/3997)). These are TCGC concepts - NOT `@azure-tools/typespec-azure-core`. Verify with `npm ls @azure-tools/typespec-client-generator-core` (npm `latest` 0.71.2 as of 2026-08-26 - the npm package version is independent of the `typespec-azure@X.Y.Z` GitHub monorepo tag; the two are not the same number).
6. **`tspconfig.yaml` schema** for the pinned compiler version: the `emit` and `options.*` shape is version-specific.

Forbidden shortcuts:

- Do not write `@autoRoute` if the project pinned a TypeSpec version where it was renamed or removed. Verify against installed `@typespec/http`.
- Do not invent `@error` vs `@errorResponse` vs `@statusCode` semantics. The library defines exactly one.
- Do not mix Azure data-plane (`@azure-tools/typespec-azure-core`) and ARM (`@azure-tools/typespec-azure-resource-manager`) patterns in the same service.
- Do not write LRO (`LongRunningOperation` / `LongRunningResourceCreateOrReplace`) signatures from memory; the Azure libraries provide canonical templates.
- Do not run `tsp compile` with `--no-emit` only; the emitter is where wrong identifiers produce silent OpenAPI omissions.

Known asymmetry traps (illustrative, verify each):

- `@route` vs `@autoRoute`: `@route` is explicit; `@autoRoute` infers from parameter decorators. Mixing them produces conflicting routes.
- `extends` vs `is` for model composition: `extends` is inheritance with merged properties; `is` is alias-like and changes the spread/include semantics.
- `Page<T>` is the canonical Azure-Core pagination model; `Cursor` and `PagedResultOf` are not. Invented pagination types break Azure SDK generation.
- ARM `Resource` requires `id`, `name`, `type`, `systemData` from the ARM library - do not write these properties manually.
- `@server` decorator host parameter formatting follows OpenAPI 3.x server template variables; invented `{baseUrl}` placeholders break Azure SDK client gen.
- **TypeSpec compiler - latest stable 1.15.0 (2026-08-11; verified 2026-08-26):** `FilterVisibility` template replaces `@withVisibilityFilter` (deprecated); `@withLifecycleUpdate` and `@applyMergePatch` are deprecated. Recent stable releases (1.14/1.15) added project-scoped config and feature flags, graduated the `internal` modifier, extended `enum-strategy: annotated`, and changed file-level `using` resolution - no decorator-surface break to the patterns this skill teaches, but re-run compile after any upgrade. Note that `@typespec/*` packages ship on **split version tracks** - `@typespec/compiler`/`http`/`openapi3` are 1.15.0 while `@typespec/rest`/`versioning` are 0.85.0; see [`bundles/emitter-gotchas/guide.md`](https://skilld.dev/api/skills-raw/lukemurraynz/hve-agent-skills/typespec-api-design/bundles/emitter-gotchas/guide.md). Always verify against the installed `@typespec/compiler` version before writing these decorators.
- **Client-generator (TCGC) and ARM deprecations (verified 2026-08-26):** In `@azure-tools/typespec-client-generator-core`, `SdkOperationGroup` was consolidated into `SdkClient` (operation groups are now `SdkClient` instances) and `@operationGroup` is deprecated in favor of `@client` for sub-clients ([Azure/typespec-azure#3997](https://github.com/Azure/typespec-azure/pull/3997)). In `@azure-tools/typespec-azure-resource-manager`, `@locationResource` is deprecated - use `@parentResource(ArmLocationResource<...>)` ([Azure/typespec-azure#4132](https://github.com/Azure/typespec-azure/pull/4132)). Current npm `latest` versions (as of 2026-08-26, verify before pinning): `@azure-tools/typespec-azure-core` **0.71.0**, `-azure-resource-manager` **0.71.0**, `-autorest` **0.71.0**, `-client-generator-core` (TCGC) **0.71.2**. The Azure packages currently sit on a shared 0.7x family but version independently of each other and of the TypeSpec core track (1.15.0) - always resolve each package's real npm version with `npm view <package> version`; never copy a number from the `typespec-azure@X.Y.Z` GitHub monorepo release tag (a tag groups a coordinated set but is not itself any package's npm version).

### Safe degraded output when verification is blocked

If you cannot run `tsp compile`, read installed `@typespec/*` `lib/main.tsp` files, or check `tspconfig.yaml` schema, do not produce production-shaped TypeSpec from memory. Wrong decorator silently omits an OpenAPI property; wrong Azure library template generates an SDK client that calls the wrong endpoint.

Instead, return:

1. **A labelled skeleton** with `[VERIFY]` markers on every decorator, library import, model template, operation template, and `tspconfig.yaml` field.
2. **Verification commands**: `npm ls @typespec/compiler @typespec/http @typespec/rest @typespec/openapi3`, `tsp compile <file> --emit @typespec/openapi3`, the library `lib/main.tsp` path, the Azure-Core/ARM library README URL.
3. **Identifier guess list** grouped by failure mode (compile failure / silently omitted OpenAPI property / wrong SDK client generation / ARM vs data-plane mismatch / LRO contract not recognized by client gen).
4. **A design-level artifact offer**: API surface inventory (resources, operations, error model), versioning plan (`@added` / `@removed` / `@versioned`), pagination strategy, LRO contract decisions, client-gen target language list, OpenAPI emitter options decision record.

Do not produce speculative TypeSpec under blocked verification. The cost of an omitted decorator is a silently incomplete OpenAPI, which downstream SDK generators do not catch.

## Quick start

From an empty directory, four commands take you to a compiled spec:

```shell
npx -y -p @typespec/compiler tsp init
npm install
npx tsp compile . --warn-as-error
npx tsp format "**/*.tsp"
```

Choose `rest` (interactive label "Generic REST API service") from the prompt - template names vary by compiler version.

`tsp init` scaffolds `main.tsp`, `tspconfig.yaml`, and `package.json` from the selected template. `npm install` resolves the pinned TypeSpec packages, `tsp compile` produces emitter output and fails on any warning, and `tsp format` normalizes all `.tsp` files in place. Open `tsp-output/@typespec/openapi3/openapi.yaml` to inspect the generated OpenAPI document.

## Routing

Use this skill when the task involves any of the following:

- Creating or reviewing a TypeSpec project.
- Designing a REST API contract before implementation.
- Generating OpenAPI 3.0/3.1, Swagger, or SDK-facing specs. (AutoRest retired July 1, 2026 - do not generate AutoRest inputs for new work.)
- Azure data-plane or Azure Resource Manager API design.
- API versioning, LROs, pagination, repeatability, conditional requests, or API review readiness.
- Migrating a hand-written OpenAPI 3.x spec into TypeSpec.
- Making an API contract agent-consumable before another skill creates tools, manifests, MCP servers, or plugins.

Do not use this skill as the primary skill for:

- Code-first API implementation unless the task asks to derive or validate the contract.
- Microsoft 365 Copilot declarative agents, plugin manifests, or Copilot-specific decorators. Stabilize the REST contract here, then route to the Microsoft 365 Copilot/App-as-Skill skill.
- Existing stable internal APIs where a validated code-first OpenAPI pipeline already exists and no redesign is planned.

### Stop conditions

Hand off or refuse when:

- User wants code-first scaffolding (controllers, handlers) without API design intent - use a language/framework skill instead.
- Target is M365 Copilot declarative agent metadata or plugin manifest - hand to the Microsoft 365 Copilot/App-as-Skill skill after the REST contract is stable.
- Target is a non-REST transport (gRPC, WebSocket). TypeSpec ships first-party emitters for protobuf and (since July 2026) GraphQL (`@typespec/graphql`), but this skill scopes to REST/OpenAPI contract design; route GraphQL-emitter work to its own documentation.
- User has a stable code-first OpenAPI pipeline and no redesign is planned - do not force a TypeSpec rewrite.
- User wants AutoRest-annotated Swagger 2.0 for new work - deprecated; redirect to `bundles/migration-from-openapi/`.

## Hard rules

Bundles inherit these hard rules; load this section first when working from any bundle.

1. Write TypeSpec first; emit OpenAPI/Swagger/SDK inputs from TypeSpec.
2. Never hand-edit `tsp-output/` or generated OpenAPI/Swagger files.
3. Commit `.tsp`, `tspconfig.yaml`, `package.json`, lockfiles, examples, and CI. Treat generated outputs as artifacts unless the repository policy requires checked-in swagger.
4. Every public model, property, operation, parameter, response, and error needs durable documentation.
5. Declare authentication at the service namespace with `@useAuth(...)` unless the API is explicitly public and unauthenticated.
6. Add versioning from the first service version for Azure APIs and for any external or partner-facing API.
7. Use Azure Core standard operation templates for Azure data-plane APIs unless there is a documented exception.
8. Do not use `@operationId` for Azure APIs. Let operation IDs derive from interface and operation names. Operations MUST live inside an `interface` whose name is the resource (PascalCase, plural). This produces operation IDs in the form `<Resource>_<Verb>` (e.g., `Widgets_Get`). Bare `op` declarations outside an interface produce inconsistent IDs.
9. Do not use `@format(...)` for Azure APIs when a concrete scalar exists. Prefer `uuid`, `url`, `eTag`, `ipV4Address`, `ipV6Address`, `utcDateTime`, and explicit numeric widths.
10. Constrain resource keys and path parameters with `@maxLength` and `@pattern`.
11. Validate with `npx tsp compile . --warn-as-error`; do not rely on a separate `tsp lint` command.
12. Use official docs or the TypeSpec/Azure TypeSpec MCP server before emitting version-sensitive decorators, templates, or package versions.
13. **AutoRest retired on July 1, 2026.** Do not generate or recommend `autorest` or `@autorest/*` inputs, annotations, or pipelines - the retirement date has passed. TypeSpec with its built-in emitters is the approved replacement. For migration tasks, use the `bundles/migration-from-openapi/` bundle and flag any remaining AutoRest dependency as a live blocker requiring immediate resolution. This refers to the retiring `autorest` CLI and `@autorest/*` npm packages. The TypeSpec emitter `@azure-tools/typespec-autorest` (which produces Swagger 2.0 + `x-ms-*` extensions for Azure SDK pipelines) is a separate, supported package - do NOT confuse the two.
14. All `@server(...)` URLs MUST use `https://` for non-local environments. `http://` is only acceptable for `localhost` development servers.
15. Pin TypeSpec packages to exact patch versions in `package.json` (`1.15.0`, not `^1.15.0`) and commit the lockfile. Use `npm ci` in CI, never `npm install`.
16. Every error response model includes a `code` string field (machine-readable, stable across versions) alongside the human-readable `message`.
17. Never put PII (email, name, SSN, phone, account number) in URL path or query parameters. PII travels in the request body, or in headers if a header is unavoidable.
18. Paginated list operations declare `@maximum` on `top`/`limit`/`pageSize` parameters to prevent pagination DoS. Cap at a documented sane maximum (typically 100).
19. Server-assigned fields (`id`, `createdAt`, `updatedAt`, `eTag`, `provisioningState`) MUST carry `@visibility(Lifecycle.Read)` so they cannot be set via Create/Update payloads (mass-assignment defense).
20. Correlation headers (`traceparent` per W3C Trace Context, or `x-ms-client-request-id`) MUST be modelled on operations exposed to external or partner callers, on both request and response. Correlation IDs never travel in URL paths or query strings.
21. Deprecated operations MUST carry both the `#deprecated` directive AND the runtime header trio (`Deprecation`, `Sunset`, `Link rel="successor-version"`). The directive alone is insufficient - live clients see headers, not source code.
22. Any API that delivers webhooks or fetches a client-supplied URL MUST document its SSRF runtime policy on the URL field's `@doc`: HTTPS-only, no private/loopback/link-local/cloud-metadata IPs, port allow-list, redirect cap, response-size cap, total timeout. See `bundles/webhooks/guide.md`.
23. Design the contract so any replica can serve any request: no server-affinity semantics (no "call the same instance that handled your last request"), no client-visible instance/session IDs, no state implied by prior calls beyond what a shared store (DB/cache) or an opaque continuation/cursor token carries.
24. Every `POST`/`PATCH` that creates or mutates a resource MUST accept a client-supplied idempotency key header (`Idempotency-Key` for generic/non-Azure REST APIs, `Repeatability-Request-ID`/`Repeatability-First-Sent` from `@azure-tools/typespec-azure-core` for Azure data-plane APIs), not just as a documented example. This applies regardless of API style - generic REST, Azure data-plane, or ARM. Apply exactly ONE scheme per API surface - do not mix `Idempotency-Key` and `Repeatability-*` on the same operations. Retries after a dropped connection are the normal case under horizontal scaling, not an edge case.
25. Rate-limited and overloaded responses (`429`, `503`) MUST include `Retry-After`. Any API with a request quota MUST expose `RateLimit-Limit`/`RateLimit-Remaining`/`RateLimit-Reset` (IETF `RateLimit` header fields draft) so clients get consistent backpressure regardless of which replica answered.
26. Every service exposes unauthenticated `GET /health` (liveness) and `GET /ready` (readiness, checks dependencies) endpoints outside the versioned API surface, for load balancer and orchestrator probes.

## Default output contract

When applying this skill, produce these sections unless the user asks for a narrower output:

1. API design summary and assumptions.
2. Recommended project structure.
3. `package.json` with pinned compatible TypeSpec packages.
4. `tspconfig.yaml` with emitters, linter rules, and `warn-as-error`.
5. `main.tsp` and any split model/operation files.
6. Example request/response and error shapes.
7. Expected emitted artifact paths.
8. CI validation commands or pipeline snippet.
9. API review checklist.
10. Known open design decisions.

## Bundle routing

Load only the bundles needed for the task:

| Task | Bundle | Covers |
| --- | --- | --- |
| Generic non-Azure REST API | `bundles/generic-rest/guide.md` | service scaffold, models, routes, errors, OpenAPI emitter wiring |
| Azure data-plane API | `bundles/azure-data-plane/guide.md` | Entra auth, ResourceOperations templates, conditional requests, repeatability |
| Azure Resource Manager API | `bundles/azure-arm/guide.md` | ARM tracked/proxy resources, identity, SystemData, RPC contract |
| LROs, pagination, versioning, repeatability | `bundles/lro-pagination-versioning/guide.md` | `@pollingOperation`, `Page<T>`, `@versioned`, `Repeatability-*` headers |
| Existing OpenAPI 3.x migration | `bundles/migration-from-openapi/guide.md` | `tsp-openapi3` conversion, cleanup, AutoRest retirement path |
| CI, validation, package hygiene | `bundles/ci-validation/guide.md` | `npm ci`, `--warn-as-error`, lockfile policy, emitter artifacts |
| Microsoft 365 Copilot/App-as-Skill boundary | `bundles/m365-copilot-boundary/guide.md` | stable operation IDs, doc coverage, agent-safe response shapes |
| Webhooks and SSRF defense for outbound/inbound URLs | `bundles/webhooks/guide.md` | subscription resource, HMAC signing + replay window, SSRF runtime checks, retry/back-off, verification handshake |
| Emitter/versioning gotchas, package version-tracks, unsupported emitters | `bundles/emitter-gotchas/guide.md` | issue-grounded emit-time traps (`@statusCode` via alias, unions, versioned examples, `Record` keys), split `@typespec/*` version tracks, shipped-vs-absent emitters (GraphQL shipped July 2026; AsyncAPI/WebSocket absent) |
| Final quality gate | `bundles/review-checklist/guide.md` | API review checklist, breaking-change scan, sign-off gates |

## Naming conventions

- Models: PascalCase singular (`Widget`, not `Widgets`).
- Properties: camelCase (`createdAt`, not `created_at`).
- Enum members: PascalCase (`Active`, `Maintenance`).
- Interfaces (resource collections): PascalCase plural (`Widgets`).
- URL path segments: camelCase plural matching the interface (`/widgets`).
- Operation verbs in the interface: camelCase (`list`, `read`, `create`, `update`, `delete`).

## Modern baseline

Use the following baseline for new projects unless a repository already pins versions:

- Node 22 LTS minimum, Node 24 LTS recommended for Azure work.
- `@typespec/compiler` and all `@typespec/*` libraries on the same stable release family.
- Azure packages matched to the current `Azure/typespec-azure` release family when using Azure libraries.
- `tspconfig.yaml`, not `tspconfig.yml`.
- `npx tsp compile . --warn-as-error` for CI validation.
- `tsp-output/` ignored locally and published as a build artifact.

Latest verified baseline at skill update time, 2026-08-26:

- TypeSpec stable family (split version tracks): `@typespec/compiler`, `@typespec/http`, `@typespec/openapi3` are **1.15.0** (2026-08-11); `@typespec/rest` and `@typespec/versioning` are **0.85.0** (same release train). These are individual npm package versions; the `typespec-stable@<tag>` GitHub release tag groups a coordinated set.
- Azure TypeSpec packages (npm `latest`, 2026-08-26): `@azure-tools/typespec-azure-core` **0.71.0**, `@azure-tools/typespec-azure-resource-manager` **0.71.0**, `@azure-tools/typespec-autorest` **0.71.0**, `@azure-tools/typespec-client-generator-core` (TCGC) **0.71.2**. The Azure packages currently share a 0.7x family but version independently of each other and of the TypeSpec core track - never assume one number covers all.
- The `typespec-azure@X.Y.Z` GitHub **monorepo release tag** is NOT a package's npm version (a `typespec-azure@0.70.x` tag can ship `@azure-tools/typespec-azure-core@0.71.0`). Always resolve the real npm version with `npm view <package> version` (or the package's npm page), not from the monorepo tag.
- Package-level Azure releases move independently within the release family; verify each `@azure-tools/typespec-*` package version at <https://github.com/Azure/typespec-azure/releases> or via `npm view` before pinning.
- OpenAPI 3 conversion uses the `tsp-openapi3` CLI from `@typespec/openapi3`.

## Core project shape

```text
my-api/
  package.json
  tspconfig.yaml
  main.tsp
  models/
    *.tsp
  operations/
    *.tsp
  examples/
    *.json
  .gitignore
  .github/workflows/typespec.yml   # or azure-pipelines.yml
```

The `examples/` directory holds JSON example payloads referenced from `@example(...)` decorators on operations and models; they flow into the generated OpenAPI `examples` fields.

Minimum `.gitignore`:

```gitignore
tsp-output/
node_modules/
```

## Generic service example

### Minimal example

```typespec
import "@typespec/http";
import "@typespec/rest";
import "@typespec/openapi3";

using Http;
using Rest;

@service(#{ title: "Contoso Widget API" })
@server("https://api.contoso.example", "Production endpoint")
@useAuth(BearerAuth)
namespace Contoso.Widgets;

/** A widget managed by the service. */
model Widget {
  @visibility(Lifecycle.Read) id: string;
  @minLength(1) @maxLength(120) name: string;
  status: "active" | "inactive" | "maintenance";
  @visibility(Lifecycle.Read) createdAt: utcDateTime;
}

/** Standard error response. */
@error
model ErrorResponse {
  @statusCode statusCode: 400 | 401 | 403 | 404 | 409 | 429 | 500;
  code: string;
  message: string;
}

@route("/widgets")
interface Widgets {
  /** List widgets. */
  @get list(@query filter?: string): Widget[] | ErrorResponse;
  /** Get a widget by identifier. */
  @get read(@path id: string): Widget | ErrorResponse;
  /** Create a widget. */
  @post create(@body widget: Widget): Widget | ErrorResponse;
}
```

### Production-ready example

```typespec
import "@typespec/http";
import "@typespec/rest";
import "@typespec/versioning";
import "@typespec/openapi3";

using Http;
using Rest;
using Versioning;

@service(#{ title: "Contoso Widget API" })
@server("https://api.contoso.example", "Production endpoint")
@useAuth(BearerAuth)
@versioned(Versions)
namespace Contoso.Widgets;

enum Versions {
  v2026_01_01: "2026-01-01",
}

/** Single page of results. */
// This `Page<T>` is a generic-API pattern. For Azure data-plane work use `Azure.Core.Page<T>` from `@azure-tools/typespec-azure-core` instead — it has shape `{ @pageItems value: T[]; @nextLink nextLink?: ResourceLocation<T>; }`.
model Page<T> {
  @doc("Items in the current page.")
  items: T[];

  @doc("Opaque cursor for the next page; absent on the last page.")
  nextLink?: url;
}

/** One field-level error detail. */
model ErrorDetail {
  code: string;
  message: string;
  target?: string;
}

/** Standard error envelope. */
@error
model ErrorResponse {
  @statusCode statusCode: 400 | 401 | 403 | 404 | 409 | 412 | 429 | 500;
  code: string;
  message: string;
  target?: string;
  details?: ErrorDetail[];
}

/** A widget managed by the service. */
model Widget {
  @visibility(Lifecycle.Read)
  @maxLength(64)
  @pattern("^[a-zA-Z0-9-]+$")
  id: string;

  @minLength(1)
  @maxLength(120)
  name: string;

  status: "active" | "inactive" | "maintenance";

  @visibility(Lifecycle.Read)
  createdAt: utcDateTime;

  @visibility(Lifecycle.Read)
  @header("ETag")
  etag: string;
}

@route("/widgets")
interface Widgets {
  /** List widgets (paginated). */
  // `@maximum(100)` caps page size — prevents pagination DoS (hard rule #18).
  @get list(@query filter?: string, @query @maximum(100) top?: int32): Page<Widget> | ErrorResponse;

  /** Get a widget by identifier. */
  @get read(
    @path @maxLength(64) @pattern("^[a-zA-Z0-9-]+$") id: string,
  ): Widget | ErrorResponse;

  /** Create a widget. */
  @post create(
    @header("Repeatability-Request-ID") repeatabilityRequestId: string,
    @body widget: Widget,
  ): Widget | ErrorResponse;

  /** Replace a widget. */
  @put update(
    @path @maxLength(64) @pattern("^[a-zA-Z0-9-]+$") id: string,
    @header("If-Match") ifMatch: string,
    @body widget: Widget,
  ): Widget | ErrorResponse;

  /** Delete a widget. */
  @delete delete(
    @path @maxLength(64) @pattern("^[a-zA-Z0-9-]+$") id: string,
    @header("If-Match") ifMatch: string,
  ): void | ErrorResponse;

  /** Archive a widget. Added in 2026-01-01. */
  @added(Versions.v2026_01_01)
  @route("/{id}:archive")
  @post archive(
    @path @maxLength(64) @pattern("^[a-zA-Z0-9-]+$") id: string,
  ): Widget | ErrorResponse;
}
```

## Common commands

```shell
npm ci
npx tsp compile . --warn-as-error
npx tsp compile . --watch
npx tsp format "**/*.tsp"
npx tsp info
```

Do not prescribe `tsp lint .` for new projects unless the repository has proven that command exists in its pinned compiler version. Configure linter rules in `tspconfig.yaml` and fail CI with `tsp compile . --warn-as-error`.

## Troubleshooting

- **`Cannot find name 'BearerAuth'`** - Missing `using Http;`. Add it (and `import "@typespec/http";`) at the top of the file.
- **`Diagnostics were reported during compilation`** with `--warn-as-error` - Run `npx tsp compile .` without the flag first to see the underlying warnings, then fix them and re-run with `--warn-as-error`.
- **`tsp-output/` empty after compile** - The `emit:` list in `tspconfig.yaml` is missing or wrong. Confirm `@typespec/openapi3` (and any other emitters) are listed and installed.
- **`tsp init` fails behind a proxy** - Set `npm_config_registry` (and `HTTPS_PROXY` if required) before running, or pre-fetch the template into a local registry mirror.
- **Mixed Azure + non-Azure libraries with version conflicts** - Align all `@azure-tools/typespec-*` packages to the same `typespec-azure` release family; do not mix release lines.
- **Node version mismatch** - TypeSpec 1.x now requires Node 22+ (microsoft/typespec CONTRIBUTING.md bumped to Node 22). Azure TypeSpec recommends Node 24 LTS. Check `node --version` and upgrade if older.
- **Corporate proxy SSL errors (`UNABLE_TO_GET_ISSUER_CERT_LOCALLY`)** - Set `NODE_EXTRA_CA_CERTS=/path/to/corporate-bundle.pem` or `npm config set cafile /path/to/corporate-bundle.pem`. Setting only `HTTPS_PROXY` is not enough when the proxy MITMs TLS.
- **VS Code TypeSpec extension not activating in a monorepo subproject** - Set `typespec.tsp-server.path` in workspace settings to point at the project-local compiler (e.g., `./node_modules/.bin/tsp-server`). The extension's default lookup misses nested `tspconfig.yaml` files.
- **`duplicate-type-name` errors** - Usually caused by `@friendlyName` collisions or by `@parameterVisibility` producing sibling shapes the emitter names identically. Run `npx tsp compile .` (no flags) to see the diagnostic location; rename one of the colliding constructs.
- **Stale `tsp-output/`** - If output looks stale after edits, delete `tsp-output/` and recompile; emitters cache aggressively.
- **VS Code TypeSpec extension not activating** - Install the official TypeSpec extension; ensure your workspace folder contains `tspconfig.yaml` at the root.
- **Peer-dep warnings during `npm ci`** - Align all `@typespec/*` and `@azure-tools/typespec-*` packages to the same release family (see Modern baseline).
- **Windows path issues with `tsp format`** - Use forward slashes in glob patterns: `tsp format "**/*.tsp"`, not `tsp format "**\*.tsp"`.

## Glossary

- **TypeSpec** - Microsoft's API description language that compiles to OpenAPI, JSON Schema, and SDK inputs.
- **Emitter** - A TypeSpec compiler plugin that turns the compiled program into output files (e.g., `@typespec/openapi3`).
- **Decorator** - A `@name(...)` annotation that attaches metadata or behavior to a model, operation, parameter, or namespace.
- **LRO** - Long-running operation; an async REST pattern that returns 202 plus a polling URL.
- **ARM** - Azure Resource Manager; the control-plane API surface for Azure resources.
- **Data plane** - The runtime API surface a service exposes to its customers, distinct from ARM control-plane operations.
- **Repeatability** - The `Repeatability-Request-ID`/`Repeatability-First-Sent` header convention that makes POST/PATCH safely retriable.
- **Visibility** - A model property modifier (`Lifecycle.Read`, `Lifecycle.Create`, etc.) that controls which payloads include the property.
- **Scalar** - A TypeSpec primitive or named refinement of a primitive (e.g., `uuid`, `eTag`, `utcDateTime`).
- **Augment decorator (`@@`)** - A top-level form (`@@doc(Target, "...")`) used to attach a decorator to a target declared elsewhere, including imported libraries.
- **`@key`** - Marks a model property as the resource key; required by resource operation templates and used to derive path parameters.
- **`interface`** - A grouping of related operations on a resource; produces the prefix of generated operation IDs (`Widgets_Get`).
- **`namespace`** - A logical container for models, operations, and the service declaration; the root namespace decorated with `@service` defines the service surface.
- **`model is X`** vs **`model extends X`** - `is` creates an alias/copy with the same shape (used with templates like `is ResourceOperation<...>`); `extends` creates a subtype that inherits properties and supports polymorphism.
- **Template** - A parameterized type (`model Page<T>`, `op StandardResourceOperation<...>`) instantiated with concrete type arguments; the primary reuse mechanism in TypeSpec.
- **Union** - A type composed of alternatives (`"active" | "inactive"` or `Cat | Dog`); named unions support discriminators for polymorphism.
- **`op`** - Declares an operation; prefer placing operations inside an `interface` so operation IDs derive from `<Interface>_<op>`.

## Agent-readiness baseline

Any API intended for Copilot, MCP, or other agent consumption MUST satisfy these minimums before handoff:

- Stable operation IDs derived from interface and operation names (do not use `@operationId`).
- `@doc` on every operation, parameter, and response model.
- Explicit error responses for every operation, with a typed error envelope.
- No `anyOf` / `oneOf` polymorphism without a `discriminator`; agents cannot reliably route untagged unions.
- Bounded response shapes: paginated lists, `@maxLength`/`@maxItems` on user-facing strings and arrays, no open-ended `Record<unknown>` payloads.

See `bundles/m365-copilot-boundary/guide.md` for the full handoff checklist used when crossing into agent/manifest territory.

## Final Output Contract

Every engagement produces:

- A TypeSpec-first contract recommendation or scaffold aligned to the requested API style.
- Pinned package, emitter, and validation guidance needed to compile and inspect the contract safely.
- Example shapes for operations, errors, versioning, and other behaviors that materially affect the API surface.
- Explicit open design decisions or `[VERIFY]` markers for any version-sensitive identifier that could not be confirmed.

## Quality Gate

Do not finish with speculative decorators, mixed Azure library tracks, or unvalidated emitter assumptions. The result must either compile under the pinned toolchain or clearly identify what still needs verification before production use.

## Hidden Decorator Surface

TypeSpec has 50+ decorators. These are present in the `@typespec/http`, `@typespec/rest`, and `@typespec/openapi` libraries but easily missed:

| Decorator | Impact |
|---|---|
| `@server(url: string, description?: string, variables?: Record<string, ...>)` | Server URL with templated variables and defaults |
| `@useAuth(MyAuth)` / `@useAuth(MyAuth | OtherAuth)` | Multiple auth schemes — `BearerAuth`, `ApiKeyAuth`, `OAuth2` with union for OR logic |
| `@route(path: string, ?shared: boolean)` | Route template with `{pathParam}` variables |
| `@query("paramName", ?format: "csv" \| "multi")` | Query parameter serialization format |
| `@header(name: string, ?format: "csv" \| "multi")` | Header parameter with format options |
| `@visibility("read", "create", "update", "delete")` | Per-visibility property exposure — Azure data-plane pattern |
| `@withVisibility("read")` | Apply visibility filter to access specific properties |
| `@extension("x-custom", value)` | OpenAPI extension — arbitrary spec extensions |
| `@encode("base64", typeof bytes)` | Encoding directives `base64`, `base64url`, `date`, `rfc1123`, `unixTimestamp` |
| `@minValue(N)` / `@maxValue(N)` / `@minLength(N)` / `@maxLength(N)` | Validation constraints on scalar types |
| `@pattern(regex)` | String pattern validation |
| `@format("email" \| "uri" \| "uuid")` | Known format annotations |
| `@added(Version)` / `@removed(Version)` / `@madeOptional(Version)` | API versioning lifecycle decorators |
| `@opExample` / `@opResponseExample` | Operation and response examples in spec |
| `@statusCode(N)` | Explicit HTTP status code on return model |

## References to verify before version-sensitive work

- TypeSpec docs: `https://typespec.io/docs`
- TypeSpec CLI docs: `https://typespec.io/docs/handbook/cli/`
- TypeSpec configuration docs: `https://typespec.io/docs/handbook/configuration/configuration/`
- OpenAPI 3 emitter guide: `https://typespec.io/docs/emitters/openapi3/openapi/`
- TypeSpec releases: `https://github.com/microsoft/typespec/releases`
- Azure TypeSpec docs: `https://azure.github.io/typespec-azure/docs/intro/`
- Azure TypeSpec style guide: `https://azure.github.io/typespec-azure/docs/reference/azure-style-guide/`
- Azure TypeSpec releases: `https://github.com/Azure/typespec-azure/releases`
- Microsoft 365 Copilot TypeSpec overview: `https://learn.microsoft.com/en-us/microsoft-365/copilot/extensibility/overview-typespec`
