A technically valid OpenAPI file can still produce bad documentation. The routes are present, the schemas parse, and every response has a status code, but the generated page says little more than GET /users/{id} beside an empty request form. The generator did its job. The contract did not give it enough to work with.
To generate API documentation from OpenAPI, point Doccupine at a local OpenAPI 3.0 or 3.1 YAML or JSON file through the openapi field in doccupine.json. Doccupine creates an /api-reference index and a page for each operation under paths, grouped by tag. Each endpoint page includes the reference content and a live request playground with cURL, JavaScript, and Python snippets.
That automates the repetitive reference layer. It does not automate the complete developer journey, and knowing where that boundary sits is what makes the generated result useful.
What an OpenAPI documentation generator can produce
The OpenAPI Specification is a machine-readable description of an HTTP API. Documentation generation is one of its intended uses, but the useful unit is not "the file." It is the set of fields that map into things a reader can see and use.
In Doccupine, one configured spec produces:
- An
/api-referencelanding page listing the operations. - One static page for each operation under
paths. - Sidebar groups based on each operation's first tag.
- Method badges, parameter definitions, a selected request-body schema, and declared responses.
- A live playground for building and sending requests.
- cURL, JavaScript
fetch, and Pythonrequestssnippets for the request currently in the playground.
The generated pages sit beside the rest of the documentation rather than replacing it. That distinction matters. An endpoint catalogue is only one of the five page types a useful API documentation site needs. OpenAPI is unusually good at producing that catalogue because consistency matters more than original prose there.
Prepare the spec before you generate API documentation from OpenAPI
A generator cannot recover information the contract does not contain. Before wiring the file into a docs site, check the seven fields that do most of the visible work:
serversdecides where live requests go. Use an absolute HTTP or HTTPS URL. Put a sandbox first if readers should be able to send write requests without touching production data.operationIdgives each operation a stable identity. Doccupine can fall back to the method and path, but a deliberate ID survives path organization changes and makes a single operation easier to reference from a hand-written page. Keep it unique across every spec configured on the site.tagsbecome navigation. A missing tag leaves the operation under a generic group. A useful tag describes the reader's domain, such asInvoicesorUsers, not an internal service name.summarybecomes the page title. Write a short action in plain language: "Create an invoice," not "Invoice endpoint."descriptionexplains behavior the schema cannot. Defaults, side effects, idempotency, and permission requirements belong here.- Examples make the playground usable. A request-body schema describes valid data. An example gives the reader a request they can send and modify immediately.
securityrequirements produce authentication fields. When an operation or the root security requirement references a supportedsecuritySchemesentry, the current playground handles header and query API keys, bearer tokens, and HTTP basic authentication.
A compact spec with those pieces looks like this:
openapi: 3.1.0
info:
title: Acme Billing API
version: 1.0.0
servers:
- url: https://sandbox.api.example.com/v1
tags:
- name: Invoices
paths:
/invoices/{invoiceId}:
get:
operationId: getInvoice
summary: Get an invoice
description: Returns the current invoice, including its payment status.
tags: [Invoices]
security:
- bearerAuth: []
parameters:
- name: invoiceId
in: path
required: true
schema:
type: string
example: inv_123
responses:
"200":
description: The invoice was found
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearerThis is not a complete API description. It is enough to show the relationship: servers provides the request target, tags organizes the sidebar, the operation becomes a page, the path parameter becomes an input, and bearerAuth becomes a credential field.
How to generate the API reference with Doccupine
With Node.js 22.12.0 or newer, add the local spec path to doccupine.json:
{
"watchDir": "docs",
"outputDir": "nextjs-app",
"port": "3000",
"openapi": "openapi.yaml"
}Then run:
npx doccupineThe default command generates the Next.js documentation app, starts its development server, and watches the configured sources. When the root OpenAPI file changes, Doccupine reparses the configured specs and refreshes the generated endpoint pages, navigation, sitemap, Markdown mirrors, and other machine-readable outputs. Restart the development server to clear an initialized in-memory search index. Rebuild the generated app to refresh its precomputed embedding index.
The configured root spec must be local and inside the project root. Remote OpenAPI URLs are rejected. Local $ref files are supported when they stay inside the root spec's directory, use .json, .yaml, or .yml, and do not pass through dotfiles or dot-directories. Those boundaries make builds reproducible and prevent a spec from pulling arbitrary files or remote content into the generated application.
Only configured root specs are watched. If a sibling $ref file changes without the root changing, touch the root spec or restart Doccupine to trigger regeneration.
Generate editable API pages in the hosted platform
The hosted platform exposes the same contract through a reviewable Git workflow. Open Settings > API Playground, choose the YAML or JSON spec in the repository, and press Save Configuration. That stages the doccupine.json change. Publish it with the spec file before generating pages, because generation reads the committed configuration.
Then press Generate API pages. For a self-contained spec, or one using only internal #/... references, the platform writes one MDX page per discovered operation plus an API Reference landing page and stages the files as pending changes. You can inspect the complete change set, edit the generated prose, discard individual files, and deploy only after the result looks right.
The hosted authoring action does not dereference sibling files while writing MDX, even though the deployed CLI build can follow permitted local $ref files. Review multi-document specs for omitted operations or schema details.
With several configured specs, the hosted action can skip an unreadable or unparseable spec when another valid spec still produces operations. Compare the pending pages with every configured spec before publishing.
Generation stages upserts, not a complete synchronization. Running it again can replace an existing generated page at the same path, and it does not delete MDX files for operations removed from the spec. Review replacements in the diff and delete obsolete endpoint pages explicitly.
This is deliberately different from hiding the generated reference in a database. Once published, the pages are normal MDX in the project's GitHub repository. The OpenAPI frontmatter keeps the playground connected to the operation, while the body remains editable when an endpoint needs context the contract cannot carry.
The settings picker manages one spec. For multiple specs, use the openapi array in doccupine.json directly.
Put a live endpoint inside a hand-written guide
The generated reference is not the only place an operation can appear. A hand-written MDX page can attach one playground through frontmatter:
---
title: "Create your first invoice"
openapi: "POST /invoices"
---
This guide explains the decisions around the request instead of repeating the
parameter table the OpenAPI contract already owns.The value can be METHOD /path or an exact operationId. Doccupine renders the matching playground above the page's prose. With several specs, prefer a globally unique operationId; the lookup itself is not namespaced. If the reference does not match, the prose still renders and the generator logs a warning.
This is the useful composition: let the contract own the request surface, then write the task, prerequisites, and consequences around it. The endpoint stays systematic without forcing the guide to read like generated reference text.
How the interactive OpenAPI playground sends requests
The playground has two execution modes because API servers and browsers enforce different boundaries.
Proxy mode is the default. The generated documentation server sends the request. This works when the target API does not permit cross-origin browser calls. The proxy only accepts targets derived from servers entries in the OpenAPI document. In production it resolves the target before connecting, rejects private, loopback, link-local, and metadata destinations, and pins the connection to the validated address to reduce DNS-rebinding risk.
Direct mode runs in the reader's browser. Credentials do not transit through the documentation server, but the API has to allow the browser origin through CORS. An HTTPS documentation site also cannot call an HTTP API without hitting mixed-content restrictions.
The proxy caps decoded request bodies at 1 MB, captures up to 5 MB of response data, and configures a 30-second request timeout. Its handler deliberately does not log target URLs, request headers, request bodies, or response bodies. That is narrower than saying credentials never pass through a server: in proxy mode they do, because the server has to make the request.
Playground requests are real requests. A POST, PATCH, or DELETE can change data. Put a sandbox server first in the spec, use narrowly scoped test credentials, and make examples non-destructive. A destination allowlist controls where a request goes; it does not limit what a valid credential can do after it gets there.
Why a generated playground can still fail
When the pages render but a request does not, the failure is usually at one of five boundaries:
- There is no absolute server URL. The reference can still render, but there is no usable target for a live request.
- Direct mode hits CORS. The browser sends an origin the API does not allow. Use proxy mode or configure the API intentionally.
- The request has no example. The schema may render correctly while the editable request body starts empty. Add a media-type example instead of expecting a generator to invent safe data.
- The contract uses unsupported execution semantics. The current playground is built for simple authentication and parameters. It does not fully model alternative or optional security requirements; cookie authentication is not reliably executable; and complex
styleandexploderules, OAuth flows, OpenID Connect, and mutual TLS need a separate integration path. Query API keys remain visible in generated snippet URLs, so treat those snippets as sensitive. - A reference crosses the local source boundary. Remote refs, path escapes, symlink escapes, dotfiles, and unsupported ref formats stop generation instead of silently widening what the build can read.
These are not reasons to abandon generation. They are reasons to test the generated request surface before publishing it. A schema that parses is not proof that the request a reader builds will match the API's real behavior.
Generate the reference, then write what the contract cannot know
OpenAPI can describe an operation exhaustively and still not answer the first question a developer asks: "Which endpoint should I use to solve my problem?"
The contract does not know why one workflow is preferred, which credentials a new account starts with, what a retry changes, how to recover from a business-rule error, or which old behavior is being deprecated. It cannot write a getting-started path or explain the sequence across three endpoints that completes a real task.
It also cannot prove itself current. Generated pages remove the drift between a spec and a hand-maintained parameter table, but they do not remove the documentation drift between the spec and the code. The contract still has to change in the same pull request as the implementation.
The right division is simple: generate the surface that should be exhaustive and uniform; write the parts that require judgement. That produces better reference pages without pretending reference is all the documentation an API needs.
Once the contract carries real descriptions and safe examples, generating the reference stops being a shortcut around documentation work. It becomes a way to reserve that work for the explanations only a person can write.
If you have an OpenAPI operation that a documentation generator consistently renders badly, email [email protected] with the feature it uses. I want to know which parts of real contracts still fall through the gap between "valid OpenAPI" and "useful API documentation."