> ## Documentation Index
> Fetch the complete documentation index at: https://docs.envole.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# API Integration

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:

```bash theme={null}
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.

```bash theme={null}
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](https://developer.mozilla.org/en-US/docs/Web/API/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

```ts theme={null}
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

```py theme={null}
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:

```json theme={null}
{
  "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](/api-reference/streaming/index).

With these steps you can programmatically interact with your assistant and handle its streaming responses end‑to‑end.

***

## What's Next?

<Card title="Sub Assistant Nodes" icon="code" href="/sub-assistants/nodes/profiles">
  Learn how nodes define your sub-assistant's workflows
</Card>
