AsyncAPI: Documenting Event-Driven APIs Like REST with OpenAPI
AI generated
{ }
GET
AsyncAPI · Events · Messaging
AsyncAPI for Event-Driven APIs
Documenting message queues and event streams as systematically as REST endpoints with OpenAPI

Once a system exchanges asynchronous messages over RabbitMQ, Kafka, or WebSockets, a description like OpenAPI's is missing: which messages flow over which channel, in what format, with what payload structure. AsyncAPI closes exactly that gap, with a specification deliberately modeled after OpenAPI.

15 min read AsyncAPI 3.0 · Channels Messaging · RabbitMQ · Kafka

1. The documentation gap for asynchronous APIs

OpenAPI has become the binding standard for synchronous REST endpoints: every endpoint, every parameter, every response structure is described in a machine-readable way. Once a system instead or additionally communicates over message queues, event streams, or WebSocket channels, that description is usually missing entirely. New team members have to read consumer and producer source code to figure out which messages with what structure travel over a queue.

AsyncAPI carries the OpenAPI principle over into this asynchronous world: a specification that describes channels (queues, topics), the messages exchanged over them, and their payload schema, independent of the concrete transport protocol. The syntax is deliberately modeled after OpenAPI, so teams already using OpenAPI for REST find their way around quickly.

2. The basic structure of an AsyncAPI specification

An AsyncAPI file defines channels as the central concept, comparable to paths in OpenAPI. A channel represents a queue, a topic, or a WebSocket channel over which messages flow. For each channel, it is described whether it is used for sending (publish), receiving (subscribe), or both, from the perspective of the respective application. The separation between channel definition (where a message lands) and operation definition (what a specific application does with it) has been explicit since AsyncAPI 3.0, resolving ambiguities from earlier versions of the specification.

Every message references a payload schema, defined in JSON Schema format just like with OpenAPI, making it reusable and validatable. Headers, correlation IDs for message tracing, and example payloads can additionally be specified, so a new team member can assemble a working message from the specification alone. Reusable components under components/messages and components/schemas prevent the same payload structure from being defined redundantly, and potentially inconsistently, in multiple places across the specification.


# order-events.asyncapi.yaml
asyncapi: 3.0.0
info:
  title: Order Events API
  version: 1.0.0
  description: Events around the order lifecycle in RabbitMQ

servers:
  production:
    host: rabbitmq.internal:5672
    protocol: amqp

channels:
  orderCreated:
    address: orders.created
    messages:
      orderCreatedMessage:
        $ref: '#/components/messages/OrderCreated'

operations:
  publishOrderCreated:
    action: send
    channel:
      $ref: '#/channels/orderCreated'

components:
  messages:
    OrderCreated:
      payload:
        type: object
        required: [orderId, customerId, totalAmount]
        properties:
          orderId:
            type: string
            format: uuid
          customerId:
            type: string
          totalAmount:
            type: number

3. Protocol agnosticism: one format for multiple transports

An important conceptual difference from OpenAPI: AsyncAPI is deliberately designed to be protocol-independent. The same basic structure describes channels over AMQP (RabbitMQ), Kafka, MQTT, WebSockets, or even HTTP webhooks, with protocol-specific bindings as an additional, optional layer per server and channel.

That is especially valuable for systems that change transport protocol over time or use several protocols in parallel, for example RabbitMQ for internal services and WebSockets for live updates in the browser. The functional description of the messages stays stable, only the protocol-specific bindings change.

4. Code generation: deriving consumers and producers from the spec

As with OpenAPI, generator tools exist (the AsyncAPI Generator) that can produce boilerplate code for consumers and producers in various languages from a specification, including typed payload classes. For PHP projects the ecosystem here is noticeably thinner than for OpenAPI, which is why the specification in practice often serves primarily as documentation rather than a codegen source.

Even without automatic code generation, the documentation value stays high: a Symfony team can use the AsyncAPI specification as a binding reference to hand-build payload DTOs that match the described schema, and validate against that schema in contract tests. Such a contract test fails in a controlled way in the CI pipeline as soon as a producer accidentally drifts from the documented schema, instead of the error only surfacing later through a consumer silently failing in production.


<?php
// PHP: payload DTO matching the AsyncAPI specification
final readonly class OrderCreatedMessage
{
    public function __construct(
        public string $orderId,
        public string $customerId,
        public float $totalAmount,
    ) {
    }

    public static function fromArray(array $data): self
    {
        // Validation against the schema defined in the AsyncAPI spec
        return new self(
            orderId: $data['orderId'],
            customerId: $data['customerId'],
            totalAmount: (float) $data['totalAmount'],
        );
    }
}

5. AsyncAPI Studio and a visual channel overview

AsyncAPI Studio (the counterpart to Swagger UI for OpenAPI) renders a specification as interactive, searchable documentation in the browser: all channels, messages, and schemas laid out clearly, with expandable example payloads. For teams with many message types across multiple queues, this replaces the tedious route through scattered wiki pages or outdated diagrams. The Studio can also run locally as a Docker container or be published statically straight from a GitHub repository, so external integrators get the same view as the team itself.

A visual architecture diagram can also be generated automatically from an AsyncAPI specification, showing which services publish and consume which channels. That makes implicit dependencies between services visible that would otherwise only be recognizable by reading through several codebases.

