Start with a registered local API from Add an OpenAPI API. Use generation when you want references for many operations without writing an MDX page for each one. The commands below run in your documentation project with SHOALRUN_CLI set as in the quickstart.
Choose generated references or MDX
Use build-time generated references when the OpenAPI contract already gives readers the method, path, parameters, schemas, and examples they need. The pages update whenever the reviewed source changes, and there are no MDX files to maintain. Use an authored MDX reference when an endpoint needs a reviewed introduction, prerequisites, limitations, or task-specific context that the contract cannot express. The compiler still appends the contract details from OpenAPI, so do not copy field tables and example payloads into MDX. Use a guide for a task spanning several endpoints; link its reference pages with relatedApiReferences.
The one-time api materialize command creates draft MDX references for every endpoint or selected source operation IDs or method/path routes. It does not change config or navigation and never rewrites an existing page. For example:
node "$SHOALRUN_CLI" api materialize notes docs.config.yaml --all
node "$SHOALRUN_CLI" api materialize notes docs.config.yaml --operation getNote
node "$SHOALRUN_CLI" api materialize notes docs.config.yaml --route 'GET /notes/{id}'
The default destination is content/api/<api-id>/; use --output for another directory inside the configured content tree. Selectors use the OpenAPI operationId or exact uppercase method and path. Repeating a command reports the existing matching page as skipped. A file with a conflicting identity stops the job before it writes anything. Existing authored references also count as skipped.
Each new page has a stable document ID, slug, operation binding, and access.policy: draft. The draft contains an editorial prompt. While it remains a draft, any already selected build-time reference continues to publish. Review the text, replace the prompt, then set access to public; that MDX page takes the generated page's place for its publication. This also works when generateReferences is off: the authored page binds to the registered API's local OpenAPI source. The command never enables build-time generation, changes the public review digest, or chooses navigation order for you. Run api review before publishing changed API content, and place the reference with operation: or doc: in navigation if it is not already covered by a generator.
Generate many pages selectively
For a large API, set generateReferences on its API config. This avoids writing one MDX file and registry entry for every operation. The build scans the local spec and creates HTML and Markdown reference pages for selected operations. An explicit operations entry and authored page take precedence for that operation. The example Create a note page is authored, while its Get a note page is generated.
The example below uses this site's two-operation Notes source, which contains createNote and getNote. To try it after the tutorial, replace your one-operation apis/notes/openapi.yaml with that source first. Keep the authored Create a note page and its binding. Store the generation choices in a separate config file:
notes:
publicReview:
digest: <digest-from-your-api-review>
source: { kind: file, path: ../apis/notes/openapi.yaml, publish: true }
operations:
- { id: notes.create, sourceOperationId: createNote, reference: reference.create-note }
generateReferences: { $include: ./notes-pages.yaml }
pathPrefix: /example-api/generated
include: [getNote]
include lists source operationId values to generate; an empty list generates none. You can use exclude instead to generate all but named operations, or set generateReferences: true to generate every operation without an authored binding. The build rejects unknown selection IDs. Generated IDs and URLs derive from the API ID and source operationId, not the operation summary, so editing a title does not move the page. If an operationId changes, add an authored binding when the old public identity must remain stable.
For an API notes and operationId: getNote, the generator creates:
| Value | Pattern | Example |
|---|---|---|
| Stable operation ID | <apiId>.<operationId> |
notes.getNote |
| Page ID | reference.<apiId>.<operationId> |
reference.notes.getNote |
| Slug | <pathPrefix>/<kebab-case operationId>-<8-character hash> |
/example-api/generated/get-note-cbedb319 |
pathPrefix defaults to /reference/<apiId>. An operationId with characters outside letters, digits, ., _, and - uses the kebab-case name plus the hash in place of the raw ID. Use the page ID in relatedApiReferences and the stable operation ID in navigation operation: entries. Generated pages pass through global frontmatter defaults and enforced values, so an enforced value or a required custom field applies to them too. The page title is the operation summary, or the method and path when there is no summary.
OpenAPI 3.1 webhooks can use the same authored bindings or generated-page selection. A webhook reference labels the inbound event, shows the request payload and expected acknowledgement, and omits the outbound cURL example. A focused contract for that page keeps the operation under webhooks. A spec can contain only webhooks without paths.
You can place an MDX guide among operation links in navigation by adding a doc: entry in the same group. Generated tag groups can sit before or after that entry; a guide within a particular tag can sit beside that tag's { id: tag-remaining, generated: remaining } entry.
Override generated reference order in navigation
generateReferences chooses which reference pages exist. The navigation list chooses where they appear. You can put a generated reference's stable operation ID in an operation: entry to override its OpenAPI tag group and source order. No MDX page is needed. Write those entries in the exact order readers should see them, then use generated: tags to place only the operations you did not list.
- id: widgets
label: Widgets
items:
- { id: get-widget-nav, operation: widgets.getWidget }
- { id: query-widgets-nav, operation: widgets.queryWidgets }
- { id: create-widget-nav, operation: widgets.createWidget }
- { id: other-widgets, api: widgets, generated: tags }
Here GET /Query/widgets may have the OpenAPI tag Query, but its generated reference appears second under Widgets. The listed operations appear in this YAML order, regardless of their path, tag, or position in the OpenAPI file. They are removed from the generated: tags remainder, so each appears once. The id fields such as query-widgets-nav identify menu entries; operation values are the stable IDs that select the reference pages. An authored MDX reference uses the same navigation rule. See Organize site navigation for the full layout.
To review the default publication's spec operation by operation, run:
node "$SHOALRUN_CLI" api select notes docs.config.yaml --output config/notes-pages.yaml
The command shows the method, path, ID, and summary for each operation and asks yes or no. It shows operations with authored pages but does not replace them. After the choices are complete, it writes the pathPrefix and include list to the selection file; q cancels without writing. Run api review again, inspect the candidate, and update publicReview.digest before rebuilding to apply the choices. Add { id: notes-generated, api: notes, generated: tags } under a navigation group to display the generated pages by their first OpenAPI tag, or place selected operations explicitly in navigation. This site's Example Notes API subnav uses the tag generator.
The digest shown above is a placeholder. Keep your existing review digest while editing. After reviewing the new selection, replace it with the reported value. Selection limits generated pages; source.publish: true still publishes the full source, including operations without pages. Use focused: true and omit publish when you only need individual operation contracts.
Use a template for editorial context
Use the built-in api-reference template for details OpenAPI cannot supply, such as operational context, task links, prerequisites, and limitations. Keep method, path, field lists, and example values in the spec so generated references have one source for those facts.
shoalrun scaffold api-reference -n content/create-enrollment.mdx \
--id reference.create-enrollment --operation cedar.createEnrollment
No template registration is needed. The scaffold requires exactly one known stable operation ID. Check that the page ID matches that operation's reference entry in API config. A scaffold is a draft; replace every prompt and review the operation against the spec before publishing. The Templates guide shows how to override the built-in outline for your project. Editing a template later affects new drafts, not existing pages.
Add useful examples
API reference pages display the examples defined in the OpenAPI file; the build does not invent them. Give each example a clear name and include the fields a reader needs to use it. In this site's Notes spec, Create a note has request and response examples. Get a note has a note ID and a response example.
The right panel offers Examples first when the spec provides them, plus a JSON shape view built from each JSON request or response schema. Shape values are type placeholders, not a runnable payload. Request and response examples stay in separate groups, with response status and media type in each heading. Groups of three or fewer use native disclosures. Larger groups use a compact selector with one visible preview when JavaScript is available; without JavaScript, every disclosure remains usable. The Create a note request has ten named examples so you can inspect this layout. Direct links still open the selected example.
Local named request examples for ordinary, non-multipart operations also have copyable request samples. In HTML, each sample has a language selector. It offers cURL, JavaScript, Node.js, Python, Go, Java, PHP, Ruby, and C#. Webhooks have no generated request samples. A sample uses the first declared server and security option, required parameters, the example body, and its media type. Replace YOUR_ credential and parameter placeholders before using it. Ordinary operations without a request body show a general request sample. An operation with a request body but no local named request examples usually shows no sample. A multipart body whose required fields are all binary strings can produce a file-upload sample in cURL only. An externalValue example has no example-specific sample. These samples are generated from the OpenAPI source and have not been executed. The Markdown twin and agent bundle keep only the cURL sample. Agents can translate cURL; one canonical sample keeps the .md output small.
An OpenAPI summary names an example, and description adds context. Without a summary, the reader turns its source key into a readable title and still shows the original key. A response without an example still has a shape: Get a note's 404 response shows code and message as strings. The full field descriptions and presence rules remain in the main reference and its Markdown twin.
If a guide shows only part of a payload to explain one field, call it an excerpt. Readers can then distinguish it from a complete request or response. To show a full named example from the spec in a guide, use ApiExample. It stays tied to the same source as the API reference.
The build expands that include in the guide's Markdown twin as a heading, source context, and a fenced payload or external example link. Agents can read the selected example directly in the .md page; the API embeds guide shows the exact output.
Link guides to API references
Add relatedApiReferences to a guide or how-to page to name API reference pages by stable document ID:
relatedApiReferences:
- reference.create-note
- reference.notes.getNote
This array contains two page IDs. One points to the authored Create a note reference; the other points to the generated Get a note reference. The build adds a Related API references list to the guide and a Related guides list to each named reference. Links use the current page titles and routes, so they stay correct when a slug changes. The lists appear in HTML and the Markdown twins. A missing ID or an ID for another page type fails the build; a reference unavailable in the guide's publication is omitted.
The Write a note guide uses this field to link to the Create a note reference. You can inspect the generated lists at the bottom of both pages.
Map extension values to Shoalrun status
If a spec uses an x-* extension for availability, explicitly map its values to Shoalrun's status badges in the API config. This works with extension names from any OpenAPI authoring service:
payments:
source: { kind: file, path: ../apis/payments/openapi.yaml }
mappings:
subjectLifecycle:
from: x-vendor-availability
values:
preview: beta
building: in-development
retired: deprecated
Only mapped values change a page's status. OpenAPI deprecated: true always yields Deprecated. An authored page can also set subjectLifecycle directly. The OpenAPI extension mappings reference specifies accepted scalar and nested value shapes, precedence, and how other extensions are handled.
Describe filters that do not use their OpenAPI parameter name
Some APIs put filter expressions directly in the query string even though their OpenAPI description has one placeholder parameter. Opt in by path prefix so examples and generated cURL use the actual wire form:
payments:
source: { kind: file, path: ../apis/payments/openapi.yaml }
querySyntax:
- parameter: filters
pathPrefixes: [/Query/]
style: unwrapped
example: FIELD(eq)=VALUE
For a matching operation, the reference explains the direct ?FIELD(eq)=VALUE form and omits filters= from its generated cURL. The source's notes on accepted fields stay in a disclosure. Named examples with one field and value supply a more specific cURL filter. Other endpoints keep ordinary OpenAPI parameter behavior.
Next, organize generated operations in navigation.