# What the renderer reads, and how to shape it.

> What the renderer reads — OpenAPI 3.0 to 3.2, Swagger 2.0, webhooks — and how to shape it: Overlay 1.1, reader-side patches, hiding, the local changelog.
>
> Source: https://apiglow.dev/docs/schema-support/index.md


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](/features/schema/) 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 `query` method, `in: querystring` parameters 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 `summary` labels its nav section, `parent` nests it (a parent immediately followed by its children, with count pills and change dots covering subgroups), and `kind` decides what a tag *is*: `nav` or 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 `links` and `externalDocs`** are rendered too. A Link Object gets a "go to operation" navigation, with its runtime expressions displayed as documentation rather than evaluated. `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 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](/docs/audit/) points out.
- **Enum values carry their meaning.** When a schema describes each value — with `x-enum-descriptions` (a list next to `enum`), `x-enumDescriptions` (a map from value to text), or as a `oneOf` of constants each with its own `description` — the documentation lists every value with what it means. A `oneOf` of 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 `description` or `summary` next to a `$ref`** is shown, as OpenAPI 3.1 says.
- **A broken schema opens as far as it can.** A `$ref` that leads nowhere no longer stops the documentation: the rest renders, and the [audit](/docs/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](/docs/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](https://spec.openapis.org/overlay/v1.1.0.html) documents applied at load. Each entry is a URL — JSON or YAML — or the overlay object itself, carried by the config:

```json {title="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. `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](/docs/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 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, 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 `hide` still applies after it.
- The [audit](/docs/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": true` on an operation, a Path Item, or a `tags` entry.
- **In the config** — `openapi.hide` patterns, 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](/docs/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](/features/schema/) 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.

