WebSocket channels

Add an AsyncAPI document beside your OpenAPI spec and its WebSocket channels join the API reference, next to the endpoints they belong with.

Markline reads AsyncAPI 2.x and 3.0 documents and shows each WebSocket channel in the API reference, as a section on a resource page, the way an operation is shown. There's nothing to generate and no second reference to maintain: the channels sit in the same sidebar, endpoint lists and page search as the REST operations.

Add a document

Put the document at api/asyncapi.json, beside api/openapi.json:

content/
  api/
    openapi.json
    asyncapi.json
  markline.json

That's all a site needs. To keep it somewhere else, set api.asyncapi.file, relative to the content folder:

markline.json
{
  "api": {
    "asyncapi": { "file": "api/realtime.json" }
  }
}

The document is read as JSON. A file that isn't JSON, or isn't valid JSON, adds no channels and the build says why (see Build output).

What a channel shows

Each channel gets a section with:

  • The connection URL, under a WSS badge: the first server's URL joined to the channel's address, with {parameters} set apart.
  • Its description, rendered as markdown.
  • Path parameters, query parameters and headers, from the channel's parameters and its ws binding's query and headers, in the same tables as an operation's.
  • The fields of each message, headed "Send · name" or "Receive · name", with the message's description.
  • A handshake card: the opening GET with its required query parameters and its headers, and the 101 Switching Protocols reply, ready to copy.
  • A message list: one row per message, with an arrow for its direction, its name, a Send or Receive label and a one-line preview. A row opens to the full example, which you can copy or expand.

What the client sends is listed before what it receives. In the sidebar and the resource's endpoint list a channel carries a WSS badge in place of a method.

Deprecated channels, messages and fields are labelled so (see Hiding and deprecation).

Where channels go

A channel goes on a resource page, so its URL sits beside the operations it belongs with. Markline picks the page in this order:

  1. Its tags. The first tag that names a resource, after api.tags renames and merges. When every tag the channel has is hidden, the channel is hidden too.
  2. Its path. The resource whose operations share the longest run of leading path segments with the channel's address. Hosts and a leading version segment (/v1) don't count, {parameters} match each other whatever their names, and case doesn't matter. A tie goes to the resource listed first.
  3. A WebSockets page. A channel that shares nothing with any resource goes on a "WebSockets" page after the others, at /api-reference/websockets. It can be renamed, moved, merged or put in a section like any other page (see below).

So /v1/text-to-speech/{voice_id}/stream-input lands on the resource that has /v1/text-to-speech/{voice_id}, without any tags. Placement never changes the URL of an existing page: channels only add sections to pages, and the WebSockets page when it's needed.

Titles and message names

A channel's title is the first of:

  1. its x-title (in 3.0, its title);
  2. its operation's summary;
  3. the last plain segment of its address, made readable: /v1/stream-input reads "Stream input".

A summary that's only publish, subscribe, send or receive, in any case, says nothing about the channel, so it isn't used as a title.

A message whose payload is a oneOf of kinds (one "client message" covering start, text and close, say) is listed once per kind. Each kind is named by its title, then the name its $ref points at, then its place ("Message 3"), and keeps its own examples. A payload that's an ordinary object, or a oneOf beside properties, stays one message.

Whose side the document is written from

AsyncAPI 2.x describes the server: an operation under publish is what the server receives, so a client sends it, and subscribe is what a client receives. Markline shows both from the client's side, as Send and Receive, since that's who reads an API reference.

Some 2.x documents are written from the client's side instead, with publish meaning what the server sends. If Send and Receive come out the wrong way round, set api.asyncapi.perspective to "client":

markline.json
{
  "api": {
    "asyncapi": { "perspective": "client" }
  }
}

The build says once that it's reading the document from the client's side, so a wrong setting is easy to spot. AsyncAPI 3.0 documents say what each side does with action, so the setting doesn't change them. A 3.0 message that no operation names has no direction; it's listed as "Message".

Hiding and deprecation

The extensions that hide parts of the OpenAPI spec hide parts of the AsyncAPI document too: x-internal and x-hidden by default, or the list in api.hideExtensions. Set one to true on:

  • a channel, and it's left out;
  • an operation (2.x publish or subscribe, a 3.0 operation), and its messages are left out. A channel whose operations are all hidden is left out with them. In 3.0, a message another visible operation also uses stays;
  • a message, or one kind in a payload oneOf, and that message is left out;
  • a field in a payload, the query or the headers, and it's left out of the tables and of every example. A path parameter stays, since the URL needs it.

A flag beside a $ref hides that use only; the same schema stays wherever it's used without one. Hidden channels, operations and messages are removed before anything is built, so they reach no page, search entry or sitemap: a hidden channel that would have been the only one on the WebSockets page takes that page with it. Hidden fields are left out of every page.

A channel, message or field marked deprecated: true stays, labelled "Deprecated": in the sidebar, in the resource's endpoint list, on the message's row, and beside the field. With api.showDeprecated set to false, deprecated channels and messages are left out, as deprecated operations are.

The WebSockets page and sections

The WebSockets page is a resource like the others, named WebSockets, so the settings that arrange the reference arrange it too:

  • api.tags: label renames it (its URL stays /api-reference/websockets), mergeInto puts its channels on another page, and hidden hides it with its channels.
  • api.tagOrder: list WebSockets to move it up. Otherwise a WebSockets page that only channels make comes after every other page. If the spec has a WebSockets tag of its own, or mergeInto names a page the spec already has, the channels join that page where it already is.
  • api.sections: list WebSockets in a section like any tag.
markline.json
{
  "api": {
    "tags": { "WebSockets": { "label": "Realtime" } },
    "sections": [
      { "title": "Speech", "tags": ["text-to-speech", "WebSockets"] }
    ]
  }
}

Without api.sections, the sidebar's sections come from the OpenAPI spec's x-tagGroups, then from the AsyncAPI document's own x-tagGroups, in the same [{ "name", "tags" }] form. A group with exactly the same name as one of the spec's joins it (names are matched as written, so Core and core are two groups), and a page stays in the first group that lists it. Sections come before pages in no section, so an AsyncAPI group that lists WebSockets moves it up to its group's place, even ahead of the spec's pages when the spec has no groups of its own. api.sections, even an empty list, replaces both.

Versions

In a versioned site, each version reads its own document, from its own folder: v1/api/asyncapi.json beside v1/api/openapi.json. api.asyncapi.file is relative to each version's folder. A version without a document has no channels.

A version can have an AsyncAPI document and no OpenAPI spec, for an API that's only WebSockets, or a site that's only that. Its reference is its channels: with no resources to join, they're on its WebSockets page (named, merged or sectioned as above), which its reference opens on. The version picker offers it like any version with a spec, and the build lists its pages and prints its warnings the same way.

content/
  api/openapi.json        # v2, the default: REST and WebSockets
  api/asyncapi.json
  v3/api/asyncapi.json    # v3: WebSockets only

Build output

A document Markline can't fully read never fails the build. What it can't read is left out, and the build prints one line about it, once (with the version, for a version other than the first):

  • a file that isn't JSON, or isn't valid JSON;
  • an AsyncAPI version other than 2.x or 3.0, or none;
  • a $ref that points outside the document, at nothing, or back at itself;
  • the client-side reading, when perspective is "client".

Not yet

  • YAML documents. Convert to JSON for now.
  • Channels in the static search index and in llms.txt. They are in the reference's own sidebar and page search.
  • Trying a channel from the page, the way Try it calls an operation.