Skip to main content
This guide walks through every step required to interact with the Session Thread API. It assumes no prior knowledge and shows how to obtain credentials, create threads, send messages, and process streaming events.

1. Obtain credentials

  1. Sign in to the Envole dashboard.
  2. Navigate to API Keys and create a new key.
  3. Choose or generate a unique identifier for the end user. This value will be sent in the required X-Envole-User-Id header on every request.
  4. Store both values in environment variables:
export ENVOLE_API_KEY="sk-your-key"
export ENVOLE_USER_ID="user-123"
All API calls must include two headers:
  • Authorization: Bearer <API_KEY>
  • X-Envole-User-Id: <user-id>

2. Start a session

Create a thread for your assistant. Replace placeholders with your real values.
curl -X POST \
  -H "Authorization: Bearer $ENVOLE_API_KEY" \
  -H "X-Envole-User-Id: $ENVOLE_USER_ID" \
  -H "Content-Type: application/json" \
  -d '{"agentId": "agent_123"}' \
  /api/assistants/threads
The response includes an id field representing the thread identifier used in subsequent calls.

3. Post a message and stream events

Messages are sent to the thread and responses are streamed back using Server‑Sent Events. The request body now uses a content array where each item describes a piece of the message, such as text or files.

TypeScript / JavaScript

const API_KEY = process.env.ENVOLE_API_KEY!;
const threadId = "YOUR_THREAD_ID";
const userId = process.env.ENVOLE_USER_ID!;

async function main() {
  // Send the user's message and stream back the assistant events
  const r = await fetch(`/api/assistants/threads/${threadId}/messages`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${API_KEY}`,
      "X-Envole-User-Id": userId,
    },
    body: JSON.stringify({
      content: [
        { type: "text", text: "Hi what can you do for me?" },
      ],
    }),
  });

  if (!r.ok) {
    console.error("Request failed", await r.json());
    throw new Error(`Request failed ${r.status}`);
  }

  const reader = r.body!.getReader();
  const decoder = new TextDecoder();
  let buffer: string[] = [];

  try {
    while (true) {
      const { done, value } = await reader.read();
      if (done) break;

      const chunk = decoder.decode(value);
      for (let i = 0; i < chunk.length; i++) {
        const ch = chunk[i];

        if (ch !== "\n") {
          buffer.push(ch);
          continue;
        }

        const line = buffer.join("").trimEnd();
        buffer = [];
        if (!line.startsWith("data:")) continue;

        const data = JSON.parse(line.slice(5).trim());
        const { type, eventMessage } = data;

        if (type === "AGENT_RESPONSE_CHUNK") {
          process.stdout.write(eventMessage.content ?? "");
        } else if (type === "AGENT_RESPONSE_COMPLETE") {
          console.log("\nDone");
        }
      }
    }
  } finally {
    reader.releaseLock();
  }
}

main().catch(console.error);

Python

import json
import os
import requests

API_KEY = os.environ["ENVOLE_API_KEY"]
thread_id = "YOUR_THREAD_ID"
user_id = os.environ["ENVOLE_USER_ID"]

def main() -> None:
    r = requests.post(
        f"/api/assistants/threads/{thread_id}/messages",
        headers={
            "Authorization": f"Bearer {API_KEY}",
            "X-Envole-User-Id": user_id,
            "Content-Type": "application/json",
        },
        json={
            "content": [
                {"type": "text", "text": "Hi what can you do for me?"}
            ]
        }
    )
    r.raise_for_status()

    buffer = ""
    for chunk in r.iter_content(decode_unicode=True):
        for ch in chunk:
            if ch != "\n":
                buffer += ch
                continue

            line = buffer.rstrip()
            buffer = ""
            if not line.startswith("data:"):
                continue

            data = json.loads(line[5:].strip())
            event_type = data["type"]
            message = data["eventMessage"].get("content", "")

            if event_type == "AGENT_RESPONSE_CHUNK":
                print(message, end="")
            elif event_type == "AGENT_RESPONSE_COMPLETE":
                print("\nDone")

if __name__ == "__main__":
    main()

4. Understanding events

Each data: line is a complete SseEvent object:
{
  "type": "AGENT_RESPONSE_CHUNK",
  "eventId": "evt_2",
  "threadId": "thread_456",
  "requestId": "req_1",
  "eventMessage": {
    "agent": null,
    "content": "Hello",
    "collaborationId": null,
    "activeAssistantCollaborationRequired": null,
    "toolExecutionApprovalRequest": null,
    "toolExecutionApprovalResponse": null,
    "timestamp": "2025-08-12T00:00:01"
  }
}
Events such as AGENT_RESPONSE_CHUNK, AGENT_RESPONSE_COMPLETE, TOOL_EXECUTION_APPROVAL_REQUEST, and the many NOTIFICATION_* types are documented in the Streaming reference. With these steps you can programmatically interact with your assistant and handle its streaming responses end‑to‑end.

What’s Next?

Sub Assistant Nodes

Learn how nodes define your sub-assistant’s workflows
I