Skip to content

Quickstart ​

This guide takes you from zero to a streaming AI response in three steps. By the end, you will have authenticated, created an AI session, sent a message, and received a streamed reply.

You need a confirmed account with AI access, a person-scoped developer token, curl, and jq. Create the developer token in Developer Access. The exchange below returns a short-lived access token for the session routes.


Environment Variables ​

Set these before running any example on this page:

bash
export BASE_URL="https://api.chainabit.com/api/v1"
export DEVELOPER_TOKEN="YOUR_DEVELOPER_TOKEN"
export TOKEN="$(curl --fail-with-body -sS -X POST "$BASE_URL/auth/developer-tokens/exchange" \
  -H "Content-Type: application/json" \
  -d "{\"token\":\"$DEVELOPER_TOKEN\"}" | jq -er '.data.tokens.accessToken')"

Developer tokens with an organization-only scope cannot be exchanged for a person-scoped session. You can also use an access token from an existing login; see Authentication.


Step 1: Create an AI Session ​

A session is a persistent conversation thread. Create one with a title to identify it.

bash
SESSION_RESPONSE="$(curl --fail-with-body -sS -X POST "$BASE_URL/ai/sessions" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"title": "My First Session"}')"
printf '%s\n' "$SESSION_RESPONSE" | jq .
export SESSION_ID="$(printf '%s\n' "$SESSION_RESPONSE" | jq -er '.data.id')"
javascript
const response = await fetch(`${BASE_URL}/ai/sessions`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ title: 'My First Session' }),
});
const { data } = await response.json();
const SESSION_ID = data.id;
python
import requests

response = requests.post(
    f"{BASE_URL}/ai/sessions",
    json={"title": "My First Session"},
    headers={"Authorization": f"Bearer {TOKEN}"},
)
data = response.json()["data"]
SESSION_ID = data["id"]

Response 201 Created

json
{
  "data": {
    "id": "<session-uuid>",
    "title": "My First Session",
    "titleGenerationState": "resolved",
    "status": "active",
    "sessionType": "chat",
    "mode": "auto",
    "visibility": "private",
    "startedAt": "<ISO-8601 timestamp>",
    "lastActivityAt": "<ISO-8601 timestamp>",
    "archivedAt": null,
    "pinnedAt": null,
    "chainyId": null,
    "metadata": { "title": "My First Session" },
    "privacyMode": "standard",
    "temporary": false,
    "temporaryExpiresAt": null,
    "temporaryRetentionDays": null,
    "temporaryContextMode": null
  }
}

The cURL command exports the returned data.id as SESSION_ID for the next step.


Step 2: Send a Message ​

Send a message into the session. The API accepts plain text content.

bash
curl -X POST "$BASE_URL/ai/sessions/$SESSION_ID/messages" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"content": "What can I build with the Chainabit API?"}'
javascript
const response = await fetch(
  `${BASE_URL}/ai/sessions/${SESSION_ID}/messages`,
  {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${TOKEN}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ content: 'What can I build with the Chainabit API?' }),
  }
);
const { data } = await response.json();
const MESSAGE_ID = data.assistantMessage.id;
python
import requests

response = requests.post(
    f"{BASE_URL}/ai/sessions/{SESSION_ID}/messages",
    json={"content": "What can I build with the Chainabit API?"},
    headers={"Authorization": f"Bearer {TOKEN}"},
)
data = response.json()["data"]
MESSAGE_ID = data["assistantMessage"]["id"]

Response 201 Created

json
{
  "data": {
    "sessionId": "sess_01HQ3K5N2P4R7T9V1X3Z5A7C9E",
    "userMessage": {
      "id": "msg_01HQ3K7R2S5T8V2Y4A6C8E1G3I",
      "role": "user",
      "content": "What can I build with the Chainabit API?",
      "createdAt": "2026-05-04T10:00:05.000Z"
    },
    "assistantMessage": {
      "id": "msg_01HQ3K8S3T6U9W3Z5B7D0F2H4J",
      "role": "assistant",
      "content": "",
      "status": "pending",
      "createdAt": "2026-05-04T10:00:05.100Z"
    },
    "run": { "id": "run_01HQ3K9T4U7V0X4A6C8E1G3I5K", "status": "queued" },
    "effortDowngrade": null
  }
}

The endpoint creates and returns both messages plus the run that will produce the reply — you stream the assistant message's ID in the next step, not the user message's:

bash
export MESSAGE_ID="msg_01HQ3K8S3T6U9W3Z5B7D0F2H4J"

Step 3: Stream the Response ​

Open a server-sent event (SSE) stream to receive the AI reply in real time.

bash
curl "$BASE_URL/ai/sessions/$SESSION_ID/messages/$MESSAGE_ID/stream" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: text/event-stream"
python
import requests

with requests.get(
    f"{BASE_URL}/ai/sessions/{SESSION_ID}/messages/{MESSAGE_ID}/stream",
    headers={"Authorization": f"Bearer {TOKEN}", "Accept": "text/event-stream"},
    stream=True,
) as r:
    for line in r.iter_lines():
        if line:
            print(line.decode())

SSE Output

Native browser EventSource cannot set the required Authorization header. Use a streaming HTTP client that supports request headers.

The first frame on every stream is a connection preamble; each event after that carries a numbered envelope with a payload field specific to its type:

event: stream.connected
data: {"runId": "run_01HQU..."}

id: 1
event: message.delta
data: {"eventId":1,"type":"message.delta","runId":"run_01HQU...","timestamp":"2026-05-04T10:00:06.000Z","payload":{"delta":"You can build"},"v":1}

id: 2
event: message.delta
data: {"eventId":2,"type":"message.delta","runId":"run_01HQU...","timestamp":"2026-05-04T10:00:06.100Z","payload":{"delta":" AI-agent workflows,"},"v":1}

id: 3
event: message.completed
data: {"eventId":3,"type":"message.completed","runId":"run_01HQU...","timestamp":"2026-05-04T10:00:07.000Z","payload":{"runId":"run_01HQU...","messageId":"msg_01HQ3K7R2S5T8V2Y4A6C8E1G3I","content":"You can build AI-agent workflows, integrate connectors, and automate pipelines."},"v":1}

Collect message.delta events' payload.delta to assemble the full response. Close the stream on message.completed, which carries the complete payload.content if you'd rather use that directly instead of concatenating deltas.


What's Next ​

Built with purpose.