Skip to content
Blume
English
Esc
navigateopen⌘Jpreview
On this page

OpenAPI / AsyncAPI

Drop in an OpenAPI or AsyncAPI spec and get a native API reference — one real page per operation, in your sidebar and search.

Point Blume at an OpenAPI spec and it generates a native API reference: one real page per operation, grouped by tag in a tab-scoped sidebar, with schema tables, request/response examples, generated code samples, and an interactive Try it panel. Because each operation is a genuine Blume page, it gets its own URL, shows up in site search and llms.txt, and gets an Open Graph image — the same as any hand-written doc. The config below points Blume at the public Petstore spec as an example.

openapi: {
  enabled: true,
  spec: "https://petstore3.swagger.io/api/v3/openapi.json",
}

That mounts the reference at /reference (an overview page) with each operation at /reference/<tag>/<operation>. The spec is either an http(s) URL or a path to a local file in your project. Blume parses it with Scalar’s OpenAPI parser — Swagger 2.0 and OpenAPI 3.0 specs are upgraded to 3.1 automatically.

The reference doesn’t add a header tab on its own. To surface it, point a navigation tab at its route — this also scopes the operations sidebar for the native renderer:

navigation: {
  tabs: [{ label: "API", path: "/reference" }],
}

A local spec

A relative path is resolved from your project root and read at build time. Both JSON and YAML work:

openapi: {
  enabled: true,
  spec: "./openapi.yaml",
}

Route

route controls where the reference mounts — the overview page and the prefix for every operation route (and the route you point a navigation tab at):

openapi: {
  enabled: true,
  route: "/api",   // overview at /api, operations at /api/<tag>/<operation>
  spec: "./openapi.yaml",
}

Code samples and schemas

codeSamples picks which languages render per operation (built in: curl, js, python); expandSchemas starts nested schema rows expanded rather than collapsed:

openapi: {
  enabled: true,
  spec: "./openapi.yaml",
  codeSamples: ["curl", "js"],
  expandSchemas: true,
}

Try it playground

Operation pages rendered natively ship an interactive Try it panel by default. Blume generates the form from the operation itself: an input per path, query, and header parameter, a body editor built from the request-body schema, everything prefilled from the spec’s examples. A server picker lists the spec’s servers, with a free-text field for any other base URL, and auth inputs match the operation’s resolved security — bearer token, API key, and basic credentials, with OAuth2 as a token paste field (bring an access token; Blume doesn’t run the flow).

The panel and the code samples stay in lockstep: values typed into the form update the generated samples live, so a copied curl command always matches exactly what Send would do. And it stays out of the way — the panel is server-rendered collapsed, and its JavaScript loads only when a reader first opens it. Readers who never touch it download none of it.

playground: false is the entire off switch:

openapi: {
  enabled: true,
  spec: "./openapi.yaml",
  playground: false,
}

Credentials

Credentials typed into the auth inputs stay in memory and vanish on reload. Checking Remember on this device persists them in localStorage, scoped to the docs origin — they’re never sent anywhere except the API being called. Code samples keep showing placeholders (YOUR_TOKEN and friends) whatever’s typed, unless the reader toggles Include my values in samples.

CORS and the proxy

As with the Scalar renderer, requests go directly from the browser to the target API, so the API must allow cross-origin requests from the docs site (Access-Control-Allow-Origin). For APIs that can’t, set playground.proxy: a URL routes requests through a proxy you host, and true enables the built-in /_api-proxy route — which needs a server build, so it requires deployment.output: "server":

openapi: {
  enabled: true,
  spec: "./openapi.yaml",
  playground: {
    proxy: true,   // or a URL of your own
  },
}

The built-in proxy only forwards requests to the origins your specs declare in servers — including across redirects — so a public docs deployment can’t be aimed at other hosts on its network. A Custom base URL typed into the panel isn’t a documented server: with the proxy enabled, requests to it are refused with a 403.

Multiple specs

Use sources to publish more than one spec. Each source gets its own overview route, operation pages, and header tab. Give each a label (used for the tab and to derive its route), or set an explicit route:

openapi: {
  enabled: true,
  sources: [
    { label: "Public API", spec: "./public.json" },   // → /reference/public-api
    { label: "Admin API", route: "/admin", spec: "./admin.json" },
  ],
}

spec is shorthand for a single-entry sources, so you only reach for sources when you have more than one.

Per-source indexing

Generated pages participate in search, llms.txt, and crawler indexing by default. A secondary or overlapping spec can opt out of any surface without hiding its pages or removing it from navigation:

openapi: {
  enabled: true,
  sources: [
    { label: "Public API", route: "/api", spec: "./public.json" },
    {
      label: "Platform API",
      route: "/platform",
      spec: "./platform.json",
      includeInSearch: false,
      includeInLlms: false,
      noindex: true,
    },
  ],
}
  • includeInSearch: false keeps the source’s overview and operations out of site search.
  • includeInLlms: false keeps them out of both llms.txt files.
  • noindex: true adds crawler noindex metadata and removes the pages from the sitemap.

With the Scalar renderer, only noindex applies — a Scalar-rendered reference already sits outside Blume’s search and llms.txt, so the two include* settings have nothing to act on there.

Authorization

Operations that declare security requirements render an Authorization section above their parameters, and the generated code samples send a placeholder credential (Authorization: Bearer YOUR_TOKEN, an API-key header, or a query key — whatever the scheme calls for). There’s nothing to configure: Blume reads security from the spec, so the reference always matches what the API actually enforces.

