OpenAPI and API docs
@dotpress/openapi builds an OpenAPI 3.1 document from the routes of your app, and serves an interactive API reference with Scalar. No annotation: the paths, parameters, bodies and responses come from the Zod schemas of the routes, and the security from their middlewares. The documentation cannot drift from the code.
The document is generated once, when the app is created.
What is documented
Request schemas are documented as the client sends them (z.coerce.number() is a number, a field with a default is optional), response schemas as the API returns them. Dates are ISO 8601 strings.
Describe the routes
meta.responses documents the responses the schemas do not describe: errors returned by the handler, or bodies that are not JSON. An entry is a description, or an object with description, schema and contentType:
An entry replaces the generated response of the same status.
Reusable schemas
A Zod schema with an identifier is declared once in components/schemas, and referenced by the routes:
When the input and output of a schema differ (defaults, transforms, the additionalProperties: false of the output of objects), the request side gets an Input variant: Task and TaskInput. Two different schemas with the same identifier fail the generation. Recursive schemas need an identifier.
Security
A route is secured by the security schemes its middlewares declare, including group and global middlewares. requireAuth, requireRole() and requireClient() of @dotpress/auth declare bearerAuth, and the auth plugin declares the scheme: the API reference shows an Authorize button, and the secured routes a lock.
Your own middlewares declare theirs:
meta.security overrides the schemes of a route, and [] marks it as public. A scheme used by a route but not declared is reported as a warning, like a schema without JSON representation: the plugin logs them with its logger.
Options
The document lists every route of the API, with its parameters. For an internal API, protect it with middlewares: [requireRole('admin')], or serve it in development only.
The API reference
The page loads a pinned version of Scalar from jsDelivr, checked with Subresource Integrity. It is neutral by default (SCALAR_NEUTRAL_CONFIGURATION): no telemetry, no AI agent or MCP integration, no developer toolbar, no link to the Scalar client, system fonts, and no "Powered by Scalar" link. ui.configuration overrides these settings with the Scalar configuration, and its customCss is appended. cdnUrl hosts the script yourself. With a strict Content Security Policy, the page needs an inline script.
The document in CI
dotpress-openapi writes the document without starting the server, to commit it, check its changes in pull requests, or generate clients:
The module exports the app, or a function creating it; --export chooses the export (default by default). The routes are mounted, no server is started, and the app is closed afterwards (onClose hooks).
- With
openapiPlugin(), its document is used as is. Otherwise, the document is generated with--titleand--api-version, or the name and version of thepackage.jsonof the current directory. - Without
--out, the document is printed on the standard output. Warnings and errors go to the standard error, with a non-zero exit code on failure. - TypeScript modules are loaded with the type stripping of Node.js 22.18 or later. Otherwise, point to the compiled module, or run the command with
NODE_OPTIONS="--import tsx".
generateOpenApi(app, options) returns the document in code, with the options of the plugin except path, ui and middlewares, plus onWarning (console.warn by default):
Example
Every example serves its document and reference. The Express example shows reusable schemas and documented errors; the Auth example, secured routes.