6. Versioning and breaking changes in event schemas

Breaking changes in event schemas are trickier than in REST, because there are often multiple independent consumers processing a message, without the producer knowing exactly who is listening. A removed or renamed property in a message can silently break consumers, without the error becoming visible at publish time itself.

AsyncAPI specifications should therefore, just like OpenAPI specifications, be versioned and checked for compatibility against the previous version in a CI step. Additive, backward-compatible changes (new optional fields) are uncritical, removing or renaming existing fields requires a new message version with a parallel transition period.

7. Combining AsyncAPI and OpenAPI in the same system landscape

Most real systems are hybrid: synchronous REST endpoints for direct requests, asynchronous events for state changes that interest other services. There is no contradiction in maintaining both specification formats in parallel in the same repository, each for the part of the API they are built for.

A sensible pattern is to publish both specifications in the same documentation portal, so an external integrator finds both the REST endpoints and the relevant event channels in one place, instead of jumping between different, disconnected documentation sources.

8. A practical starting point for an existing Symfony project

The pragmatic starting point is not a complete specification for all existing queues, but a single, well-understood message type as a proof of concept. The team learns the syntax along the way and can assess whether the documentation effort is worth the benefit before describing the entire event landscape.

After that, it pays off to couple maintenance of the specification directly to the code, for example through a CI check that flags a missing update to the AsyncAPI file whenever the message handler code changes. Without that coupling, the specification tends to go stale just as fast as unversioned wiki documentation.

9. AsyncAPI compared to other documentation approaches

Besides AsyncAPI, there are other ways to document event schemas, with different trade-offs between effort, tooling maturity, and expressiveness.

Approach Tooling maturity Protocol coverage Typical use
AsyncAPI Growing, good for docs AMQP, Kafka, MQTT, WebSocket Systematic event documentation
Schema Registry (e.g. Confluent) Mature for Kafka Primarily Kafka Kafka-centric landscapes with Avro/Protobuf
Hand-written wiki No tooling Any Small teams, few message types
Reading code only No tooling Any Anti-pattern, but common status quo

Mironsoft

OpenAPI design, Symfony APIs, and API security

APIs that external teams can integrate without back-and-forth questions?

We review existing REST APIs for inconsistent error formats, missing OpenAPI documentation, and security gaps, then build an API that is clearly documented, versioned, and hardened against abuse.

API Review

Checking the OpenAPI spec, error formats, and status codes for consistency.

Symfony Implementation

Using DTOs, Serializer, and Validator for clean, type-safe request/response models.

Security Audit

Hardening rate limiting, auth schemes, and input validation against real attack surfaces.

10. Summary

AsyncAPI: The Essentials at a Glance

Core idea

AsyncAPI carries the OpenAPI principle over to asynchronous communication: describing channels, messages, and payload schemas in a machine-readable way.

Protocol agnosticism

The same specification covers AMQP, Kafka, MQTT, and WebSockets, protocol-specific details are added as bindings.

Versioning

Event schemas need CI-checked compatibility rules, since breaking changes are especially tricky with multiple independent consumers.

Getting started

Start with a single, well-understood message type as a proof of concept, not documenting the entire event landscape at once.

11. FAQ: AsyncAPI: The Essentials at a Glance

1What is the difference between AsyncAPI and OpenAPI?
OpenAPI describes synchronous REST endpoints with request/response. AsyncAPI describes asynchronous communication over message queues, event streams, or WebSockets, with channels instead of paths as the central concept.
2Does AsyncAPI work with RabbitMQ?
Yes, RabbitMQ over AMQP is one of the supported protocol bindings. The basic structure of the specification stays protocol-independent, the AMQP binding only adds protocol-specific details like exchange types.
3Is there AsyncAPI code generation for PHP?
The ecosystem for PHP is noticeably thinner than for OpenAPI. In practice the specification more often serves as binding documentation for hand-written DTOs rather than a fully automatic codegen source.
4How do I display an AsyncAPI specification in the browser?
With AsyncAPI Studio, the counterpart to Swagger UI, which produces an interactive, searchable view of all channels and messages with example payloads.
5Can I use AsyncAPI and OpenAPI in the same project?
Yes, that is even the normal case in hybrid systems with both REST endpoints and event communication. Both specifications can be maintained in parallel in the same repository and documentation portal.
6How do I prevent the AsyncAPI spec from going stale?
Most reliably through a CI check that flags a missing update to the specification whenever the message handler code changes. Without that coupling it goes stale like any unversioned documentation.
7What happens with a breaking change in an event schema?
Since multiple independent consumers can process a message without the producer knowing all of them, removed or renamed fields often break consumers silently. A new message version with a transition period is the safe path.
8Is AsyncAPI worth it for a small project with few queues?
With very few, stable message types the added value over a short wiki note is limited. The benefit grows significantly with the number of message types and the number of teams consuming them.
9Does AsyncAPI also support Kafka?
Yes, Kafka is one of the best-supported protocol bindings, with specific extensions for Kafka concepts like partitioning and consumer groups.
10What is the best way to start with AsyncAPI in an existing project?
With a single, well-understood message type as a proof of concept, to learn the syntax and realistically assess the documentation effort before describing the entire event landscape.