AsyncAPI 2.6 description of Letta's **agent message streaming** surface. Letta does not publish a WebSocket API. The only asynchronous / event-style transport documented in Letta's OpenAPI spec (https://raw.githubusercontent.com/letta-ai/letta/main/fern/openapi.json) is **HTTP Server-Sent Events (SSE)**, returned as `text/event-stream` from `POST /v1/agents/{agent_id}/messages` when the request body sets `stream_steps` (or the legacy, now-deprecated `POST /v1/agents/{agent_id}/messages/stream` endpoint). The OpenAI-compatible `POST /v1/chat/completions` endpoint streams the same way when `stream: true` is set. SSE is a one-way, server-to-client HTTP streaming channel; it is **not** WebSocket. Each SSE `data:` line carries one JSON-serialized Letta message object - `reasoning_message`, `assistant_message`, `tool_call_message`, `tool_return_message`, a periodic `ping` keep-alive, or (when `stream_tokens` and usage reporting are requested) a final usage statistics object - followed by a literal `data: [DONE]` terminator. This AsyncAPI document models only the streamed events. The request body itself (agent_id, messages, stream_steps, stream_tokens, etc.) is modeled in the companion OpenAPI document at `openapi/letta-ai-openapi.yml`.
Agent message SSE stream. The client opens this channel by issuing `POST /agents/{agent_id}/messages` with `Content-Type: application/json` and a JSON body containing `stream_steps: true` (add `stream_tokens: true` to additionally stream partial assistant-message tokens). The server responds with `Content-Type: text/event-stream` and emits an ordered sequence of `data:` lines, each carrying one JSON-serialized Letta message, followed by a final `data: [DONE]` line.
Messages
✉
ReasoningMessage
Agent reasoning step
The agent's internal chain-of-thought for the current step, when reasoning is enabled and visible.
✉
ToolCallMessage
Agent tool call
A tool invocation the agent is making as part of this step.
✉
ToolReturnMessage
Agent tool result
The result returned by a tool the agent called.
✉
AssistantMessage
Agent reply to the user
The agent's user-facing reply content for this turn. Streamed token-by-token when stream_tokens is set.
✉
LettaPing
Keep-alive ping
A periodic keep-alive event so the SSE connection does not time out during long-running turns.
✉
UsageStatistics
End-of-turn usage statistics
Optional final chunk reporting token and step usage for the completed turn.
✉
StreamDone
Stream terminator
The literal SSE event `data: [DONE]` that marks end of stream. Not JSON; the payload is the string `[DONE]`.
Servers
https
lettaCloudapi.letta.com/v1
Letta Cloud's REST base. Agent message streaming is delivered as HTTP Server-Sent Events over this base when `stream_steps: true` (and optionally `stream_tokens: true`) is set on the JSON request body. AsyncAPI 2.6 does not define a dedicated SSE protocol identifier; `https` is used here and the SSE transport is documented in `info.x-transport-notes` and on each channel.
asyncapi: '2.6.0'
id: 'urn:com:letta:api:v1:agents:messages:sse'
info:
title: Letta Agent Message Streaming (HTTP + SSE)
version: '1.0.0'
description: |
AsyncAPI 2.6 description of Letta's **agent message streaming** surface.
Letta does not publish a WebSocket API. The only asynchronous / event-style
transport documented in Letta's OpenAPI spec
(https://raw.githubusercontent.com/letta-ai/letta/main/fern/openapi.json)
is **HTTP Server-Sent Events (SSE)**, returned as `text/event-stream` from
`POST /v1/agents/{agent_id}/messages` when the request body sets
`stream_steps` (or the legacy, now-deprecated `POST
/v1/agents/{agent_id}/messages/stream` endpoint). The OpenAI-compatible
`POST /v1/chat/completions` endpoint streams the same way when
`stream: true` is set. SSE is a one-way, server-to-client HTTP streaming
channel; it is **not** WebSocket.
Each SSE `data:` line carries one JSON-serialized Letta message object -
`reasoning_message`, `assistant_message`, `tool_call_message`,
`tool_return_message`, a periodic `ping` keep-alive, or (when
`stream_tokens` and usage reporting are requested) a final usage
statistics object - followed by a literal `data: [DONE]` terminator.
This AsyncAPI document models only the streamed events. The request body
itself (agent_id, messages, stream_steps, stream_tokens, etc.) is modeled
in the companion OpenAPI document at `openapi/letta-ai-openapi.yml`.
contact:
name: API Evangelist
email: kin@apievangelist.com
url: https://apievangelist.com
license:
name: API documentation - Letta Terms of Service
url: https://www.letta.com/terms-of-service
x-transport-notes:
transport: HTTP Server-Sent Events (SSE)
protocol: https
direction: server-to-client (one-way)
mediaType: text/event-stream
triggeredBy: 'POST https://api.letta.com/v1/agents/{agent_id}/messages with request body { "stream_steps": true }'
terminator: 'data: [DONE]'
notWebSocket: true
source: https://raw.githubusercontent.com/letta-ai/letta/main/fern/openapi.json
defaultContentType: text/event-stream
servers:
lettaCloud:
url: api.letta.com/v1
protocol: https
description: |
Letta Cloud's REST base. Agent message streaming is delivered as HTTP
Server-Sent Events over this base when `stream_steps: true` (and
optionally `stream_tokens: true`) is set on the JSON request body.
AsyncAPI 2.6 does not define a dedicated SSE protocol identifier;
`https` is used here and the SSE transport is documented in
`info.x-transport-notes` and on each channel.
security:
- bearerAuth: []
channels:
/agents/{agent_id}/messages:
parameters:
agent_id:
description: The ID of the agent, in the format `agent-<uuid4>`.
schema:
type: string
description: |
Agent message SSE stream. The client opens this channel by issuing
`POST /agents/{agent_id}/messages` with `Content-Type: application/json`
and a JSON body containing `stream_steps: true` (add `stream_tokens: true`
to additionally stream partial assistant-message tokens). The server
responds with `Content-Type: text/event-stream` and emits an ordered
sequence of `data:` lines, each carrying one JSON-serialized Letta
message, followed by a final `data: [DONE]` line.
bindings:
http:
type: request
method: POST
bindingVersion: '0.3.0'
x-sse:
mediaType: text/event-stream
eventField: 'data'
terminator: '[DONE]'
subscribe:
operationId: streamAgentMessages
summary: Subscribe to streamed agent turn events (SSE).
description: |
After `POST /agents/{agent_id}/messages` is issued with
`stream_steps: true`, the server emits an ordered sequence of SSE
`data:` events representing the agent's reasoning, tool calls, tool
results, and final assistant reply for the turn, interleaved with
periodic `ping` keep-alives, and terminated by the literal sentinel
`[DONE]`.
bindings:
http:
type: response
bindingVersion: '0.3.0'
message:
oneOf:
- $ref: '#/components/messages/ReasoningMessage'
- $ref: '#/components/messages/ToolCallMessage'
- $ref: '#/components/messages/ToolReturnMessage'
- $ref: '#/components/messages/AssistantMessage'
- $ref: '#/components/messages/LettaPing'
- $ref: '#/components/messages/UsageStatistics'
- $ref: '#/components/messages/StreamDone'
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: 'Letta API key'
description: |
Standard Letta bearer token, created at https://app.letta.com/api-keys.
Set the `Authorization: Bearer <LETTA_API_KEY>` header on the
`POST /agents/{agent_id}/messages` request that opens the SSE stream.
messages:
ReasoningMessage:
name: ReasoningMessage
title: Agent reasoning step
summary: The agent's internal chain-of-thought for the current step, when reasoning is enabled and visible.
contentType: application/json
description: |
Sent as `data: {json}\n\n` on the SSE stream. `message_type` is always
the literal string `reasoning_message`.
payload:
$ref: '#/components/schemas/ReasoningMessage'
examples:
- name: reasoningStep
summary: Agent reasoning before acting
payload:
id: message-6f7e8d9c-0000-4a1b-9c2d-3e4f5a6b7c8d
date: '2026-07-02T18:04:11.000Z'
message_type: reasoning_message
reasoning: The user is asking about their prior order, I should check archival memory first.
source: reasoner_model
ToolCallMessage:
name: ToolCallMessage
title: Agent tool call
summary: A tool invocation the agent is making as part of this step.
contentType: application/json
description: |
Sent as `data: {json}\n\n` on the SSE stream. `message_type` is always
the literal string `tool_call_message`.
payload:
$ref: '#/components/schemas/ToolCallMessage'
examples:
- name: archivalSearch
summary: Agent calling the archival_memory_search tool
payload:
id: message-1a2b3c4d-0000-4a1b-9c2d-3e4f5a6b7c8d
date: '2026-07-02T18:04:11.500Z'
message_type: tool_call_message
tool_call:
name: archival_memory_search
arguments: '{"query": "prior order"}'
tool_call_id: call-9f8e7d6c
ToolReturnMessage:
name: ToolReturnMessage
title: Agent tool result
summary: The result returned by a tool the agent called.
contentType: application/json
description: |
Sent as `data: {json}\n\n` on the SSE stream. `message_type` is always
the literal string `tool_return_message`.
payload:
$ref: '#/components/schemas/ToolReturnMessage'
examples:
- name: archivalSearchResult
summary: Result of the archival_memory_search call
payload:
id: message-2b3c4d5e-0000-4a1b-9c2d-3e4f5a6b7c8d
date: '2026-07-02T18:04:11.900Z'
message_type: tool_return_message
tool_return: 'Found 1 matching passage.'
status: success
tool_call_id: call-9f8e7d6c
AssistantMessage:
name: AssistantMessage
title: Agent reply to the user
summary: The agent's user-facing reply content for this turn. Streamed token-by-token when stream_tokens is set.
contentType: application/json
description: |
Sent as `data: {json}\n\n` on the SSE stream. `message_type` is always
the literal string `assistant_message`.
payload:
$ref: '#/components/schemas/AssistantMessage'
examples:
- name: assistantReply
summary: Final assistant-facing reply
payload:
id: message-3c4d5e6f-0000-4a1b-9c2d-3e4f5a6b7c8d
date: '2026-07-02T18:04:12.400Z'
message_type: assistant_message
content: 'Your prior order shipped on June 30th.'
LettaPing:
name: LettaPing
title: Keep-alive ping
summary: A periodic keep-alive event so the SSE connection does not time out during long-running turns.
contentType: application/json
description: |
Sent as `data: {json}\n\n` on the SSE stream. `message_type` is always
the literal string `ping`. Carries no meaningful content beyond `id`
and `date`.
payload:
$ref: '#/components/schemas/LettaPing'
examples:
- name: keepAlive
summary: Keep-alive ping during a long-running tool call
payload:
id: message-ping-0001
date: '2026-07-02T18:04:20.000Z'
message_type: ping
UsageStatistics:
name: UsageStatistics
title: End-of-turn usage statistics
summary: Optional final chunk reporting token and step usage for the completed turn.
contentType: application/json
description: |
Sent as `data: {json}\n\n` on the SSE stream, immediately before
`data: [DONE]`, when usage reporting applies to the request.
payload:
$ref: '#/components/schemas/LettaUsageStatistics'
examples:
- name: turnUsage
summary: Usage for the completed turn
payload:
message_type: usage_statistics
completion_tokens: 128
prompt_tokens: 512
total_tokens: 640
step_count: 3
StreamDone:
name: StreamDone
title: Stream terminator
summary: |
The literal SSE event `data: [DONE]` that marks end of stream. Not
JSON; the payload is the string `[DONE]`.
contentType: text/plain
description: |
Clients must stop reading the stream when this sentinel is observed.
payload:
$ref: '#/components/schemas/StreamDoneSentinel'
examples:
- name: done
summary: End-of-stream sentinel
payload: '[DONE]'
schemas:
StreamDoneSentinel:
type: string
enum:
- '[DONE]'
description: |
End-of-stream sentinel. The full SSE line is `data: [DONE]`. The
payload value modeled here is the string literal `[DONE]`.
ReasoningMessage:
type: object
description: The agent's internal reasoning for the current step.
required:
- id
- date
- reasoning
properties:
id:
type: string
date:
type: string
format: date-time
message_type:
type: string
enum:
- reasoning_message
default: reasoning_message
reasoning:
type: string
source:
type: string
description: Which model/pass produced this reasoning (for example, a dedicated reasoner model).
signature:
type: string
nullable: true
description: Optional provider-signed signature for the reasoning content.
ToolCallMessage:
type: object
required:
- id
- date
- tool_call
properties:
id:
type: string
date:
type: string
format: date-time
message_type:
type: string
enum:
- tool_call_message
default: tool_call_message
tool_call:
type: object
properties:
name:
type: string
arguments:
type: string
description: JSON-encoded arguments the model generated for the call.
tool_call_id:
type: string
ToolReturnMessage:
type: object
required:
- id
- date
- tool_return
- status
- tool_call_id
properties:
id:
type: string
date:
type: string
format: date-time
message_type:
type: string
enum:
- tool_return_message
default: tool_return_message
tool_return:
type: string
status:
type: string
enum:
- success
- error
tool_call_id:
type: string
stdout:
type: array
items:
type: string
stderr:
type: array
items:
type: string
AssistantMessage:
type: object
required:
- id
- date
- content
properties:
id:
type: string
date:
type: string
format: date-time
message_type:
type: string
enum:
- assistant_message
default: assistant_message
content:
type: string
description: The agent's user-facing reply text (or a token fragment, when stream_tokens is set).
LettaPing:
type: object
required:
- id
- date
properties:
id:
type: string
date:
type: string
format: date-time
message_type:
type: string
enum:
- ping
default: ping
description: Keep-alive event to prevent SSE streams from timing out during long-running requests.
LettaUsageStatistics:
type: object
description: Token and step usage for a completed agent turn.
properties:
message_type:
type: string
enum:
- usage_statistics
completion_tokens:
type: integer
prompt_tokens:
type: integer
total_tokens:
type: integer
step_count:
type: integer
description: Number of model-call steps the agent took to complete the turn.
cached_input_tokens:
type: integer
reasoning_tokens:
type: integer