Summary
Fern does not parse or render the AsyncAPI 3.0 Operation Reply Object. The reply field on Operation Objects is silently ignored during import, meaning correlated responses for request/reply (RPC-style) operations are absent from rendered WebSocket documentation pages.
The reply Object in AsyncAPI 3.0
The reply field was introduced in AsyncAPI 3.0.0 as a first-class, optional property on the Operation Object (JSON Schema source). It enables the request/reply pattern — the spec describes it as:
"Describes the reply part that MAY be applied to an Operation Object. If an operation implements the request/reply pattern, the reply object represents the response message."
It has its own dedicated schema (Operation Reply Object) with three properties:
| Field |
Type |
Description |
address |
Operation Reply Address Object | Reference |
Where to send the reply (runtime expression) |
channel |
Reference Object |
Which channel carries the reply |
messages |
[Reference Object] |
Which message schemas are valid replies |
This field did not exist in AsyncAPI 2.x. It was added in 3.0.0 specifically to model protocols like JSON-RPC, AMQP RPC, and any request/response pattern over messaging.
Why reply Matters — It Is Not the Same as receive
AsyncAPI 3.0 distinguishes two fundamentally different server-to-client message patterns:
reply (on a send operation): A correlated response to a specific request. The reply only exists because the client sent a request — it is causally linked, 1:1.
operations:
callingDial:
action: send
channel: { $ref: "#/channels/callingDial" }
messages:
- $ref: "#/channels/callingDial/messages/callingDialRequest"
reply:
channel: { $ref: "#/channels/callingDial" }
messages:
- $ref: "#/channels/callingDial/messages/callingDialResponse"
receive (standalone operation): An autonomous, unsolicited event from the server. It can arrive at any time with no correlation to a prior request.
operations:
onCallStateChanged:
action: receive
channel: { $ref: "#/channels/calling" }
messages:
- $ref: "#/channels/calling/messages/callStateEvent"
| Aspect |
send + reply |
receive |
| Trigger |
Direct response to a specific request |
Server pushes independently |
| Correlation |
1:1 with the send operation |
None |
| Semantic meaning |
"Call this, get that back" |
"Listen for these events" |
| Analogous to |
HTTP request → response |
Webhook / SSE event |
Without reply support, there is no way for a spec author to express this distinction. Both correlated responses and autonomous events are flattened into receive, losing the request/reply semantics that the spec was designed to convey.
Current Behavior
Given this spec:
asyncapi: 3.0.0
info:
title: RPC Test API
version: 1.0.0
servers:
production:
host: ws.example.com
protocol: wss
channels:
test:
address: /
messages:
commandRequest:
payload:
type: object
properties:
method: { type: string, const: "test.command" }
params: { type: object }
commandResponse:
payload:
type: object
properties:
result: { type: object }
asyncEvent:
payload:
type: object
properties:
event_type: { type: string, const: "test.event" }
data: { type: object }
operations:
testCommand:
action: send
channel: { $ref: "#/channels/test" }
messages:
- $ref: "#/channels/test/messages/commandRequest"
reply:
channel: { $ref: "#/channels/test" }
messages:
- $ref: "#/channels/test/messages/commandResponse"
onAsyncEvent:
action: receive
channel: { $ref: "#/channels/test" }
messages:
- $ref: "#/channels/test/messages/asyncEvent"
What renders:
- ✅ Send section →
commandRequest (the request)
- ✅ Receive section →
asyncEvent (the autonomous event)
- ❌
commandResponse does not render anywhere — it is completely absent from the page
The reply block and its referenced message are silently dropped.
Additionally, if the reply targets a different channel (e.g., a ping operation replies on a pong channel), the reply channel is skipped entirely with: Skipping AsyncAPI channel pong as it does not qualify for inclusion (no headers, query params, or operations). This happens because no send/receive operation references the reply-only channel.
Workaround and Why It Is Insufficient
The only workaround is to model the response as a separate receive operation:
operations:
testCommand:
action: send
messages: [commandRequest]
onTestCommandResponse: # Workaround — pretend the response is an event
action: receive
messages: [commandResponse]
onAsyncEvent:
action: receive
messages: [asyncEvent]
This renders both messages, but introduces two problems:
-
Semantic loss: onTestCommandResponse and onAsyncEvent are now structurally identical — both are receive operations. The rendered page shows them both under "Receive" with no distinction. A reader cannot tell which is a correlated response to testCommand and which is an independent event. For APIs with many RPC methods plus async events on the same channel, this is confusing.
-
Spec incorrectness: The receive workaround tells consumers "this message can arrive at any time, unprompted" — which is false for a correlated response. Any tooling that processes the spec (SDK generators, validators, documentation) will treat it as an autonomous event. Spec authors are forced to misrepresent their API semantics to get documentation to render.
-
Duplication: To keep the spec correct for non-Fern tooling, authors must maintain BOTH the reply block (for spec compliance) AND a duplicate receive operation (for Fern rendering) — doubling the operation count per RPC method.
Expected Behavior
-
Parse the reply field from AsyncAPI 3.0 Operation Objects during import.
-
Render reply messages on the WebSocket channel page, visually distinct from receive messages. For example:
┌─────────────────────────────────────┐
│ test.command │
├─────────────────────────────────────┤
│ ► Send │
│ commandRequest │
│ │
│ ◄ Reply │
│ commandResponse │
│ │
│ ◄ Receive │
│ asyncEvent │
└─────────────────────────────────────┘
The Reply section communicates "this is what you get back when you send the request." The Receive section communicates "these events may arrive independently."
-
Support cross-channel replies: When reply.channel references a different channel than the operation, include the reply messages on the operation's page rather than skipping the reply channel.
Real-World Use Case
This is a practical blocker for documenting JSON-RPC over WebSocket APIs. Any protocol where the client sends a JSON-RPC request and receives a correlated JSON-RPC response, plus independent server-pushed events over the same connection, needs reply support. APIs with 50+ RPC methods are especially affected — each method's response is either missing from docs or incorrectly modeled as an autonomous event. The reply object is the spec-correct way to model this, but Fern's lack of support forces authors to either:
- Drop the response from docs entirely (bad DX)
- Model responses as
receive events (semantically wrong, confusing for readers)
- Maintain duplicate operations (spec bloat)
Spec References
Summary
Fern does not parse or render the AsyncAPI 3.0 Operation Reply Object. The
replyfield on Operation Objects is silently ignored during import, meaning correlated responses for request/reply (RPC-style) operations are absent from rendered WebSocket documentation pages.The
replyObject in AsyncAPI 3.0The
replyfield was introduced in AsyncAPI 3.0.0 as a first-class, optional property on the Operation Object (JSON Schema source). It enables the request/reply pattern — the spec describes it as:It has its own dedicated schema (Operation Reply Object) with three properties:
addresschannelmessagesThis field did not exist in AsyncAPI 2.x. It was added in 3.0.0 specifically to model protocols like JSON-RPC, AMQP RPC, and any request/response pattern over messaging.
Why
replyMatters — It Is Not the Same asreceiveAsyncAPI 3.0 distinguishes two fundamentally different server-to-client message patterns:
reply(on asendoperation): A correlated response to a specific request. The reply only exists because the client sent a request — it is causally linked, 1:1.receive(standalone operation): An autonomous, unsolicited event from the server. It can arrive at any time with no correlation to a prior request.send+replyreceiveWithout
replysupport, there is no way for a spec author to express this distinction. Both correlated responses and autonomous events are flattened intoreceive, losing the request/reply semantics that the spec was designed to convey.Current Behavior
Given this spec:
What renders:
commandRequest(the request)asyncEvent(the autonomous event)commandResponsedoes not render anywhere — it is completely absent from the pageThe
replyblock and its referenced message are silently dropped.Additionally, if the
replytargets a different channel (e.g., apingoperation replies on apongchannel), the reply channel is skipped entirely with:Skipping AsyncAPI channel pong as it does not qualify for inclusion (no headers, query params, or operations). This happens because nosend/receiveoperation references the reply-only channel.Workaround and Why It Is Insufficient
The only workaround is to model the response as a separate
receiveoperation:This renders both messages, but introduces two problems:
Semantic loss:
onTestCommandResponseandonAsyncEventare now structurally identical — both arereceiveoperations. The rendered page shows them both under "Receive" with no distinction. A reader cannot tell which is a correlated response totestCommandand which is an independent event. For APIs with many RPC methods plus async events on the same channel, this is confusing.Spec incorrectness: The
receiveworkaround tells consumers "this message can arrive at any time, unprompted" — which is false for a correlated response. Any tooling that processes the spec (SDK generators, validators, documentation) will treat it as an autonomous event. Spec authors are forced to misrepresent their API semantics to get documentation to render.Duplication: To keep the spec correct for non-Fern tooling, authors must maintain BOTH the
replyblock (for spec compliance) AND a duplicatereceiveoperation (for Fern rendering) — doubling the operation count per RPC method.Expected Behavior
Parse the
replyfield from AsyncAPI 3.0 Operation Objects during import.Render reply messages on the WebSocket channel page, visually distinct from
receivemessages. For example:The Reply section communicates "this is what you get back when you send the request." The Receive section communicates "these events may arrive independently."
Support cross-channel replies: When
reply.channelreferences a different channel than the operation, include the reply messages on the operation's page rather than skipping the reply channel.Real-World Use Case
This is a practical blocker for documenting JSON-RPC over WebSocket APIs. Any protocol where the client sends a JSON-RPC request and receives a correlated JSON-RPC response, plus independent server-pushed events over the same connection, needs
replysupport. APIs with 50+ RPC methods are especially affected — each method's response is either missing from docs or incorrectly modeled as an autonomous event. Thereplyobject is the spec-correct way to model this, but Fern's lack of support forces authors to either:receiveevents (semantically wrong, confusing for readers)Spec References
replyas an optional fieldreplyis a valid property