The OpenAPI semantics carry over as written:

  • An operation’s own security overrides the document’s root default; security: [] marks it public and renders no Authorization section.
  • Multiple requirement entries are alternatives — rendered as “or” groups; every scheme inside one entry is required together. The first alternative feeds the code samples.
  • An empty {} entry means auth is optional for that operation, and the section says so.
  • OAuth2 scopes are listed per scheme; scheme descriptions from components.securitySchemes render inline.

The Scalar renderer

The native renderer is the default — operation pages, search integration, and the Try it playground above are all its work. If you’d rather embed Scalar’s self-contained API reference UI — its own sidebar, search, theme, and request client on a single route — set renderer: "scalar":

openapi: {
  enabled: true,
  renderer: "scalar",
  spec: "./openapi.yaml",
  theme: "purple",   // a Scalar theme name (Scalar renderer only)
}

A Scalar-rendered reference is a self-contained embed on its own route — it doesn’t weave into Blume’s sidebar, search, or llms.txt, and Blume’s playground config doesn’t apply to it. Scalar brings its own request client, which calls your target API directly from the browser (the playground.proxy route isn’t available here), so the API must allow cross-origin requests from the docs site (Access-Control-Allow-Origin). theme applies to the Scalar renderer only.

Passing Scalar options

theme is a shorthand for the one option most people reach for, but Scalar supports many more. A scalar object forwards any Scalar configuration straight to the embedded reference — Blume doesn’t gate the keys, so anything Scalar accepts flows through:

openapi: {
  enabled: true,
  renderer: "scalar",
  spec: "./openapi.yaml",
  scalar: {
    localization: { locale: "es" },   // translate Scalar's own UI
    agent: { disabled: true },         // disable the Scalar Agent
    hideTestRequestButton: true,
    orderSchemaPropertiesBy: "preserve",
  },
}

Blume’s own i18n translates the docs chrome, but Scalar has a separate localization system — set scalar.localization.locale to translate the embedded reference too. Options in the scalar object win over Blume’s derived config, so anything set here (including theme, customCss, or the spec content/url) overrides Blume’s defaults. The same scalar block works on the asyncapi reference.

AsyncAPI

Event-driven APIs use a sibling asyncapi block with the same shape — and the same native renderer. Each send/receive operation becomes a real page with message payload and header schema tables, channel parameters, protocol bindings, an Authorization section derived from the spec’s securitySchemes (server-level and operation-level, alternatives as “or” groups), and a Try it message composer. Only the default route differs (/events):

asyncapi: {
  enabled: true,
  spec: "./asyncapi.yaml",
}

AsyncAPI 2.x specs are normalized to 3.x automatically with the official AsyncAPI converter, so publish/subscribe channels map onto send/receive operation pages with stable URLs — later upgrading the spec file itself through the converter moves nothing. Operations group by tag; untagged operations group under their channel address.

Code samples are protocol-aware, keyed off the operation’s binding (or its servers’ protocol): wscat and a browser WebSocket snippet for WebSockets, kcat for Kafka, mosquitto_pub/mosquitto_sub for MQTT. codeSamples filters that set, the same way it picks languages on the openapi block; a protocol without a supported tool renders the message payload example alone rather than a fabricated client.

Everything documented above carries over, playground included: route, sources with label/route, expandSchemas, the per-source indexing flags, and search indexing by operation summary and tag.

Setting renderer: "scalar" opts back into the embedded Scalar SPA, where — as with OpenAPI — only noindex applies. Scalar has no AsyncAPI playground of its own; its embed auto-detects the document type and renders channels, operations, messages, and a Models section, so that swap trades the composer away.

Try it for events

Operation pages rendered natively ship a Try it panel here too, on the same terms as the OpenAPI panel: server-rendered collapsed, with its JavaScript loaded only when a reader first opens it.

Whatever the protocol, the panel opens with a payload editor prefilled from the message’s examples — or, when the message declares none, from a value sampled out of the payload schema — validated against the message payload schema as you type. Under it sit an input per channel parameter and a server picker fed by the channel’s servers, with a free-text field for any other URL. The protocol-aware code samples stay in lockstep with the form exactly as curl, js, and python do on an HTTP operation: the channel address template is filled in with the parameter values you type, so a copied wscat, WebSocket, kcat, or mosquitto_pub snippet matches what the form says.

Live connect is WebSocket-only. On a ws or wss binding the panel connects to the resolved channel URL, shows the connection state, and logs every frame with a timestamp. AsyncAPI 3 states an action from the API’s side, and the panel follows it: a receive operation is one the API receives from you, so it gets a Send button that publishes the composed payload; a send operation only streams messages at you, so it connects and logs. There’s no reconnect logic — once a socket closes, it stays closed until you connect again. Kafka, MQTT, AMQP, and every other protocol get the composer and the copyable CLI samples, and the panel says as much on the page: Blume doesn’t fake broker connectivity from a browser tab.

asyncapi.playground mirrors openapi.playground — on by default with the native renderer, and false is the entire off switch:

asyncapi: {
  enabled: true,
  spec: "./asyncapi.yaml",
  playground: false,
}

The event composer collects no broker credentials. Each operation page’s Authorization section documents what the broker expects, and a WebSocket connect carries only what’s already in the URL. Nothing is persisted for event operations.

Was this page helpful?