Documentation

Read and extend conversations

Session-memory conversations are stored in ModelRiver. Read them, add human handoff turns, and reset them from your backend.

When a workflow has Session memory enabled, every turn sent with a session_id is stored in ModelRiver (see Session memory). The Sessions API lets your backend read those conversations and add turns to them, so you don't need to keep your own copy of the chat history.

Typical uses:

  • Show a user's past conversations in your app.
  • Hand a chat over to a human agent: store the user's messages and the agent's replies in the same session, so the AI sees what the agent said when it takes over again.
  • Show new turns the moment they happen, in your app or your agents' dashboard, with live updates.
  • Reset a conversation by deleting its session.

All endpoints use the same project API key as /v1/ai (Authorization: Bearer mr_live_…) and only see sessions in that key's project. A session from another project, or an id that isn't a UUID, returns 404 {"error": "Session not found"}.


List sessions

HTTP
1GET /v1/sessions?workflow=support_bot&limit=20&page=1
ParameterDefaultNotes
workflowallOnly sessions of the workflow with this name
limit20Page size, at most 100
page11-based page number

Sessions are sorted by most recent activity first.

JSON
1{
2 "data": [
3 {
4 "id": "9f8c2c1e-5b8a-4d0e-9a51-2f6f0f1d7a10",
5 "workflow": "support_bot",
6 "message_count": 6,
7 "last_activity_at": "2026-10-25T09:14:03.512Z",
8 "inserted_at": "2026-10-25T08:58:41.007Z",
9 "last_message": {
10 "role": "assistant",
11 "content": "Your refund was issued today.",
12 "at": "2026-10-25T09:14:03.498Z"
13 }
14 }
15 ],
16 "page": 1,
17 "limit": 20
18}

message_count counts stored turns: each AI reply and each appended message counts as one. last_message is null when the latest turn has no readable text.


Read a session

HTTP
1GET /v1/sessions/:id
JSON
1{
2 "id": "9f8c2c1e-5b8a-4d0e-9a51-2f6f0f1d7a10",
3 "workflow": "support_bot",
4 "inserted_at": "2026-10-25T08:58:41.007Z",
5 "last_activity_at": "2026-10-25T09:14:03.512Z",
6 "messages": [
7 {
8 "id": "4b1d…:user",
9 "role": "user",
10 "author": "user",
11 "author_name": null,
12 "content": "I was charged twice for order 1234",
13 "created_at": "2026-10-25T08:58:40.112Z"
14 },
15 {
16 "id": "4b1d…:assistant",
17 "role": "assistant",
18 "author": "ai",
19 "author_name": null,
20 "content": "Sorry about that. Let me connect you with our billing team.",
21 "created_at": "2026-10-25T08:58:41.007Z"
22 },
23 {
24 "id": "7e02…:assistant",
25 "role": "assistant",
26 "author": "agent",
27 "author_name": "Dana",
28 "content": "Hi, I'm Dana. I've refunded the duplicate charge.",
29 "created_at": "2026-10-25T09:14:03.498Z"
30 }
31 ],
32 "truncated": false
33}

Messages are sorted oldest first.

FieldDescription
idStable message id. Treat it as an opaque string.
roleuser or assistant
authoruser, ai (a model reply) or agent (a human reply appended with this API)
author_nameThe author_name sent when the message was appended, otherwise null
created_atWhen the turn was stored
truncatedtrue when the session has more than the 100 most recent turns returned here

Only the conversation is returned. System prompts, injected session memory and ModelRiver's built-in safety instructions are left out, and so are replies from failed requests. Turns are kept for your plan's log retention period.


Append a message

Stores a turn in the session without calling a model. Nothing is billed and it doesn't count as a request.

HTTP
1POST /v1/sessions/:id/messages
2Content-Type: application/json
3 
4{ "role": "assistant", "content": "Hi, I'm Dana. I've refunded the duplicate charge.", "author_name": "Dana" }
FieldRequiredNotes
role✅user stores a user message; assistant stores a human agent's reply (author: "agent")
content✅Non-empty text, up to 100,000 characters
author_name❌Display name, up to 200 characters

Response 201:

JSON
1{
2 "id": "7e02…:assistant",
3 "role": "assistant",
4 "author": "agent",
5 "author_name": "Dana",
6 "content": "Hi, I'm Dana. I've refunded the duplicate charge.",
7 "created_at": "2026-10-25T09:14:03.498Z"
8}

Appended turns become part of session memory. The next /v1/ai request with this session_id sees the user messages and agent replies as earlier conversation, so the AI can continue where the agent left off.

A missing or invalid field returns 400 with an error message. Appending also returns 400 when the project has stopped storing request bodies, because session content can't be saved.


Live updates

Follow a session as it happens instead of polling: new turns, events your app sends, and deletion arrive over the same WebSocket that streams async responses.

  1. Your backend asks for a one-time token for the session:

    HTTP
    1POST /v1/sessions/:id/subscribe
    JSON
    1{
    2 "session_id": "4b1d…",
    3 "ws_token": "Qm9i…",
    4 "websocket_url": "wss://api.modelriver.com/socket",
    5 "websocket_channel": "session:7c2e…:4b1d…",
    6 "expires_in": 300
    7}
  2. Your frontend joins websocket_channel with ws_token. The client SDK does this, and asks your backend for a new token whenever it reconnects:

    TYPESCRIPT
    1import { subscribeToSession } from '@modelriver/client';
    2 
    3const subscription = subscribeToSession({
    4 // Your backend route that calls POST /v1/sessions/:id/subscribe
    5 getSubscription: () => fetch(`/api/chat/${chatId}/live`, { method: 'POST' }).then((r) => r.json()),
    6 onMessage: (message) => addToChat(message),
    7 onSignal: (signal) => { if (signal.name === 'agent.joined') showAgent(signal.data); },
    8 onDeleted: () => closeChat(),
    9});
    10 
    11// Later
    12subscription.close();

A token works once, only for that session, and expires after five minutes if unused. Connections close after an hour; the SDK reconnects with a fresh token. Ask for tokens only for users who may see the conversation, since the channel carries its messages.

Events

EventPayloadWhen
messageA message as in Read a session, plus session_idA /v1/ai request in the session succeeds (its user message, then the reply), or a turn is appended
signal{ "session_id", "name", "data", "sent_at" }Your backend sends a signal
deleted{ "session_id" }The session is deleted

Failed requests and test-mode runs are not announced. Message ids match the ones GET /v1/sessions/:id returns, so a client can merge live messages into a history it loaded earlier without duplicates. Events sent while a client is disconnected are not replayed: after reconnecting, read the session again to catch up (the SDK's onLive callback tells you when).

Send a signal

Signals tell subscribers about things that aren't conversation turns, such as "an agent joined", "the chat was closed" or "someone is typing". They are delivered to whoever is connected and never stored, so they don't appear in the transcript or in session memory.

HTTP
1POST /v1/sessions/:id/signals
2Content-Type: application/json
3 
4{ "name": "agent.joined", "data": { "name": "Dana" } }
FieldRequiredNotes
name✅1–64 characters: lowercase letters, digits, _ . : -
data❌Any JSON, up to 4 KB

Response 202: { "status": "sent" }.


Delete a session

HTTP
1DELETE /v1/sessions/:id
JSON
1{ "message": "success", "status": "deleted" }

Deleting a session removes its id and memory and sends deleted to its live subscribers, the same as deleting it in the console under Logs → Sessions. Request logs stay for audit purposes. Send the next /v1/ai request without session_id to start a new conversation. On a protected project, deleting returns 403 project_protected; delete the session from the console as an organization owner or admin instead.


Next steps