Skip to main content

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 exactly 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 control feature page is the short version.

Versions and formats

  • OpenAPI 3.0.x, 3.1.x, 3.2.x are rendered natively — including 3.2’s query method, in: querystring parameters and $self. JSON or YAML.
  • JSON Schema 2020-12 keywords are rendered rather than silently dropped, 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 summary labels its nav section, parent nests it (a parent immediately followed by its children, count pills and change dots covering subgroups), and kind decides what a tag isnav or absent makes a section, any other value (badge, audience, or your own) becomes a badge on the operations carrying it. The two shapes the spec forbids are rooted, never crashed: an unknown parent and a parent cycle both detach back to the root.
  • Response links and externalDocs are rendered too: a Link Object gets a “go to operation” navigation (its runtime expressions displayed as documentation, never evaluated), and externalDocs renders 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 (three levels, then an explicit “expand”). No unbounded recursion, in the doc or in the generated examples. Deep cycles are also something the audit points out to the author.

Swagger 2.0, converted at load

A document declaring swagger: "2.0" is converted to OpenAPI 3.0.4 before anything else reads it — nothing downstream knows 2.0 exists. host/basePath/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 is honest about its limits: 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 a URL (JSON or YAML) or the overlay object itself, carried by the config:

api-doc-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. update merges (objects recursively, arrays by concatenation, primitives by replacement), remove: true deletes the node from its parent, and 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 — your readers can never fetch the un-fixed file by accident.
  • 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, for the same reason 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. The owner is another team, another company, or a ticket queue — and until they act, 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; the only new thing is who wrote the document and where it is kept.

Four buttons, and each is exactly what it says:

  • 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, 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. Nothing is live-patched behind your back.
  • Download emits overlay-{specId}.json: a standard Overlay 1.1 file, which is the point. The workaround’s natural end state is upstream — attach it to the bug report, open the pull request, or hand it to whoever maintains openapi.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 — it rides the ref-parser — but the app carries no YAML serializer, so a textarea that parsed YAML and could only emit JSON would make the download lie about what was typed.

Nothing about a local patch is allowed to be quiet, because the failure mode this feature creates is forgetting you applied one: 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 rest of the contract, in one breath: one document per spec, capped at 64 KB (a save over the cap is refused and writes nothing); stored in the reader’s browser under the spec’s own key, so it is spec-scoped and the settings purge clears it; no host veto — it changes one reader’s view in one browser, hide still applies after it, and a host that curates its documentation loses nothing. The audit grades the patched document, which is the point of patching it; the schema download button is the deliberate exception and still serves the API’s published file, because that is the one you hand to someone else.

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 — the editor stays the reader’s, the host only proposes its opening state.

Hiding internal endpoints

Two channels, stackable:

  • In the schema"x-apiglow-hide": true on an operation, a Path Item, or a tags entry.
  • In the configopenapi.hide patterns, * accepted 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 — at once. Two places still see the full document, on purpose: the audit (an author wants the whole picture; findings on hidden operations are labeled instead of 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 — and badges mark added or changed operations in the nav and in the doc itself, with accessible names, never color alone. Opening the modal once marks the diff as seen; 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 (twenty schemas; an oversized record is simply not stored). The limits follow from the design, and are worth knowing: a first visit has nothing to compare against, an evicted snapshot costs exactly one changelog, and the diff compares bounded fingerprints — 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 — your endpoint under development, not 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 — a missing one blocks the send, as everywhere — but deliberately injects no credentials: the call targets your receiver, not the API. It honors tryIt.proxyUrl when one is configured, and for a receiver that allows no origin at all, a fire-and-forget toggle sends in no-cors mode: the event is delivered, the response is opaque, and the headers that mode drops are named before the send rather than lost quietly.