Azure data-plane API bundle
Use this bundle for Azure-aligned data-plane APIs and for non-Azure APIs that intentionally want Microsoft API Guidelines-style rigor.
Recommended dependencies
{
"type": "module",
"private": true,
"scripts": {
"build": "tsp compile . --warn-as-error",
"watch": "tsp compile . --watch",
"format": "tsp format \"**/*.tsp\""
},
"dependencies": {
"@typespec/compiler": "<verified-compatible-version>",
"@typespec/http": "<verified-compatible-version>",
"@typespec/rest": "<verified-compatible-version>",
"@typespec/openapi3": "<verified-compatible-version>",
"@typespec/versioning": "0.85.0",
"@azure-tools/typespec-azure-core": "0.71.0",
"@azure-tools/typespec-autorest": "0.71.0"
}
}Before committing, replace <verified-compatible-version> with versions verified from official TypeSpec/Azure TypeSpec release notes, the repository lockfile, or tsp init https://aka.ms/typespec/azure-init output. Azure TypeSpec packages can move independently.
Recommended imports
import "@typespec/http";
import "@typespec/rest";
import "@typespec/versioning";
import "@azure-tools/typespec-azure-core";
using Http;
using Rest;
using Versioning;
using Azure.Core;Recommended tspconfig.yaml
warn-as-error: true
emit:
- "@azure-tools/typespec-autorest"
- "@typespec/openapi3"
linter:
extends:
- "@azure-tools/typespec-azure-core/all"
options:
"@azure-tools/typespec-autorest":
emitter-output-dir: "{output-dir}/swagger"
"@typespec/openapi3":
openapi-versions:
- "3.0.0"Use @azure-tools/typespec-autorest for Azure SDK-facing output. @typespec/openapi3 alone does not emit Azure x-ms-* extensions required by Azure SDK tooling.
Service skeleton
@service(#{ title: "Contoso Widget Service" })
@server("https://{accountName}.widgets.contoso.azure.com", "Service endpoint", {
/** Azure resource account name. */
accountName: string;
})
@versioned(Contoso.WidgetService.Versions)
@useAuth(EntraIDToken)
namespace Contoso.WidgetService;
/** Service API versions. */
enum Versions {
/** Initial stable release. */
v2026_05_10: "2026-05-10",
}
/** Microsoft Entra ID OAuth2 flow. */
model EntraIDToken is OAuth2Auth<[
{
type: OAuth2FlowType.authorizationCode;
authorizationUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize";
tokenUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/token";
scopes: ["https://widgets.contoso.azure.com/.default"];
}
]>;Resource and operation templates
alias ServiceTraits = SupportsRepeatableRequests &
SupportsConditionalRequests &
SupportsClientRequestId;
alias Operations = Azure.Core.ResourceOperations<ServiceTraits>;
/** A widget resource. */
@resource("widgets")
model Widget {
/** Widget name. */
@key("widgetName")
@maxLength(64)
@pattern("^[A-Za-z0-9-]+$")
@visibility(Lifecycle.Read)
name: string;
/** User-visible display name. */
@maxLength(120)
displayName?: string;
/** Provisioning state. */
@visibility(Lifecycle.Read)
provisioningState?: "Succeeded" | "Failed" | "Canceled" | "Creating" | "Updating" | "Deleting";
}
interface Widgets {
/** Get a widget. */
getWidget is Operations.ResourceRead<Widget>;
/** Create or update a widget. */
createOrUpdateWidget is Operations.ResourceCreateOrUpdate<Widget>;
/** Delete a widget. */
deleteWidget is Operations.ResourceDelete<Widget>;
/** List widgets. */
listWidgets is Operations.ResourceList<Widget>;
}OAuth2 import clarification
OAuth2Auth, OAuth2FlowType, BearerAuth, and NoAuth are all provided by @typespec/http and become available once you do using Http;. They are not Azure-specific. The EntraIDToken symbol shown in the service skeleton above is a user-defined alias built on top of OAuth2Auth - it is not a built-in. You can name your auth model anything; only OAuth2Auth/OAuth2FlowType/BearerAuth/NoAuth are framework symbols.
Repeatability headers
Clients MUST send TWO headers to make a request repeatable:
Repeatability-Request-ID- a uuid that uniquely identifies the request attempt.Repeatability-First-Sent- the original send time in HTTP-date format (RFC 7231).
The SupportsRepeatableRequests trait adds both headers to the operation contract automatically. The server MUST return the same response for the same Repeatability-Request-ID within a 24-hour window; conflicting reuse of an ID with a different payload MUST return 409 Conflict.
api-version requirement
Every Azure data-plane operation MUST accept api-version as a required query parameter. Azure Core operation templates (Operations.ResourceRead, ResourceCreateOrUpdate, ResourceList, etc.) inject this parameter automatically. Hand-written op declarations must add it explicitly via @query("api-version") apiVersion: string in the parameter list - omitting it breaks the Azure SDK pipeline.
Standard error model
Azure data-plane services MUST use the standard error envelope. The canonical type is Azure.Core.Foundations.ErrorResponse; if you define it locally for reference, wrap an ErrorDetail inside an ErrorResponse:
model ErrorResponse {
error: ErrorDetail;
}
model ErrorDetail {
code: string;
message: string;
target?: string;
details?: ErrorDetail[];
innererror?: InnerError;
}Do not emit a flat { code, message } payload at the response root - Azure SDK error parsers expect the wrapper.
Conditional request safety
When an operation accepts If-Match or If-None-Match, the success response MUST include an ETag header, and the contract MUST declare a 412 Precondition Failed response for concurrency conflicts. Without these, clients cannot implement safe optimistic concurrency.
/** Precondition failed - the resource was modified by another caller. */
@error
model PreconditionFailedResponse {
@statusCode statusCode: 412;
@header eTag?: string;
code: "PreconditionFailed";
message: string;
}
model WidgetReadResponse {
@header eTag: string;
@body widget: Widget;
}Pair this with SupportsConditionalRequests in service traits so Azure Core wires If-Match/If-None-Match parameters consistently.
Security hard rules for Azure data plane
- All endpoints MUST be served over HTTPS. No HTTP, no exceptions, including dev/test rings.
- The OAuth2 token audience MUST be the resource-specific audience: the data-plane resource URI (e.g.,
https://widgets.contoso.azure.com/.default) for data-plane APIs, orhttps://management.azure.com/.defaultfor ARM. Document the audience explicitly in the OAuth2 flowscopes. - Use Microsoft Entra ID (Azure AD) as the identity provider. Do not invent shared-secret, API-key, or custom HMAC schemes for new Azure APIs.
- Service-level
@useAuthdeclares the default auth. Per-operation@useAuth(NoAuth)overrides are reserved for explicit anonymous endpoints such as a public OpenAPI document endpoint or a liveness probe; each one needs a documented justification. - Never embed secrets, connection strings, or tokens in URLs, path segments, query parameters, or error messages.
Avoid Legacy.* templates
The Azure.Core.Legacy.* operation templates exist for migrating older patterns; they ignore @parentResource and miss some traits. Default to modern Operations.Resource* templates for new work.
Azure data-plane review checks
@useAuthis present and scopes use the<resource-uri>/.defaultpattern.- API is versioned from the first public version.
- Resource keys have
@key,@visibility(Lifecycle.Read),@maxLength, and@pattern. - Standard operation templates are used before custom operations.
- Mutating create operations support repeatability where appropriate.
- Conditional requests are supported where concurrency matters.
- LROs have a status monitor operation and final-state behavior is clear.
- No explicit
@operationIdin Azure specs. - No open-ended
@formatwhen a concrete scalar exists.