---
title: AsyncAPI
description: Drop in an AsyncAPI spec and get a native event reference — one real page per send and receive operation, in your sidebar and search.
---

Event-driven APIs use the `asyncapi()` adapter from `blume/reference`, listed under `reference` beside any [OpenAPI](/docs/references/openapi) or [GraphQL](/docs/references/graphql) adapters. It takes the same options as `openapi()` and renders with 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](#try-it-for-events) message composer. 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.

```ts blume.config.ts lineNumbers
import { defineConfig } from "blume";
import { asyncapi } from "blume/reference";

export default defineConfig({
  reference: [asyncapi({ spec: "./asyncapi.yaml" })],
});
```

That mounts the reference at `/events` (an overview page) with each operation on its own page beneath it. The `spec` is either an `http(s)` URL or a path to a local file in your project, JSON or YAML. As with every reference, it doesn't add a header tab on its own — point a [navigation tab](/docs/content/navigation#tabs) at its route to surface it and scope the operations sidebar:

```ts blume.config.ts
navigation: {
  tabs: [{ label: "Events", path: "/events" }],
}
```

## Spec versions

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. The converter is an optional peer dependency, so a site with a 1.x or 2.x spec installs it (`npm install @asyncapi/converter`); without it the build fails with that install command. A 3.x spec needs nothing extra. Operations group by tag; untagged operations group under their channel address.

## Code samples

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 `openapi()`; a protocol without a supported tool renders the message payload example alone rather than a fabricated client.

## Shared options

Everything documented for [OpenAPI](/docs/references/openapi) carries over, [`playground`](#try-it-for-events) included: [`route`](/docs/references/openapi#route), [`sources`](/docs/references/openapi#multiple-specs) with `label`/`route`, `expandSchemas`, the [per-source indexing](/docs/references/openapi#per-source-indexing) flags (`seoDescriptionSuffix` too — the generated sentence names the channel and action instead of an endpoint), and search indexing by operation summary and tag.

## Embedding Scalar instead

`asyncapi()` always renders Blume's own pages. To embed [Scalar](https://scalar.com)'s UI instead, list a [`scalar()`](/docs/references/scalar) adapter pointed at the AsyncAPI document — its embed detects the document type and renders channels, operations, messages, and a Models section. Scalar has no AsyncAPI playground of its own, so that swap trades the composer away, and only `noindex` of the per-source controls applies to it.

## Try it for events

Operation pages rendered natively ship a **Try it** panel here too, on the same terms as the [OpenAPI panel](/docs/references/openapi#try-it-playground): 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()`'s `playground` mirrors `openapi()`'s — on by default with the native renderer, and `false` is the entire off switch:

```ts blume.config.ts lineNumbers
reference: [asyncapi({ spec: "./asyncapi.yaml", playground: false })],
```

:::note
`playground.proxy` doesn't apply to event operations. It forwards HTTP requests, and a WebSocket connect goes straight from the browser to the server named in the URL, so there's nothing for a proxy to sit in front of.
:::

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.
