Messages

Endpoints for reading and writing message history within a session.

List messages

Returns the full ordered message history for the session.

GET/v1/messages/:sessionId

Path parameters

sessionIdstringrequired

The 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.

POST/v1/messages/:sessionId

Path parameters

sessionIdstringrequired

The session to send into.

Request body

messagestringrequired

The user's message text. Plain text only — formatting and attachments are not supported on this field.

metadataobject

Optional 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.

POST/v1/messages/:sessionId/stream

Path parameters

sessionIdstringrequired

The 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

messageStartevent

Signals the beginning of a new message. Contains { role: 'assistant' } (or 'user' for server-injected tool results).

contentBlockStartevent

Beginning of a content block. For text, this is empty. For tool calls, carries { start: { toolUse: { toolUseId, name } } }.

contentBlockDeltaevent

A chunk of content. For text: { delta: { text: '...' } }. For tool input: { delta: { toolUse: { input: '...' } } } (streamed JSON string).

contentBlockStopevent

Signals the end of the current content block.

messageStopevent

Signals 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.

Info

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();
Tip

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.