TypeSpec for OpenAPI Developers
This guide introduces TypeSpec using concepts familiar to developers who build or use API definitions in OpenAPI v2 or v3.
In many cases, this will also describe how the typespec-autorest and openapi3 emitters translate TypeSpec designs into OpenAPI.
The document is organized around the features of an OpenAPI v2 or v3 definition. The idea is that if you know how to describe some API feature in OpenAPI, you can just navigate to the section of this document for that feature.
Data Types
Section titled âData TypesâIn OpenAPI v2/v3, data types are specified using the type and format fields in a schema.
The TypeSpec equivalents of OpenAPI data types are the TypeSpec primitive types or built-in models.
type and format
Section titled âtype and formatâThe following table shows how common OpenAPI types map to TypeSpec types:
type: | format: | TypeSpec type | Notes |
|---|---|---|---|
integer | int32 | int32 | |
integer | int64 | int64 | |
number | float | float32 | |
number | double | float64 | |
string | string | ||
string | byte | bytes | for content-type == âapplication/jsonâ or âtext/plainâ |
string | binary | bytes | for âbinaryâ content types, e.g. âapplication/octet-streamâ, âimage/jpegâ |
string | date | plainDate | |
string | date-time | utcDateTime | RFC 3339 date in coordinated universal time (UTC) |
string | date-time | offsetDateTime | RFC 3339 date with offset |
string | password | @secret string | |
boolean | boolean |
You can also define a property with no type specified using the TypeSpec unknown type.
model Example { /** This property has no `type` defined. */ noType?: unknown;}OpenAPI allows any string as a format, and there is a registry of common formats. TypeSpec supports some of these directly.
type: | format: | TypeSpec type | Notes |
|---|---|---|---|
number | decimal | decimal | |
number | double-int | safeint | |
integer | int8 | int8 | |
integer | int16 | int16 | |
integer | uint8 | uint8 | |
string | uri | url |
For formats that are not supported directly, you can use the built-in @format decorator to specify
the format explicitly.
JSON Schema assertions
Section titled âJSON Schema assertionsâOpenAPI supports a variety of âassertionsâ that can be used to further restrict the values allowed for a data type. These are actually borrowed into OpenAPI from JSON Schema.
For type: integer and type: number data types:
| OpenAPI/JSON Schema keyword | TypeSpec construct | Notes |
|---|---|---|
minimum: value | @minValue(value) decorator | |
maximum: value | @maxValue(value) decorator |
For type: string data types:
| OpenAPI/JSON Schema keyword | TypeSpec construct | Notes |
|---|---|---|
minLength: value | @minLength(value) decorator | |
maxLength: value | @maxLength(value) decorator | |
pattern: regex | @pattern(regex) decorator |
For type: array data types:
| OpenAPI/JSON Schema keyword | TypeSpec construct | Notes |
|---|---|---|
minItems: value | @minItems(value) decorator | |
maxItems: value | @maxItems(value) decorator |
There are two ways to define an enum data type in TypeSpec. One is with the TypeSpec enum statement, e.g.:
enum Color { "red", "blue", "green",}Another is to use the union operation to define the enum values inline, e.g.:
model Example { size?: "small" | "medium" | "large" | "x-large";}default
Section titled âdefaultâA model property that specifies a default value using â=â will produce a default field in the schema for this property.
model Example { answer?: int32 = 42; color?: string = "purple";}produces
answer: type: integer format: int32 default: 42color: type: string default: purpleHost / BasePath / Servers
Section titled âHost / BasePath / ServersâIn OpenAPI v2, the host and basePath fields at the top-level of the API definition combine to form the base URL for the API. The paths defined in the paths object are appended to this base URL to form the absolute URL for an operation.
In OpenAPI v3, the top-level servers field specifies an array of server objects [v3] with a base URL, which may be parameterized, to which the operation path is appended.
In TypeSpec, the host in OpenAPI v2 or servers in OpenAPI v3 can be specified with the @server decorator
on the namespace (from @typespec/http library). You can use this decorator multiple times to specify multiple servers.
Paths Object
Section titled âPaths ObjectâIn OpenAPI, the paths object [v2, v3] is the top-level structure for defining the operations of the API, organized by the âpathâ for the operation.
In TypeSpec, you can specify the path for a namespace, interface, or operation using the @route decorator.
When the value of the @route decorator contains path parameters, operations within the scope of the decorator
must declare parameters with the same name and type. If an operation declares a path parameter that is not present
in the route, this defines a new path that is the value from the @route decorator with the path parameter appended.
@route("/pets")namespace Pets { op create(@body pet: Pet): Pet; // uses path "/pets" op read(@path petId: int32): Pet; // uses path "/pets/{petId}"}When the @route decorator is used within a namespace or interface that also has a @route decorator, the path is
obtained by concatenating the routes.
@route("/widgets")namespace Widgets { // widgets operations
@route("/{id}/parts") namespace Parts { op list(@path id: string): Part[] | Error; // uses path "/widgets/{id}/parts" }}Path Item Object
Section titled âPath Item ObjectâIn OpenAPI, a path item object [v2, v3] describes the operations available on a single path. A path may have at most one get, put, post, patch, delete, or head operation.
In TypeSpec, operations are defined within a namespace or interface with a syntax similar to typescript functions.
The HTTP method for an operation can be specified explicitly using a decorator: @get, @put, @post, @patch, @delete, or @head.
If an HTTP method decorator is not specified then the default is post if there is a body and get otherwise.
@tag("Gadgets")@route("/gadgets")namespace Gadgets { op create(@body gadget: Gadget): Gadget | Error; // uses "post" method op read(@path id: string): Gadget | Error; // uses "get" method}Other path item fields:
OpenAPI pathItem field | TypeSpec construct | Notes |
|---|---|---|
summary | Not currently supported. | |
description | Not currently supported. | |
parameters | Not currently supported. |
Operation Object
Section titled âOperation ObjectâIn OpenAPI, an operation object [v2, v3] describes an operation.
The fields in an OpenAPI operation object are specified with the following TypeSpec constructs:
OpenAPI operation field | TypeSpec construct | Notes |
|---|---|---|
tags | @tag decorator | |
summary | @summary decorator | |
description | @doc decorator or doc comment | |
externalDocs | @externalDocs decorator | |
operationId | operation name or @operationId decorator | |
parameters | op parameter list | see Parameter Object |
requestBody | parameter with @body decorator | see Request Body Object |
responses | op return type(s) | see Responses Object |
callbacks | Not currently supported. | |
deprecated | #deprecated directive | |
security | see Security Schemes Object. | |
servers | @server decorator | Can be specified multiple times. |
Tags can be specified using the @tag decorator on an operation.
The @tag decorator can also be used on a namespace or interface to specify tags for all operations within the namespace or interface.
Tags are additive, so tags specified on an operation are added to the tags specified on the namespace or interface.
The @tag decorator can be used multiple times to specify multiple tags on an operation, namespace, or interface.
Description
Section titled âDescriptionâUse the @doc decorator to specify a description for an operation. The value of the @doc decorator can be a multi-line string
and can contain markdown formatting.
@doc(""" Get status info for the service. The status includes the current version of the service. The status value may be one of: - `ok`: the service is operating normally - `degraded`: the service is operating in a degraded state - `down`: the service is not operating """)@tag("Status")@route("/status")@getop status(): string;You can also use a âdoc commentâ to specify the description for an operation. A doc comment is a comment that begins with /**.
Doc comments may be spread across multiple lines and may contain markdown formatting.
/** * Get health info for the service. * The health includes the current version of the service. * The health value may be one of: * - `ok`: the service is operating normally * - `degraded`: the service is operating in a degraded state * - `down`: the service is not operating */@tag("Health")@route("/health")@getop health(): string;See documentation doc for more information.
operationId
Section titled âoperationIdâYou can specify the operationId for an operation using the @operationId decorator.
When the @operationId decorator is not specified, the operationId is generated from the operation name.
For an operation defined in the top-level namespace, the operationId is the just operation name.
If the operation is defined within a inner namespace or interface, then the operationId is
prefixed with the name of the innermost namespace or interface name.
Note: this approach will generally produce unique operationIds, as required by OpenAPI, but it is possible to create duplicate operationIds.
Parameter Object
Section titled âParameter ObjectâIn OpenAPI, a parameter object [v2, v3] describes a single operation parameter.
The following fields of a parameter object are common to both OpenAPI v2 and v3:
OpenAPI parameter field | TypeSpec construct | Notes |
|---|---|---|
name | parameter name | |
in | decorator | @query, @path, @header, @body |
description | /** */ or @doc decorator | |
required | from parameter âoptionalityâ | a â?â following the parameter name indicates it is optional (required: false), otherwise it is required (required: true) |
allowEmptyValue | Not supported, this field is NOT RECOMMENDED in OpenAPI. |
OpenAPI v2
Section titled âOpenAPI v2âThe following fields of a parameter object are specific to OpenAPI v2:
OpenAPI v2 parameter field | TypeSpec construct | Notes |
|---|---|---|
type | parameter type | see Data Types |
collectionFormat | uri template in @route or expode, style |
Collection Formats
Section titled âCollection FormatsâIn OpenAPI v2, the collectionFormat field of a query or header parameter object specifies how multiple values are delimited.
You can use a combination of the @encode decorator and explode field of the @query or @header decorator to specify the collection format.
op read( @query csv?: string[], // equivalent to collectionFormat: csv @query(#{ explode: false }) csvExplicit?: string[], // equivalent to collectionFormat: csv @query(#{ explode: true }) multi?: string[], // equivalent to collectionFormat: multi @query @encode(ArrayEncoding.pipeDelimited) pipes?: string[], // equivalent to collectionFormat: pipes): Widget | Error;OpenAPI v3
Section titled âOpenAPI v3âThe following fields of a parameter object are specific to OpenAPI v3:
OpenAPI v3 parameter field | TypeSpec construct | Notes |
|---|---|---|
style | explode parameter on @query or @header and @encode | |
explode | explode parameter on @query or @header | |
schema | parameter schema | see Schema Object |
deprecated | #deprecated directive. | |
example | @example on data types (e.g. models, scalars), @opExample to describe in-line parameters of operations | Open API 3.0 only supports a single example when using @example on data types |
examples | @example on data types (e.g. models, scalars), @opExample to describe parameters/return types of operations | Open API 3.1 always outputs examples instead of example. |
content | Not currently supported. |
Request Body Object (OAS3)
Section titled âRequest Body Object (OAS3)âIn OpenAPI v3, the operation request body is defined with a Request Body object rather than as a parameter.
An OpenAPI v3 Request Body object corresponds to a TypeSpec op parameter with the @body decorator.
OpenAPI requestBody field | TypeSpec construct | Notes |
|---|---|---|
description | @doc decorator | |
required | parameter âoptionalityâ | a â?â following the parameter name indicates it is optional (required: false), otherwise it is required (required: true) |
content | @body parameter type |
The media type of the request body is specified with a content-type header. If content-type has multiple values
then content will have one entry for each value.
@put op uploadImage(@header contentType: "image/png", @body image: bytes): void;@post op analyze( @header contentType: "application/octet-stream" | "application/pdf" | "image/jpeg", @body image: bytes,): string | Error;To get multiple content entries with different schemas (say one structured and one binary),
you need to define two separate operations that share the same path and method.
You do with with the @sharedRoute decorator.
@route(":process")namespace Process { @sharedRoute @post op process(...Widget): Widget | Error;
model CsvBody { @header contentType: "text/csv"; @body _: string; } @sharedRoute @post op process2(...CsvBody): Widget | Error;}Responses Object
Section titled âResponses ObjectâIn OpenAPI, the responses object [v2, v3] specifies the possible responses for an operation. The responses object maps a HTTP response code to the expected response.
In TypeSpec, operation responses are defined by the return types of the op. The status code for a response can be specified as a property in the return type with the @statusCode decorator. The value of the property with the @statusCode decorator should be an HTTP status code or union of status codes. When the value is a union of status codes, a response is generated for each status code in the union.
If a return type does not contain a statusCode, the default is 200 except for void which defaults to 204.
To get the default response, specify the @error decorator on the return type model.
@get op read(@path id: string): Widget; // has "200" response@delete op delete(@path id: string): void; // has "204" response// has "200" and "201" response@put op create(@body widget: Widget): { @statusCode _: "200" | "201"; @body body: Widget;};// has "200" and "default" response@post op update(@body widget: Widget): Widget | Error;The TypeSpec.Http package also defines several standard response types.
| HTTP Status Code | TypeSpec construct |
|---|---|
200 | OkResponse |
201 | CreatedResponse |
202 | AcceptedResponse |
204 | NoContentResponse |
301 | MovedResponse |
304 | NotModifiedResponse |
401 | UnauthorizedResponse |
404 | NotFoundResponse |
409 | ConflictResponse |
You can intersect these standard response types with your own response types.
// has "200", '409', and "default" responses@post op update(@body widget: Widget): Widget | (ConflictResponse & Error) | Error;Response Object
Section titled âResponse ObjectâIn OpenAPI, a response object [v2, v3] describes a single response for an operation. The structure of the response object changed significantly from OpenAPI v2 to OpenAPI v3, but there are many elements common to both.
The fields in an OpenAPI response object are specified with the following TypeSpec constructs:
OpenAPI response field | TypeSpec construct | Notes |
|---|---|---|
description | @doc decorator | |
headers | fields in the return type with @header decorator | Required or optional based on optionality of field. |
schema (OAS2) | return type or type of `@bodyâ property | |
content (OAS3) | return type or type of `@bodyâ property | |
examples (OAS3) | @opExample to describe return types of operations | Supported on an operation. |
links (OAS3) | Not currently supported. |
@get op read(@path id: string): { /** the widget */ @body widget: Widget;
@header xRateLimitRemaining: int32; @header xRateLimitReset: int32;};The media type of the request body is specified with a content-type header. If content-type has multiple values
then content will have one entry for each value.
To get multiple content entries with different schemas, use a union type.
@tag("Response Content")@route("/response-content")namespace ResponseContent { @get op read(@path id: string): Widget | { @header contentType: "text/html"; @body _: string; } | { @header contentType: "image/jpeg"; @body _: bytes; };}Schema Object
Section titled âSchema ObjectâOpenAPI schemas are represented in TypeSpec by models. Models have any number of members and can extend and be composed with other models.
Models can be defined with the model statement and then referenced by name, which generally results in a $ref to a schema for the model in the definitions or components.schemas section of the OpenAPI document.
TypeSpec supports the âspreadâ operator (...), which copies the members of the source model into the target model.
But TypeSpec processes all spread transformations before emitters are invoked, so this form of reuse is not represented in the emitted OpenAPI.
The spread operation is useful if you want one or more properties to be present in several different models but in a standard fashion. For example:
model Legs { /** number of legs */ legs: int32;}
model Dog { name: string; ...Legs;}
model Cat { name: string; ...Legs;}
model Snake { name: string; // snakes have no legs}additionalProperties/unevaluatedProperties
Section titled âadditionalProperties/unevaluatedPropertiesâYou can generate a schema with additionalProperties with the TypeSpec Record construct.
Note: unevaluatedProperties is used instead of additionalProperties when emitting Open API 3.1 specs.
bar: Record<unknown>;is produced as
bar: type: object additionalProperties: {}To get a schema having both properties and additionalProperties, define a model that extends Record<unknown>.
model Bar extends Record<unknown> { bar?: string;}produces
Bar: type: object properties: bar: type: string additionalProperties: {}To define a schema with additionalProperties that has a specific type, use the Record construct with a type parameter.
bar: Record<string>;results in
bar: type: object additionalProperties: type: stringTo define a schema that does not allow any additional properties, use Record<never>.
bar: Record<never>;results in
bar: type: object additionalProperties: not: {}In the openapi3 emitter, schemas that donât define additionalProperties can automatically be set to additionalProperties: { not: {} } by setting the seal-object-schemas emitter option to true. This will not add additionalProperties to object schemas that are referenced in an allOf of another schema.
allOf and polymorphism using allOf
Section titled âallOf and polymorphism using allOfâTypeSpec supports single inheritance of models with the extends keyword. This construct can be used to produce an allOf with a single element (the parent schema) in OpenAPI. For example:
model Pet { name: string;}
model Cat extends Pet { meow: int32;}
model Dog extends Pet { bark: string;}TypeSpec does not currently provide a means to produce an allOf with more than one element â these are generally treated as âcompositionâ in code generators and thus better represented in TypeSpec with the spread operator.
Models with a @discriminator decorator can be extended to produce polymorphic schemas in either OpenAPI v2 or v3 using allOf.
This schema produced for the base model will be defined with a discriminator property and schemas for the child models will allOf the base schema and add additional properties.
For example:
@discriminator("kind")model Pet { name: string; weight?: float32;}model Cat extends Pet { kind: "cat"; meow?: int32;}model Dog extends Pet { kind: "dog"; bark?: string;}generates:
Cat: type: object properties: kind: type: string enum: - cat meow: type: integer format: int32 required: - kind allOf: - $ref: "#/components/schemas/Pet"Dog: type: object properties: kind: type: string enum: - dog bark: type: string required: - kind allOf: - $ref: "#/components/schemas/Pet"Pet: type: object properties: kind: type: string description: Discriminator property for Pet. name: type: string weight: type: number format: float discriminator: propertyName: kind mapping: cat: "#/components/schemas/Cat" dog: "#/components/schemas/Dog" required: - namePolymorphism using anyOf and oneOf (OAS3)
Section titled âPolymorphism using anyOf and oneOf (OAS3)âPolymorphism can also be represented in OpenAPI v3 with anyOf or oneOf constructs.
These can be represented in TypeSpec with a union type.
union Pet { cat: Cat, dog: Dog,}
model Cat { meow?: int32;}
model Dog { bark?: string;}generates a Pet schema with anyOf.
Pet: anyOf: - $ref: "#/components/schemas/Cat" - $ref: "#/components/schemas/Dog"The openapi emitter uses anyOf by default because the schemas may not be mutually exclusive.
But the @oneOf decorator of the OpenAPI library can be used to force the use of oneOf instead.
import "@typespec/openapi3";using OpenAPI;
@oneOfunion Pet { cat: Cat, dog: Dog,}produces:
Pet: oneOf: - $ref: "#/components/schemas/Cat" - $ref: "#/components/schemas/Dog"To make Pet a discriminated union, add the @discriminator decorator and add the discriminator property
with a string literal value to each of the child schemas.
View the @discriminated documentation to learn how to further customize what is emitted.
@discriminated(#{ envelope: "none" })union Pet { cat: Cat, dog: Dog,}model Cat { kind: "cat"; meow?: int32;}model Dog { kind: "dog"; bark?: string;}results in the following schema for Pet:
Pet: oneOf: - $ref: "#/components/schemas/Cat" - $ref: "#/components/schemas/Dog" discriminator: propertyName: kind mapping: cat: "#/components/schemas/Cat" dog: "#/components/schemas/Dog"definitions / components
Section titled âdefinitions / componentsâOpenAPI supports reuse of schemas, parameters, responses, and other elements with the definitions (OAS2) or components (OAS3) section of an OpenAPI definition.
Referencing a model by name (not with âspreadâ), as an op parameter or return type or as the type of a property in another model, generally results in a $ref to a schema for the model in the definitions or components.schemas section of the OpenAPI document.
Reusable parameters can be defined as members of a model and then incorporated into an operation parameter list using the spread operator. For example:
model PetId { @path petId: int32;}
namespace Pets { op read(...PetId): Pet | Error;}results in a $ref to the named parameter PetId in either parameters or components.parameters.
Info Object
Section titled âInfo ObjectâIn OpenAPI, the info object [v2, v3] contains metadata about the API such as a title, description, license, and version.
In TypeSpec this information is specified with decorators on the namespace.
OpenAPI info field | TypeSpec decorator | Notes |
|---|---|---|
title | @service(#{title: } | TypeSpec built-in decorator |
description | @doc | TypeSpec built-in decorator |
version | @info | |
license | @info | |
contact | @info |
/** The Contoso Widget Service provides access to the Contoso Widget API. */@service(#{ title: "Widget Service" })@info(#{ contact: #{ name: "API Support", email: "contact@contoso.com" }, license: #{ name: "Apache 2.0", url: "https://www.apache.org/licenses/LICENSE-2.0.html" },})namespace DemoService;Consumes / Produces (OAS2)
Section titled âConsumes / Produces (OAS2)âIn OpenAPI v2, the top-level consumes and produces fields specify a list of MIME types an operation can consume / produce
when not overridden by a consumes or produces on an individual operation.
The typespec-autorest emitter previously supported @produces and @consumes decorators on a namespace, but these are deprecated
in favor of explicit content-type and accept header properties in request and response bodies.
securityDefinitions / securitySchemes Object
Section titled âsecurityDefinitions / securitySchemes ObjectâUse the @useAuth decorator from the `@typespec/restâ library
using Http;@useAuth(OAuth2Auth<["read", "write"]>)namespace MyService;Specification Extensions
Section titled âSpecification ExtensionsâYou can add arbitrary specification extensions (âx-â properties) to a model or an operation with the @extension decorator.
For example:
namespace Pets { @extension("x-streaming-operation", true) op read(...PetId): Pet | Error;}OpenAPI decorators that map directly to an object in the openapi document also allow to provide extension.
@tagMetadata
@tagMetadata("my-tag", #{ description: "My tag", `x-custom`: "custom value",})@info
@info(#{ version: 1.1.0, `x-custom`: "custom value",})