All skills
lukemurraynz avatar

/typespec-api-design

@2cc2455

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.

Use this Skill: https://skilld.dev/gh/lukemurraynz/hve-agent-skills/typespec-api-design

This session only. Nothing lands on disk.

bundleslro-pagination-versioningguide.md

≈2.1k tokens on demand. Your agent reads this file only when SKILL.md points to it.

LRO, pagination, versioning, and request trait bundle

Use this bundle when the API involves long-running operations, collection pagination/filtering, version lifecycle, repeatability, conditional requests, or client request IDs.

Versioning

Add versioning at the first external release.

@versioned(Contoso.WidgetService.Versions)
namespace Contoso.WidgetService;

/** Service API versions. */
enum Versions {
  /** Initial stable release. */
  v2026_05_10: "2026-05-10",
}

When changing the contract:

model Widget {
  /** Widget name. */
  name: string;

  /** Added in the 2026-09-01 API version. */
  @added(Versions.v2026_09_01)
  color?: string;
}

For Azure APIs, keep version strings date-based. For non-Azure APIs, use the organization standard, but avoid mixing date and semantic versioning in one API.

Preview version suffix

Azure REQUIRES the YYYY-MM-DD-preview suffix as the ONLY acceptable preview format (e.g., 2026-05-01-preview). Other suffixes - -beta, -rc, -alpha, -private, semver-style 1.0.0-preview.2 - are rejected by Azure API review. Promote to GA by dropping the -preview suffix; do not introduce intermediate suffixes.

Version-level retirement

Deprecating individual operations is not the same as retiring an entire API version. Retire a whole version when most of its operations are deprecated or its auth/error/pagination patterns are obsolete. A version retirement needs its own plan, not a pile of per-operation deprecations:

  • Announce the retirement with a dated Sunset header on EVERY operation in that version (12+ months out for public APIs) plus a version-to-version migration guide published before the announcement.
  • Track traffic to the old version as a whole - retire only when residual calls are trivial, then remove the enum member with @removed(Versions.vOld) semantics in a new version projection.
  • Do not silently drop an enum member from the current Versions enum: existing clients still send the old api-version, and removing it without a documented window turns every such call into a 400.

Pagination

For Azure data-plane APIs, use Azure Core list templates and query parameter traits instead of hand-rolling paging.

op listWidgets is Operations.ResourceList<
  Widget,
  ListQueryParametersTrait<
    StandardListQueryParameters &
    SelectQueryParameter &
    ExpandQueryParameter
  >
>;

For generic APIs, use a stable envelope:

/** A page of widgets. */
model PagedWidgets {
  /** Items in this page. */
  items: Widget[];

  /** Opaque continuation token or next link. */
  nextLink?: url;
}

Long-running operations

Only use an LRO when the operation can outlive a normal HTTP request/response window or requires polling for final state.

Azure Core pattern:

interface Widgets {
  /** Get widget operation status. */
  getWidgetOperationStatus is Operations.GetResourceOperationStatus<Widget>;

  /** Create or replace a widget asynchronously. */
  @pollingOperation(Widgets.getWidgetOperationStatus)
  createOrReplaceWidget is Operations.LongRunningResourceCreateOrReplace<Widget>;

  /** Delete a widget asynchronously. */
  @pollingOperation(Widgets.getWidgetOperationStatus)
  deleteWidget is Operations.LongRunningResourceDelete<Widget>;
}

The polling/status operation must be defined before operations that reference it.

Polling location safety

@pollingOperation MUST point to a status endpoint hosted on the same service host as the initiating operation. Some Azure SDK pollers block cross-host polling URLs to prevent token-leak and SSRF vectors, so a polling URL that drifts to a different hostname will fail at runtime even though the contract compiles. Keep the polling/status route under the same @server and the same auth scope as the LRO that produced it.

Repeatability

Use repeatable request support for create/action operations that can be retried safely after network failure. For Azure data-plane APIs, include SupportsRepeatableRequests in service traits where applicable.

alias ServiceTraits = SupportsRepeatableRequests & SupportsClientRequestId;
alias Operations = ResourceOperations<ServiceTraits>;

