Sessions
The single runtime contract: open a session against an agent, exchange messages, read its traces.
Overview
Anything that can call this API can trigger an Agent — Channels conversations, a customer cron, an inbound webhook, an automation tool. A session is durable and resumable, and every model loop it runs is recorded as a Generation.
See the OpenAPI spec for the full endpoint and schema reference, or browse it rendered under API Reference → Sessions.
Data Model
Session
| Field | Type | Description |
|---|---|---|
id | string | Public session ID (ses_ prefix). |
project_id | string | The owning project. |
agent_id | string | The agent this session runs. |
conversation_id | string, nullable | The Channels conversation this session maps to, if any. |
status | string | Session lifecycle status. |
name | string, nullable | Human-readable label. |
actor_id | string, nullable | The underlying runtime actor id. |
auto_generate | boolean | Whether adding a message automatically triggers a generation. |
tool_context | object, nullable | Per-call context forwarded to the agent's tools, each entry as an X-Naturali-Context-<key> header — see per-user credentials reach a tool as a header for the key rules and how to land a value in Authorization instead. |
inactivity_ttl_seconds | integer, nullable | Seconds of inactivity after which the session expires. |
message_delay_seconds | integer, nullable | Delay applied before a triggered generation runs, to batch fast-following messages. |
last_activity_at | string (date-time), nullable | |
created_at | string (date-time) | |
updated_at | string (date-time) |
SessionTranscriptMessage
| Field | Type | Description |
|---|---|---|
document_id | string, nullable | The runtime document holding the message text. |
role | string, nullable | user, assistant, system, or null. |
content | string, nullable | The message text. |
position | integer, nullable | Zero-based position in the session. |
actor_id | string, nullable | The actor who authored the message, when set. |
agent_id | string, nullable | The agent that produced the message, for assistant turns. |
metadata | object, nullable | Arbitrary metadata attached to the message. |
Key Concepts
A conversation maps 1:1 to a session
A Channels conversation maps one-to-one to a session; the session is what actually runs the agent turn.
Reading a session's transcript
GET /v1/projects/{project_id}/agents/{agent_id}/sessions/{session_id}/messages
reads the session's messages back, oldest first, by session id directly. Use
this for a session opened through
POST /v1/projects/{project_id}/agents/{agent_id}/sessions
— it has no Channels conversation, so
GET /v1/projects/{project_id}/channels/{channel_id}/conversations/{conversation_id}/messages
cannot reach it; both routes read the same underlying transcript.
Generating a response
POST /v1/projects/{project_id}/agents/{agent_id}/sessions/{session_id}/generate
runs the agent over the session's message history, producing a
Generation.
Like every turn that can outlast its request, it runs in the background by
default: the call answers 202 with { "status": "accepted", "session_id": "..." } and the reply lands in the transcript, where
GET /v1/projects/{project_id}/agents/{agent_id}/sessions/{session_id}/messages
picks it up. Pass wait=true to block and receive the turn inline instead —
see Generations → Background by default
for the rule and when each mode is the right one.
The handle names the session, not a generation, which is the one place this
differs from
POST /v1/projects/{project_id}/agents/{agent_id}/generations:
a background session turn is read from the transcript rather than polled by
generation id.
A session turn answers with message.content — a string, and the only place the
reply can go. An agent with an
output_schema is still constrained here, but
there is no object field on the response, so its JSON arrives inside that
string for the caller to parse. Bind schemas to agents you drive through
POST /v1/projects/{project_id}/agents/{agent_id}/generations
unless you want to parse message.content yourself.
Examples
- CLI
- SDK
- curl
naturali create-session \
--project-id proj_V1StGXR8Z5jdHi6B \
--agent-id agent_V1StGXR8Z5jdHi6B
const { data: session } = await naturali.sessions.createSession({
path: {
project_id: 'proj_V1StGXR8Z5jdHi6B',
agent_id: 'agent_V1StGXR8Z5jdHi6B',
},
});
curl -X POST https://api.naturali.ai/v1/projects/proj_V1StGXR8Z5jdHi6B/agents/agent_V1StGXR8Z5jdHi6B/sessions \
-H "Authorization: Bearer $NATURALI_API_KEY"
Reading the transcript
- CLI
- SDK
- curl
naturali list-session-messages \
--project-id proj_V1StGXR8Z5jdHi6B \
--agent-id agent_V1StGXR8Z5jdHi6B \
--session-id session_V1StGXR8Z5jdHi6B
const { data: transcript } = await naturali.sessions.listSessionMessages({
path: {
project_id: 'proj_V1StGXR8Z5jdHi6B',
agent_id: 'agent_V1StGXR8Z5jdHi6B',
session_id: 'session_V1StGXR8Z5jdHi6B',
},
});
curl https://api.naturali.ai/v1/projects/proj_V1StGXR8Z5jdHi6B/agents/agent_V1StGXR8Z5jdHi6B/sessions/session_V1StGXR8Z5jdHi6B/messages \
-H "Authorization: Bearer $NATURALI_API_KEY"