OpenAPI
Generate OpenAPI document from the contract.
createOpenApiDocument() generates OpenAPI document object from HTTP
route declarations.
import { createOpenApiDocument } from "@rest-rpc/core";
const document = createOpenApiDocument(api, {
info: {
title: "Todo API",
version: "1.0.0",
},
servers: [{ url: "https://api.example.com" }],
schemaConverter,
});
What Is Included
OpenAPI generation includes HTTP routes.
It maps:
- route methods and paths
- path params
- query params
- request headers
- request bodies
- status-keyed responses
- custom body content types
- route
openApimetadata - shared
commonOpenApimetadata
WebSocket routes are skipped as there is no faithful mapping to OpenAPI.
Schema Conversion
Standard Schema defines validation behavior, not JSON Schema conversion.
OpenAPI generation needs a project-provided schemaConverter.
import {
createOpenApiDocument,
isTypeOnlySchema,
looseJsonSchema,
} from "@rest-rpc/core";
import { z } from "zod";
const document = createOpenApiDocument(api, {
info: {
title: "Todo API",
version: "1.0.0",
},
schemaConverter: (schema, { io }) => {
if (isTypeOnlySchema(schema)) {
return looseJsonSchema(schema);
}
if (schema["~standard"].vendor === "zod") {
return z.toJSONSchema(schema as z.ZodType, { io });
}
return looseJsonSchema(schema);
},
});
Use a precise converter when your schema library supports JSON Schema output.
Use looseJsonSchema() when a loose OpenAPI shape is acceptable. If you have single validation library, you can just pass the library’s JSON Schema converter directly.
Route Metadata
Use openApi on a route for operation metadata.
create: {
method: "POST",
path: "/todos",
body: {
title: z.string().min(1),
},
response: todoSchema,
openApi: {
summary: "Create a todo",
operationId: "createTodo",
responses: {
201: {
description: "Todo created.",
},
},
},
}
Status-keyed responses produce status-keyed OpenAPI responses.
create: {
method: "POST",
path: "/todos",
body: {
title: z.string().min(1),
},
responses: {
201: todoSchema,
409: z.object({
code: z.literal("TODO_ALREADY_EXISTS"),
}),
},
openApi: {
summary: "Create a todo",
operationId: "createTodo",
responses: {
201: {
description: "Todo created.",
},
409: {
description: "A todo with the same title already exists.",
},
},
},
}
openApi.responses only affects generated OpenAPI output. It does not change
rest-rpc runtime behavior, handler return types, client response types,
validation, or server headers. Metadata for statuses that a route does not
declare is skipped.
openApi: {
responses: {
200: {
headers: {
"x-request-id": {
description: "Request correlation id.",
schema: z.string(),
},
"x-rate-limit": z.number(),
},
},
},
}
Use route response headers instead when a handler-owned header is part of the
typed rest-rpc contract.
Shared Metadata
Use commonOpenApi on router() options to apply metadata across a tree.
export const api = router(routes, {
commonOpenApi: {
tags: ["Todos"],
security: [{ bearerAuth: [] }],
responses: {
200: {
headers: {
"x-request-id": z.string(),
},
},
401: {
description: "Authentication is required.",
},
},
},
});
commonOpenApi.responses is merged into each route’s openApi.responses.
Route-level OpenAPI metadata overrides common metadata. Typed headers declared
on a route response override OpenAPI-only headers with the same name in generated
OpenAPI.
Transform Hooks
Use transformOperation and transformDocument when generated output needs
project-specific changes.
const document = createOpenApiDocument(api, {
info: {
title: "Todo API",
version: "1.0.0",
},
schemaConverter,
transformOperation: ({ route, operation }) => {
if (route.metadata.auth === "required") {
return {
...operation,
security: [{ bearerAuth: [] }],
};
}
return operation;
},
});