What the renderer reads, and how to shape it.
Maximal OpenAPI support is a stated obligation of the project: an unsupported construct of a supported version is treated as a defect, not a limitation.
This guide states what is read, and documents the four levers that shape the rendered document — overlays declared by the host, overlays written by the reader, hiding, and the local changelog. The schema pillar is the short version.
Versions and formats
- OpenAPI 3.0.x, 3.1.x and 3.2.x are rendered natively, including 3.2’s
querymethod,in: querystringparameters and$self. JSON or YAML. - JSON Schema 2020-12 keywords are rendered, in the schema views and in the generated examples.
- Webhooks and callbacks are normalized and rendered like operations — see below.
- 3.2 tag sections are rendered in full. A tag’s
summarylabels its nav section,parentnests it (a parent immediately followed by its children, with count pills and change dots covering subgroups), andkinddecides what a tag is:navor absent makes a section, and any other value —badge,audience, or one of your own — becomes a badge on the operations carrying it. The two shapes the spec forbids both detach back to the root rather than crash: an unknown parent, and a parent cycle. - Response
linksandexternalDocsare rendered too. A Link Object gets a “go to operation” navigation, with its runtime expressions displayed as documentation rather than evaluated.externalDocsrenders at the root, tag, operation and schema levels. - Circular references are handled everywhere. Cyclic nodes are marked in the model, and rendering expands lazily with a depth budget of three levels, then an explicit “expand”. There is no unbounded recursion, in the doc or in the generated examples. A recursion no finite value can satisfy is something the audit points out.
- Enum values carry their meaning. When a schema describes each value — with
x-enum-descriptions(a list next toenum),x-enumDescriptions(a map from value to text), or as aoneOfof constants each with its owndescription— the documentation lists every value with what it means. AoneOfof constants reads as the enum it is: a select in the try-it, not a choice between variants. - Relative URLs resolve against the schema, as OpenAPI 3.1 says, not against the documentation page: a server on a path or an operation (
/v2), OAuth and OpenID Connect URLs, external examples,externalDocs, license and contact links. The base is the schema’s$self, else the URL it was read from. - A
descriptionorsummarynext to a$refis shown, as OpenAPI 3.1 says. - A broken schema opens as far as it can. A
$refthat leads nowhere no longer stops the documentation: the rest renders, and the audit names the broken reference. A file with a syntax error opens if it can be read past the error, and one that cannot be used says what is wrong in it, at its line and column. The tolerant reader that does this,read-document.js, loads only for such a schema.
Swagger 2.0, converted at load
A document declaring swagger: "2.0" is converted to OpenAPI 3.0.4 before anything else reads it, so nothing downstream knows 2.0 exists. host, basePath and schemes become servers; in: body and in: formData parameters become request bodies; definitions move to components with every $ref rewritten; security definitions and flow names are renamed to their 3.x forms.
The conversion has limits and reports them. The few collectionFormat values 3.0 cannot express fall back to comma-joined values, the original is recorded in the document, and the audit reports each approximation. The settings diagnostics show that a conversion happened.
Overlays: fix a schema you don’t own
openapi.overlays declares OpenAPI Overlay 1.1 documents applied at load. Each entry is a URL — JSON or YAML — or the overlay object itself, carried by the config:
{
"openapi": {
"url": "https://vendor.example.com/openapi.json",
"overlays": [
{
"overlay": "1.1.0",
"info": { "title": "Docs fixes", "version": "1" },
"actions": [
{ "target": "$.paths['/pets'].get", "update": { "summary": "List pets" } },
{ "target": "$.paths['/internal']", "remove": true }
]
}
]
}
}What the engine guarantees:
- Targets are full RFC 9535 JSONPath — filters, slices, unions, descent and the function extensions, not just the child/wildcard subset the Overlay spec’s examples use.
- Three actions.
updatemerges — objects recursively, arrays by concatenation, primitives by replacement.remove: truedeletes the node from its parent.copy, new in 1.1, merges a node of the document itself into each target. A 1.0 overlay document is accepted and read with the 1.1 rules. - Overlays apply to the parsed source, before the Swagger 2.0 conversion. An overlay is written against the file its author actually has, which may well be a 2.0 one. Everything downstream sees a single document, the overlaid one: the audit grades it, and the schema download button serves it.
- Order is declaration order. The root’s overlays first, then the active spec’s own in multi-spec. They accumulate, like
hide. - Nothing is silent. A target matching nothing, an update that cannot merge where it points, an unloadable URL — every deviation is listed in the settings diagnostics and echoed to the console. An overlay never breaks the load.
When the reader is the one who is stuck
The section above is the integrator’s lever. Readers get the same one, at a smaller scale: a parameter typed string that the server rejects unless it is a number, a servers entry that was never filled in, a required field the API quietly stopped requiring. Until the schema’s owner acts, the try-it builds requests from a model everyone knows to be false.
Patch this schema locally, a section of the settings panel, takes an Overlay 1.1 document written by the reader and applies it after everything the configuration declared. Same engine, same JSONPath, same diagnostics.
Four buttons:
- Check dry-runs the document against the schema already in memory and reports, per action, how many nodes its target matches. It fetches nothing, writes nothing and reloads nothing, so “target matches 0 nodes” is something you learn before committing to it.
- Save & reload persists the document and reloads the page. Overlays run on the parsed source before anything reads it, so applying a patch means re-running the pipeline — the same stance as switching spec.
- Download emits
overlay-{specId}.json, a standard Overlay 1.1 file. Attach it to the bug report, open the pull request, or hand it to whoever maintainsopenapi.overlays. - Remove the patch deletes the document and reloads, behind a confirm step.
The editor takes JSON only. The openapi.overlays URL channel still accepts YAML, since it rides the ref-parser, but the app carries no YAML serializer — a textarea that parsed YAML and could only emit JSON would make the download disagree with what was typed.
While a patch is active, the header carries a permanent “Patched schema” badge that opens the editor, and the settings diagnostics list the reader’s document beside the host’s, actions and warnings alike, marked as theirs. The failure mode this feature creates is forgetting you applied one.
The rest of the contract:
- One document per spec, capped at 64 KB. A save over the cap is refused and writes nothing.
- It is stored in the reader’s browser under the spec’s own key, so it is spec-scoped and the settings purge clears it.
- There is no host veto. It changes one reader’s view in one browser, and
hidestill applies after it. - The audit grades the patched document. The schema download button is the exception and still serves the API’s published file.
One config key exists on the host side, and it seeds rather than governs. openapi.userOverlay hands the reader’s editor a starting patch, default null. Per spec it replaces the root’s document rather than stacking, and re-seeding a different document discards the reader’s local edits.
Hiding internal endpoints
Two channels, stackable:
- In the schema —
"x-apiglow-hide": trueon an operation, a Path Item, or atagsentry. - In the config —
openapi.hidepatterns, with*as a wildcard:"tag:Internal","DELETE /admin/*","/admin/*", or an operationId. The fallback id{method}-{path-slug}matches too, so an operation without an operationId can still be named.
Filtering happens when the document is normalized, so a hidden operation is absent from the nav, the search, the pager, the changelog diff and every export, llms.txt included, all at once.
Two places still see the full document: the audit, where an author wants the whole picture and findings on hidden operations are labeled rather than linked, and the network tab.
This is documentation hiding, not security. The browser downloads the full schema, and anyone can read it. Never use it as access control.
The local schema changelog
Returning readers get “what changed since my last visit”. On load, the document is diffed against a fingerprint of the operations that reader last saw, stored in their own browser. A modal lists the operations added, removed and changed, with both schema versions and the date of the last visit. Badges mark added or changed operations in the nav and in the doc itself, with accessible names, never colour alone. Opening the modal once marks the diff as seen, and the next changelog starts from there.
There is no service behind it and nothing to configure. Fingerprints are compact per-schema snapshots in the reader’s IndexedDB, capped LRU-style at twenty schemas, and an oversized record is simply not stored.
Three limits follow from that design: a first visit has nothing to compare against, an evicted snapshot costs one changelog, and the diff compares bounded fingerprints, so a change buried below the expansion depth is not flagged. For contract-level guarantees, diff schema files in CI. This is a reading aid for humans — the feature page shows what readers see.
Webhooks, callbacks, and the simulator
Webhooks — the 3.1+ webhooks key — render like operations, in their own nav section. Callbacks render inside the operation that declares them.
Instead of a try-it, a webhook page carries a simulator: it sends the example payload to a receiver URL you provide, meaning your endpoint under development rather than the API, so a receiver can be exercised without waiting for the real event.
The simulator resolves the active environment’s {{variables}} in the URL, headers and payload, and a missing one blocks the send as everywhere else. It injects no credentials, since the call targets your receiver rather than the API. It honours tryIt.proxyUrl when one is configured.
For a receiver that allows no origin at all, a fire-and-forget toggle sends in no-cors mode: the event is delivered and the response is opaque. The headers that mode drops are named before the send.