Conditional requests

Use conditional request support for update/delete/read scenarios that need concurrency control.

alias ServiceTraits = SupportsConditionalRequests & SupportsClientRequestId;

Client request ID

External APIs should accept a caller-supplied correlation/client request ID. For Azure APIs, prefer SupportsClientRequestId.

@added and @removed interactions

If a property is @removed(Versions.v2) on a model that itself was @added(Versions.v2), you MUST also add @added(Versions.v2) to the property - otherwise the emitter generates an orphan reference and the diagnostic is opaque (microsoft/typespec issue #7035). Pattern:

@added(Versions.v2)
model Foo {
  @added(Versions.v2) @removed(Versions.v3) bar?: string;
}

Breaking-change review

Before adding a version, classify changes:

  • Additive: new optional property, new operation, new enum value if clients tolerate unknown values.
  • Potentially breaking: required property, renamed property, removed value, changed format/type, changed status code, changed auth, changed route, changed error shape.
  • Breaking: removed operation, required parameter added to existing operation, route identity changed, incompatible response body change.

Use @added, @removed, and versioned projections intentionally. Do not hide breaking changes behind undocumented emitter output differences.

Worked classification table

Change type Breaking? Mitigation
Add new optional request property No Ship in next version with @added.
Add new operation No Ship in next version with @added.
Add new enum value (closed enum) Yes - strict SDKs throw Use an open union, or bump major version.
Make optional request property required Yes New API version; keep optional in old version via projection.
Rename property Yes Keep the old name as @deprecated alias for one version, then @removed.
Tighten @pattern/@maxLength Yes New API version; loosening is non-breaking.
Remove operation Yes @removed in a new version after a deprecation window.
Change response status code Yes New API version with explicit version-gated response.
Change OAuth2 audience or tokenUrl Yes (security) Major new API version; coordinate token rollover.

Security-impacting breaking changes

Some changes look small but break clients in ways that fail-closed. Treat all of these as breaking and require a new API version:

  • Removing a scope from an OAuth2Auth flow. Existing tokens may have been issued with the removed scope and will now be rejected (or new tokens will lack permissions the client expects). Adding a new scope is usually breaking too if it's required for existing operations.
  • Tightening a @pattern regex (or @maxLength, @minLength, numeric bounds). Payloads that previously validated will now fail at the contract boundary. Loosening these constraints is generally non-breaking.
  • Required to optional on response fields is non-breaking; optional to required on request fields IS breaking. Clients that previously omitted the field will start receiving 400 errors.
  • Renaming an enum value is breaking. Adding a new enum value is breaking only for clients that treat unknown values as errors (true for many strict-typed SDKs). To rename safely, keep the old value as a @deprecated alias for at least one version.
  • Changing the audience or tokenUrl on an OAuth2 flow. Even if scopes are unchanged, tokens for the old audience will not authenticate against the new one.

Audience changes

Changing the OAuth2 token audience invalidates ALL existing tokens regardless of scope overlap - the audience is part of the token signature payload and is validated independently of scope claims. Even if the new audience is a strict superset of the old one's permissions, every client that holds a cached token will fail-closed on its next request. Treat any audience change as a major breaking change that requires a new API version, coordinated client rollout, and a documented token-refresh window.

Source: SKILL.md on GitHub

No alerts8d3 checks · Risk SAFE
  • Gen Agent Trust Hub8d

    This skill is a comprehensive and legitimate tool for designing API-first contracts using TypeSpec and Azure patterns. It includes extensive security best practices, particularly regarding SSRF defense, PII handling, and idempotency. The only detected concern is a standard vulnerability surface for indirect prompt injection when processing external OpenAPI specifications for migration.

  • Socket8d

    No alerts

  • Snyk8d

    Risk: LOW · No issues

Signed by skilld at 2cc2455. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub last month.

Steadyupdated last month
metadata
{
  "last_verified": "2026-08-26",
  "version": "1.3.1"
}

README badge

README badge for lukemurraynz/hve-agent-skills/typespec-api-design