Messages
Endpoints for reading and writing message history within a session.
List messages
Returns the full ordered message history for the session.
/v1/messages/:sessionIdPath parameters
sessionIdstringrequiredThe session to read from.
Response
messagesMessage[]All messages in index order, including soft-deleted ones (use the
deletedAt field to filter them out client-side if needed).
See Concepts: Messages for the message shape.
Example
curl "$BASE_URL/v1/messages/$SESSION_ID" \
-H "Authorization: Bearer $TOKEN"Send a message
Posts a user message into the session and returns the full assistant reply once it's ready. Use this when you want a single JSON to log; use the streaming variant below when latency matters.
/v1/messages/:sessionIdPath parameters
sessionIdstringrequiredThe session to send into.
Request body
messagestringrequiredThe user's message text. Plain text only — formatting and attachments are not supported on this field.
metadataobjectOptional opaque metadata to attach to the user turn. Echoed back on subsequent reads. Useful for client-side trace IDs.
Response
The full message list after the assistant has replied. If the model
emitted a tool call, the last assistant message will contain a tool_use
block and you'll need to fulfill it before calling again — see
Tools.
Example
curl -X POST "$BASE_URL/v1/messages/$SESSION_ID" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "message": "Summarize the latest activity in this account." }'Stream a response
Same semantics as the above, but returns a streaming response so you can render tokens as they arrive. The stream delivers chunked JSON containing Bedrock conversation events — not SSE text/event-stream.
/v1/messages/:sessionId/streamPath parameters
sessionIdstringrequiredThe session to send into.
Request body
Identical to the non-streaming variant.
Response format
The response is a stream of JSON chunks. The top-level shape is:
{ "events": [ ...bedrockEvents ] }Each event in the array is a Bedrock conversation stream event. Parse them
with a streaming JSON parser (like @streamparser/json) using the path
$.events.* to receive events as they arrive.
Event types
messageStarteventSignals the beginning of a new message. Contains { role: 'assistant' }
(or 'user' for server-injected tool results).
contentBlockStarteventBeginning of a content block. For text, this is empty. For tool calls,
carries { start: { toolUse: { toolUseId, name } } }.
contentBlockDeltaeventA chunk of content. For text: { delta: { text: '...' } }. For tool
input: { delta: { toolUse: { input: '...' } } } (streamed JSON string).
contentBlockStopeventSignals the end of the current content block.
messageStopeventSignals the end of the current message. Contains
{ stopReason: 'end_turn' | 'tool_use' }. When stopReason is
tool_use, the model wants to call a tool — fulfill it via the
Tools API.
A single stream can contain multiple messages. For example, when the
server handles an internal tool call (like session titling), you'll see:
assistant message → tool result → assistant message, all in one stream.
Track message boundaries by watching for messageStart / messageStop
pairs.
Parsing the stream
Use @streamparser/json (or a similar streaming JSON parser) to process
chunks incrementally. Don't buffer the entire response — it defeats the
purpose of streaming.
import { JSONParser } from "@streamparser/json";
const res = await fetch(`${BASE_URL}/v1/messages/${sessionId}/stream`, {
method: "POST",
headers,
body: JSON.stringify({ message: "What changed in this record?" }),
});
const parser = new JSONParser({ paths: ["$.events.*"] });
let currentText = "";
parser.onValue = ({ value, partial }) => {
if (partial) return;
const event = value as Record<string, unknown>;
// New text chunk
if (event.contentBlockDelta) {
const delta = (event.contentBlockDelta as any).delta;
if (delta?.text) {
currentText += delta.text;
// Update your UI here
}
}
// Message complete
if (event.messageStop) {
console.log("Final text:", currentText);
}
};
// Read chunks from the response body
const reader = res.body!.getReader();
while (true) {
const { value, done } = await reader.read();
if (done) break;
parser.write(value);
}
parser.end();The @rfsmart/conversation-stream package wraps this parsing logic with
proper TypeScript types and event callbacks. See
Client Libraries if you'd
rather not implement stream parsing yourself.