@massivescale/tsp-api-docs
v1.0.0
Published
TypeSpec emitter for generating API reference documentation in Markdown for Azure DevOps Wiki, GitHub, and DocFx.
Maintainers
Readme
@massivescale/tsp-api-docs
TypeSpec emitter for generating API reference documentation in Markdown.
Supports multiple output formats targeting Azure DevOps Wiki, GitHub, and DocFx.
Features
- Emits Markdown docs with per-service overview pages, per-operation pages, and per-type pages (models, enums, unions, scalars). Type pages cover payload data only — response models and metadata-only models are documented on operation pages instead. See Which types get pages.
- Three output formats:
azure-devops(default),github, anddocfx. - Automatically formats Markdown tables with aligned columns.
- External Handlebars templates — override any built-in template with a custom
.hbsfile. - Optional root service index page.
- Versioned API support via
@typespec/versioning. - Optional
api-nameprefix for versioned file/folder slugs (e.g.my-api-v1-0/). - Configurable
route-prefixwith{version}token substitution for HTTP request lines (default:api/{version}). - Response headers documented per status code, and included in example responses.
- Query, header, path, and cookie parameters documented by their HTTP wire names (e.g.
$expand,If-Match). - Request body examples automatically omit read-only and immutable properties based on HTTP verb visibility.
- Type names rendered exactly as defined — no CamelCase splitting in page titles.
- Related-methods table on type pages lists only operations that directly address the type — return it (including through a response model such as
EntityResponse<Widget>), accept it as a parameter or body (including throughMergePatchUpdate<Widget>), or use a direct array/record/union of it. Operations that reference the type only through nested properties are excluded, except for one-level wrapper request models that are direct operation parameters (e.g.CreateWidgetRequest { widget: Widget }). Types decorated with@errorare never treated as addressable entities and have no Methods section. - DocFx
toc.ymloutput is YAML-safe — service and page titles containing colons or other special characters are properly quoted.
Requirements
- Node.js 22 or later
@typespec/compiler^1.15.0@typespec/http^1.15.0@typespec/openapi^1.15.0@typespec/versioning>=0.85.0 <1.0.0
Install
npm install --save-dev @massivescale/tsp-api-docsPeer dependencies must also be present in your project:
npm install --save-dev @typespec/compiler @typespec/http @typespec/openapi @typespec/versioningEmitter Usage
Add the emitter to your TypeSpec config and set options as needed.
emit:
- "@massivescale/tsp-api-docs"
options:
"@massivescale/tsp-api-docs":
emitter-output-dir: ./tsp-output
format: azure-devopsOptions
All options are set under the "@massivescale/tsp-api-docs" key in tspconfig.yaml:
options:
"@massivescale/tsp-api-docs":
format: azure-devops
api-name: "My API"
route-prefix: "api/{version}"General options
| Option | Type | Default | Description |
| ------------------------- | ------------------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| api-name | string | — | API name prefix for file/folder slugs. See API Name below. |
| clean-output-dir | boolean | true | Before emitting, deletes the entire emitter-output-dir for azure-devops and github formats. For docfx, enumerates and removes only non-project files, preserving docfx.json. Do not point emitter-output-dir at a directory that contains unrelated files when this is true. Set to false to skip cleaning entirely. |
| emit-project-files | boolean | true | When true, emits project/configuration files (e.g. docfx.json). Set to false to emit documentation files only. |
| emit-relation-diagram | boolean | false | When true, emits a relation-diagram.md containing a Mermaid ER diagram of all types for each service. |
| emitter-output-dir | string | ./tsp-output | Output directory for generated files. |
| format | "azure-devops" | "github" | "docfx" | "azure-devops" | Output format. See Output Formats below. |
| overwrite-project-files | boolean | false | When false, project files are only written if they do not already exist. Set to true to always overwrite them. |
| page-title-prefix | string | — | Fallback title prefix used when the service has no explicit title. |
| render-service-index | boolean | false | Emit a root index page listing all services. |
| route-prefix | string | api/{version} | Prefix prepended to HTTP request paths. Supports {version} token substitution. See Route Prefix below. |
| templates | TemplateOverrides | — | Per-template path overrides for custom Handlebars templates. See Custom Templates below. |
DocFx options
DocFx-specific options are nested under a docfx: key, which itself lives under "@massivescale/tsp-api-docs". These are only applied when format: docfx.
options:
"@massivescale/tsp-api-docs":
format: docfx
docfx:
app-name: "My API"
app-title: "My API Reference"
enable-pdf: true
enable-pdf-toc-page: true
theme:
- default
- modern| Option | Type | Default | Description |
| --------------------- | ---------- | ----------------------- | ------------------------------------------------------------------------------------------------ |
| app-name | string | api-name or "API" | Application name in the site header. Sets globalMetadata._appName in docfx.json. |
| app-title | string | api-name or "API" | Application title in the browser <title> tag. Sets globalMetadata._appTitle in docfx.json. |
| emit-json | boolean | true | When false, suppresses docfx.json output while leaving documentation pages unaffected. |
| enable-pdf | boolean | true | Sets globalMetadata.pdf in docfx.json, enabling PDF generation in the DocFx build. |
| enable-pdf-toc-page | boolean | true | Sets globalMetadata.pdfTocPage in docfx.json, enabling a PDF table-of-contents page. |
| theme | string[] | ["default", "modern"] | Template names applied to the build.template array in docfx.json. |
Which types get pages
A type page (and a row in the Types index) describes data: something a client sends or receives as an HTTP body. The emitter decides this by asking @typespec/http how each operation's request and response bodies resolve, rather than documenting every model in the namespace. This is the same rule @massivescale/tsp-aspnetcore-api uses to decide which models get a C# class, and it matches how @typespec/openapi3 builds schemas.
TypeSpec calls @statusCode, @header, @cookie, @query, and @path properties metadata. Two kinds of model describe HTTP details rather than data:
- A metadata-only model has only metadata properties, e.g.
model ETagHeader { @header("ETag") etag: string; },model UpdatedResponse { ...NoContentResponse; ...ETagHeader; }, or a spread parameter model likemodel IfMatchHeader { @header("If-Match") ifMatch?: string; }. It never gets a type page. Its properties show up on the operation pages that use it, as request headers, response headers, or parameters. - A response model is used as an operation's return type and carries metadata. Only its body is data:
- Explicit body. With an
@bodyor@bodyRootproperty (model EntityResponse<T> { ...OkResponse; ...ETagHeader; @body body: T; }, or@error model NotFoundError { ...NotFoundResponse; @body body: Error; }), the response model gets no type page. Its body type (T,Error) does. The response model's@docstill appears as that status code's description on the operation page. - Implicit body. If a response model mixes metadata with plain properties and has no
@body, the plain properties form the body. The model gets a type page that lists only those plain properties.
- Explicit body. With an
model WidgetResult {
@header("ETag") etag: string; // metadata: shown as a response header on the operation page
name: string; // body: shown on the WidgetResult type page
}
@get read(): WidgetResult;Metadata never appears in a type's property table, JSON representation, or relation diagram entry, wherever the model is used. Specifically, @header, @cookie, and @statusCode properties are left out, as are properties typed as a metadata-only or explicit-body response model. @path and @query properties stay, because on a returned resource (model Widget { @path id: string; ... }) they are ordinary body data.
Reachability
When the service has HTTP operations, a model, union, or scalar gets a page only if an operation reaches it:
- a request body, a response body (as resolved above), or a parameter type;
- transitively, a property type, base model, array/record element, union variant, or
MergePatchUpdate<T>source of one of those; - every derived model of a reachable
@discriminatorbase. A derived model that is itself a response model (e.g.model OddPet extends Pet { kind: "odd"; @body body: Toy; }) still gets no page.
Enums always get a page, whether or not an operation uses them. Templated models (EntityResponse<T>) never get their own page; their body types do.
If a base model is metadata-only, a derived model's "Base type" points at the nearest base that does have a page (or is omitted).
When the service has no HTTP operations (a models-only library), every named model, enum, union, and scalar in the namespace gets a page, except metadata-only models and explicit-body response models.
Operation pages
Each operation page documents the HTTP request and every response:
- HTTP request — the verb and URI template, with the route prefix applied. Percent-encoded names are decoded so the line reads the way a client types it (
{?$expand}rather than{?%24expand}). - Path parameters, Optional query parameters, Request headers, and Request cookies — one table each, using the wire name from
@path,@query,@header, or@cookie(e.g.$expand,If-Match), not the TypeSpec property name. Empty path and cookie sections are omitted. Operations without HTTP metadata get a single Parameters table instead. - Request body — the body type, content types, and a JSON example.
- Response — what the caller receives on success (the 2xx body types, or
void), followed by one row per status code with its body type and description. Status codes show their standard reason phrase (e.g.409 Conflict,412 Precondition Failed). - Response headers — one table per status code that returns headers (name, type, required, summary).
- Examples — from
@opExample, or generated from the HTTP metadata. Example requests use wire names (?$expand=string), and example responses include response headers (ETag: string). When an@opExamplereturn value is a response model, its@bodyvalue becomes the JSON body and its header values become the header lines.
Output Formats
azure-devops (default)
Generates structure compatible with Azure DevOps Wiki. Index pages sit beside the folder they describe — Azure DevOps Wiki associates a page with a folder when the page is at the same level as (not inside) the folder.
tsp-output/
README.md # root index (render-service-index: true)
<service-slug>.md # overview — beside the service folder
<service-slug>/
api.md # operations index — beside api/
api/
<Operation>.md
resources.md # types index — beside resources/
resources/
<Type>.mdgithub
Generates structure compatible with GitHub rendering. Each folder's landing page is README.md, which GitHub renders automatically when browsing directories.
tsp-output/
README.md # root index (render-service-index: true)
<service-slug>/
README.md # overview
api/
README.md # sub-folder index
<Operation>.md
resources/
README.md # sub-folder index
<Type>.mddocfx
Generates structure compatible with a DocFx project. Each service folder contains an index.md overview and a toc.yml table of contents for navigation. When render-service-index: true, a root toc.yml is also emitted. A docfx.json project configuration file is emitted at the output root by default (see DocFx Project Files).
tsp-output/
docfx.json # project config (emit-project-files: true)
index.md # root index (render-service-index: true)
toc.yml # root TOC (render-service-index: true)
<service-slug>/
index.md # overview
toc.yml # service TOC (Overview / API / Resources)
relation-diagram.md # Mermaid ER diagram (emit-relation-diagram: true)
api/
<Operation>.md
resources/
<Type>.mdDocFx Project Files
When format: docfx is used, the emitter writes a docfx.json configuration file at the output root. This file is only written if it does not already exist (controlled by overwrite-project-files). To skip it entirely, set emit-project-files: false or docfx: { emit-json: false }.
The following options populate fields in the generated docfx.json:
options:
"@massivescale/tsp-api-docs":
format: docfx
docfx:
app-name: "My API" # globalMetadata._appName
app-title: "My API Docs" # globalMetadata._appTitle
enable-pdf: true # globalMetadata.pdf
enable-pdf-toc-page: true # globalMetadata.pdfTocPage
theme:
- default
- my-custom-themeRelation Diagram
When emit-relation-diagram: true, a relation-diagram.md file is emitted in each service folder. It contains a Mermaid erDiagram block that shows every type that has a page (see Which types get pages) and their relationships:
options:
"@massivescale/tsp-api-docs":
emit-relation-diagram: trueThe code fence syntax is automatically selected per format — Azure DevOps Wiki uses :::mermaid / ::: while GitHub and DocFx use ```mermaid / ```.
The diagram includes:
- Models as entities with their properties and types
- Enums as entities with their members
- Unions as entities with their named variants
- Relationship lines between models that reference other service types
API Name
The api-name option provides a consistent prefix for file and folder slugs, which is useful when the TypeSpec service title differs from how you want the output organized.
options:
"@massivescale/tsp-api-docs":
api-name: "My Awesome API"Non-versioned service — the slug is derived from api-name instead of the service title:
my-awesome-api.md
my-awesome-api/
api/Get-Widget.md
resources/Widget.mdVersioned service — the slug is <api-name> <version> slugified:
my-awesome-api-v1-0.md # version 1.0 folder and overview
my-awesome-api-v2-0.md # version 2.0 folder and overviewWhen render-service-index is also enabled, versioned entries are grouped under the api-name heading in the index.
The combined label (e.g. "My Awesome API v1.0") is also exposed to all templates as the {{apiName}} variable so custom templates can reference it.
Route Prefix
The route-prefix option controls the path prefix shown in HTTP request lines on operation pages. It supports a {version} token that is substituted with the actual API version value for versioned services.
The default value is api/{version}:
options:
"@massivescale/tsp-api-docs":
route-prefix: "api/{version}"Non-versioned service — {version} resolves to an empty string, giving just api:
GET /api/widgets/{id}Versioned service at v1.0 — {version} is substituted with 1.0:
GET /api/1.0/widgets/{id}To use a custom prefix pattern:
options:
"@massivescale/tsp-api-docs":
route-prefix: "v{version}/rest"To emit bare paths with no prefix, set route-prefix to an empty string:
options:
"@massivescale/tsp-api-docs":
route-prefix: ""Custom Templates
Any of the built-in Handlebars templates can be replaced by specifying a path to a custom .hbs file. Paths are resolved relative to the directory where tsp compile is run.
options:
"@massivescale/tsp-api-docs":
templates:
overview: ./my-templates/overview.md.hbs
operation: ./my-templates/operation.md.hbsOnly the templates you list are overridden; all others continue to use the built-in defaults.
Available template keys
| Key | Built-in file | Renders |
| ------------------ | ----------------------------------- | ----------------------------------------------------- |
| overview | templates/overview.md.hbs | Service overview page |
| operation | templates/operation.md.hbs | Individual operation reference page |
| type | templates/type.md.hbs | Type page (models, unions, scalars) |
| enum | templates/enum.md.hbs | Enum type page |
| service-index | templates/service-index.md.hbs | Root service index |
| operations-index | templates/operations-index.md.hbs | api/ sub-folder index |
| types-index | templates/types-index.md.hbs | resources/ sub-folder index |
| docfx-project | templates/docfx.json.hbs | DocFx docfx.json project config (docfx format only) |
Template variables
All templates receive the standard view model for their page type. The following variables are common across all page templates:
| Variable | Description |
| -------------- | --------------------------------------------------------------------------------------------------- |
| title | Page title (service label, operation name, or type name). |
| summary | Doc summary from the TypeSpec @summary decorator, if present. |
| versionLabel | The API version string (e.g. "v1.0"), present only on versioned services. |
| apiName | The full api-name-prefixed label (e.g. "My Awesome API v1.0"). undefined when not configured. |
Refer to the built-in templates in templates/ for the full variable list for each page type.
The operation template also receives pathParameters, optionalQueryParameters, requestHeaders, and requestCookies (each a list of name/type/requiredLabel/summaryOrFallback, keyed by wire name), parameters (every TypeSpec parameter by TypeSpec name), and responses, where each response has statusCode, type, description, and headers. responseHeaders lists every response header across all status codes.
Contributing
git clone https://github.com/MassiveScale/tsp-api-docs.git
cd tsp-api-docs
npm install
npm testKey source files: src/emitter.ts (core), src/lib.ts (option schema), src/templates.ts (template loader). Example TypeSpec projects are in examples/.
