> For the complete documentation index, see [llms.txt](https://voyzu.gitbook.io/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://voyzu.gitbook.io/docs/customising-and-extending-voyzu/api-jsdoc-guide.md).

# API JSDoc Guide

This guide explains how to document HTTP handler functions using the `@api` JSDoc convention, and how those comments are consumed by the OpenAPI generation script.

***

## Overview

Each HTTP handler function that should appear in the OpenAPI specification must have a JSDoc comment containing an `@api` tag followed by a JSON object. The script reads all `route.ts` files under `src/app/api`, resolves each exported HTTP method back to its handler function (which may live in a separate handlers file), and extracts the `@api` block from that function's JSDoc.

**Location:** The `@api` comment goes on the handler function, not the route wrapper.

```typescript
/**
 * @api
 * {
 *   "summary": "Get company",
 *   "description": "Retrieves a single company by its business code.",
 *   "pathParametersDto": "{ code: string }",
 *   "responses": {
 *     "200": {
 *       "description": "The requested company.",
 *       "dto": "CompanyResponseDto"
 *     },
 *     "404": {
 *       "description": "Company not found.",
 *       "dto": "EntityNotFoundErrorResponseDto"
 *     }
 *   }
 * }
 */
export async function handleGet(req: NextRequest, { params }) { ... }
```

> **Important:** The JSON block must be valid JSON. Even a single unescaped quote or trailing comma will silently suppress the entire operation from the generated document. If an operation is missing from the output, check for a JSON parse warning in the script output.

***

## Top-level fields

### `summary` *(string, optional)*

A short, human-readable title for the operation. Displayed as the operation heading in GitBook and other OpenAPI viewers.

```json
"summary": "Create company"
```

### `description` *(string, optional)*

A longer explanation of what the operation does, including any behaviour worth noting. Displayed as prose beneath the summary.

```json
"description": "Creates a new company. The code must be unique across all companies and follows the standard business code format."
```

> Avoid using embedded double-quotes inside the description string — they break JSON parsing. Use a colon or paraphrase instead (e.g. `Content-Disposition: attachment` not `"Content-Disposition": "attachment"`).

### `operationId` *(string, optional)*

An explicit operation identifier. If omitted the script does not generate one, which is fine for most use cases.

```json
"operationId": "createCompany"
```

### `tags` *(string\[], optional)*

Groups operations in the viewer. If omitted the script auto-derives the tag from the folder containing the handler file (e.g. a handler in `.../companies/api/` gets the tag `companies`). Explicit tags are rarely needed.

```json
"tags": ["companies"]
```

***

## Parameter fields

Parameters are described via DTOs. Each parameter field accepts either a **named DTO**, an **inline type literal**, or an **array of either** (see [DTO references](#dto-references) below).

### `pathParametersDto` *(string, optional)*

Describes URL path parameters (the `{code}` segments in the path). Each property becomes an OpenAPI `in: path` parameter. Path parameters are always treated as required regardless of the `?` optional marker.

For simple cases an inline literal is the most convenient form:

```json
"pathParametersDto": "{ code: string }"
```

For a sub-resource with two segments:

```json
"pathParametersDto": "{ code: string; mapId: number }"
```

### `queryParametersDto` *(string, optional)*

Describes query string parameters. Optional properties (`?`) become non-required parameters; required properties become required parameters.

Inline literal for a single search param:

```json
"queryParametersDto": "{ q: string }"
```

Named DTO when there are several documented parameters:

```json
"queryParametersDto": "GeneratePdfQueryDto"
```

### `headerParametersDto` *(string, optional)*

Describes custom request header parameters. Follows the same rules as `queryParametersDto`.

### `cookieParametersDto` *(string, optional)*

Describes cookie parameters. Follows the same rules as `queryParametersDto`.

***

## Request body fields

### `requestBodyDto` *(string, optional)*

The DTO describing the JSON request body. Always a named DTO (inline literals are supported but a named DTO is preferred for request bodies since they tend to be complex).

```json
"requestBodyDto": "CompanyCreateRequestDto"
```

### `requestBodyRequired` *(boolean, optional)*

Whether the request body is required. Defaults to `true` if omitted when `requestBodyDto` is set.

```json
"requestBodyRequired": true
```

### `requestBodyContentType` *(string, optional)*

The content type of the request body. Defaults to `application/json` if omitted.

```json
"requestBodyContentType": "multipart/form-data"
```

***

## `responses`

A map of HTTP status codes to response objects. Every operation should document at least a success response and any error responses that are part of the contract.

```json
"responses": {
  "200": { ... },
  "400": { ... },
  "404": { ... },
  "500": { ... }
}
```

### Response object fields

#### `description` *(string, optional)*

A human-readable explanation of when this response is returned.

```json
"description": "The created company."
```

#### `dto` *(string | JSON Schema object, optional)*

The shape of the response body. Accepts any form described in [DTO references](#dto-references).

For standard JSON responses, use a named DTO:

```json
"dto": "CompanyResponseDto"
```

For array responses:

```json
"dto": "CompanyResponseDto[]"
```

For binary file responses (PDF, CSV, etc.), pass a raw JSON Schema object instead of a DTO name:

```json
"dto": { "type": "string", "format": "binary" }
```

#### `contentType` *(string, optional)*

The content type of the response. Defaults to `application/json`. Use this for non-JSON responses:

```json
"contentType": "application/pdf"
```

***

## DTO references

Wherever a field accepts a DTO (parameters or responses), three forms are accepted:

### 1. Named DTO

A string matching the exact TypeScript interface name of a file under `src/dto/`. The script looks up the interface, generates a JSON Schema from it, and emits a `$ref` in the OpenAPI output.

```json
"requestBodyDto": "CompanyCreateRequestDto"
"dto": "CompanyResponseDto"
```

### 2. Array of a named DTO

Append `[]` to the interface name. The script emits an `array` schema with a `$ref` item.

```json
"dto": "CompanyResponseDto[]"
```

### 3. Inline TypeScript type literal

A TypeScript type literal string, used when the shape is simple and doesn't warrant its own DTO file. Mostly used for path and query parameters. The script parses this via ts-morph and generates an inline schema.

```json
"pathParametersDto": "{ code: string }",
"queryParametersDto": "{ q: string; limit?: number }"
```

### 4. Inline JSON Schema object

A raw JSON Schema object passed directly in the `dto` field of a response. Used when the response is not JSON (e.g. a binary file download). This is passed through to the OpenAPI document verbatim.

```json
"dto": { "type": "string", "format": "binary" }
```

***

## DTO property descriptions

JSDoc comments placed on **properties inside a DTO interface** flow through automatically into the OpenAPI parameter descriptions — no extra script work is needed.

When the script loads a DTO from `src/dto/`, it reads the JSDoc on each property and stores it alongside the property type. Wherever that DTO is used as a `queryParametersDto` or `pathParametersDto`, the description appears as the OpenAPI parameter `description`.

**Example DTO with JSDoc properties:**

```typescript
// src/dto/framework/pdf/generate-pdf.query.dto.ts
export interface GeneratePdfQueryDto {
  /**
   * Absolute internal application path to render as PDF
   * e.g. /global-reports/companies/printable
   */
  path: string;
  /**
   * PDF orientation
   * Defaults to landscape if omitted
   */
  orientation?: "portrait" | "landscape";
}
```

When `"queryParametersDto": "GeneratePdfQueryDto"` is used on any handler, both `path` and `orientation` will appear in the OpenAPI output with their descriptions. The description follows the DTO everywhere it is referenced — you write it once and it appears consistently.

> Property descriptions only flow through for **parameter** DTOs (`pathParametersDto`, `queryParametersDto`, `headerParametersDto`, `cookieParametersDto`). For request body and response DTOs, the full JSON Schema (including any JSDoc-derived descriptions) is emitted as a component schema and referenced via `$ref`.

***

## Complete examples

### Simple GET with path parameter

```typescript
/**
 * @api
 * {
 *   "summary": "Get company",
 *   "description": "Retrieves a single company by its business code.",
 *   "pathParametersDto": "{ code: string }",
 *   "responses": {
 *     "200": {
 *       "description": "The requested company.",
 *       "dto": "CompanyResponseDto"
 *     },
 *     "404": {
 *       "description": "Company not found.",
 *       "dto": "EntityNotFoundErrorResponseDto"
 *     },
 *     "500": {
 *       "description": "An unexpected server error occurred.",
 *       "dto": "InternalServerErrorResponseDto"
 *     }
 *   }
 * }
 */
```

### POST with request body

```typescript
/**
 * @api
 * {
 *   "summary": "Create company",
 *   "description": "Creates a new company record.",
 *   "requestBodyDto": "CompanyCreateRequestDto",
 *   "requestBodyRequired": true,
 *   "responses": {
 *     "201": {
 *       "description": "The created company.",
 *       "dto": "CompanyResponseDto"
 *     },
 *     "400": {
 *       "description": "Validation failed.",
 *       "dto": "InputValidationErrorResponseDto"
 *     },
 *     "500": {
 *       "description": "An unexpected server error occurred.",
 *       "dto": "InternalServerErrorResponseDto"
 *     }
 *   }
 * }
 */
```

### Query parameters from a named DTO

```typescript
/**
 * @api
 * {
 *   "summary": "Generate PDF",
 *   "description": "Generates a PDF of any internal page and returns it as a file download.",
 *   "queryParametersDto": "GeneratePdfQueryDto",
 *   "responses": {
 *     "200": {
 *       "contentType": "application/pdf",
 *       "description": "The generated PDF file as a binary download.",
 *       "dto": { "type": "string", "format": "binary" }
 *     },
 *     "400": {
 *       "description": "The path parameter is missing or invalid.",
 *       "dto": "InputValidationErrorResponseDto"
 *     },
 *     "500": {
 *       "description": "PDF generation failed.",
 *       "dto": "InternalServerErrorResponseDto"
 *     }
 *   }
 * }
 */
```

***

## The generate-openapi script

**Location:** `scripts/framework/generate-openapi.ts`

**Run:**

```bash
npx tsx scripts/framework/generate-openapi.ts
```

**Output:** `public/openapi.json`

### What the script does

1. **Loads DTOs** — scans all `.ts` files under `src/dto/` recursively. For each exported interface it extracts the property map (including JSDoc descriptions) and generates a full JSON Schema using `ts-json-schema-generator`. All schemas are collected into the OpenAPI `components/schemas` section.
2. **Finds route files** — recursively scans `src/app/api/` for `route.ts` files. The file path is converted to an OpenAPI path (e.g. `src/app/api/(finance-core)/companies/[code]/route.ts` → `/companies/{code}`). Group segments in parentheses are stripped.
3. **Resolves handlers** — for each exported `GET`, `POST`, `PUT`, `PATCH`, or `DELETE` in a route file, the script follows re-exports and identifier references to find the actual function node (which is usually in a separate `.http.handlers.ts` file).
4. **Parses `@api` blocks** — reads the JSDoc from the handler function and extracts the JSON object following the `@api` tag. Invalid JSON silently suppresses the operation (a `[warn]` is printed to stdout).
5. **Builds parameters** — for each `*ParametersDto` field, the script expands the DTO's property map into individual OpenAPI parameter objects, carrying through type, required status, and description.
6. **Builds schemas** — for each request body and response DTO, the pre-generated JSON Schema is registered in `components/schemas` and the operation references it via `$ref`. Inline JSON Schema objects (`"dto": { ... }`) are emitted directly.
7. **Derives tags** — the tag for each operation is taken from the folder containing the handler file (the folder immediately before the `api/` folder in the path). Tags control grouping in GitBook.
8. **Sorts operations** — within each tag, operations are sorted in a canonical order: list → filter → search → get → create → update → patch → delete → batch operations → next-code.
9. **Validates and writes** — the completed document is validated with `@readme/openapi-parser`. If validation passes, `public/openapi.json` is written. If it fails, the error is printed and the process exits with code 1.

### Warnings to watch for

| Warning                                  | Cause                                                                                                                         |
| ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `Invalid JSON in @api comment`           | A syntax error in the JSON block — check for unescaped quotes, trailing commas, or embedded `"` inside string values          |
| `DTO "X" not found`                      | The DTO name in the `@api` comment does not match any exported interface under `src/dto/`                                     |
| `X parameter DTO "Y" has no typeLiteral` | A parameter DTO resolved to something other than an interface (e.g. a type alias) — parameters will be omitted for that field |
| `Could not generate schema for "X"`      | `ts-json-schema-generator` failed on the DTO — check for circular references or unsupported types                             |

### Adding a new DTO

Create the file anywhere under `src/dto/` — the script scans recursively. Export the interface, add JSDoc to properties where useful, and reference the interface name in `@api` comments. No registration step is needed.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://voyzu.gitbook.io/docs/customising-and-extending-voyzu/api-jsdoc-guide.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
