Skip to content

AI Sessions

Sessions are persistent conversation contexts for multi-turn AI interactions. Sessions support archiving and pinning for organization, and are automatically purged 30 days after being ended to comply with AI provider data retention policies.

Session Lifecycle Walkthrough

  1. POST /ai/sessions to create a session (set assistantType, optional metadata, and the primary context scope you need).
  2. Persist the session.id and poll GET /ai/sessions/:id to render the session title, status, and last activity.
  3. Use PATCH /ai/sessions/:id/pin / archive / unpin / unarchive for the organization workflow you want, then call DELETE /ai/sessions/:id when the conversation is over.
  4. Keep session IDs to load history (GET /ai/sessions), resume SSE streams, or reference messageCount in your UI.
javascript
const chainyId = process.env.CHAINY_ID; // id of the Chainy this session is scoped to

const manageSession = async () => {
  const sessionRes = await fetch(`${BASE_URL}/ai/sessions`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${TOKEN}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      title: "Chainabit onboarding",
      assistantType: "chao",
      primaryContextType: "chainy",
      primaryContextId: chainyId,
    }),
  });
  const session = (await sessionRes.json()).data;

  await fetch(`${BASE_URL}/ai/sessions/${session.id}/pin`, {
    method: "PATCH",
    headers: { Authorization: `Bearer ${TOKEN}` },
  });

  const refreshed = await fetch(
    `${BASE_URL}/ai/sessions/${session.id}`,
    { headers: { Authorization: `Bearer ${TOKEN}` } }
  );
  console.log("Session status:", (await refreshed.json()).data.status);
};

Endpoints

MethodPathDescriptionAuthRate Limit
GET/ai/sessionsList sessionsJWT + Entitlement60/min
POST/ai/sessionsCreate a sessionJWT + Entitlement20/min
GET/ai/sessions/:idGet a sessionJWT + Entitlement60/min
DELETE/ai/sessions/:idEnd a sessionJWT + Entitlement30/min
PATCH/ai/sessions/:id/archiveArchive a sessionJWT + Entitlement30/min
PATCH/ai/sessions/:id/unarchiveRestore an archived sessionJWT + Entitlement30/min
PATCH/ai/sessions/:id/pinPin a sessionJWT + Entitlement60/min
PATCH/ai/sessions/:id/unpinUnpin a sessionJWT + Entitlement60/min
POST/workspaces/:workspaceId/ai/sessions/:sessionId/shareCreate or reactivate a public share linkJWT + Entitlement20/min
DELETE/workspaces/:workspaceId/ai/sessions/:sessionId/shareRevoke a share linkJWT + Entitlement20/min
GET/cs/:urlShortResolve a shared session by short URLNone (public)60/60s

GET /ai/sessions

Description

List AI sessions for the authenticated user. Returns active sessions by default. Pass ?status=archived to list archived sessions. Results are offset-paginated and can be searched with q.

Authentication: JWT Bearer token + active AI entitlement required. Rate limit: 60/min

Request

Query Parameters
ParameterTypeDescription
limitintegerNumber of sessions to return. Defaults to 20, max 50.
offsetintegerNumber of sessions to skip. Defaults to 0.
status"active" | "archived"Filter by session status. Defaults to active.
chainyIdstring (uuid)Optional. Filter to sessions associated with a specific Chainy (AI personality).
qstringOptional. Search session titles or date-like session timestamps. Max 200 characters.

Response

Response Example
json
{
  "data": [
    {
      "id": "cm5sess01",
      "title": "Spanish Learning Coach",
      "status": "active",
      "messageCount": 5,
      "pinnedAt": "2026-03-17T09:00:00.000Z",
      "archivedAt": null,
      "createdAt": "2026-03-17T10:00:00.000Z",
      "updatedAt": "2026-03-17T11:00:00.000Z"
    }
  ],
  "meta": {
    "limit": 20,
    "offset": 0,
    "total": 1,
    "totalCount": 1,
    "hasNextPage": false
  }
}
Response Fields
FieldTypeDescription
idstringSession ID
titlestring | nullSession title
statusstringactive or archived
messageCountnumberNumber of messages in the session
pinnedAtstring | nullISO 8601 timestamp when pinned, or null
archivedAtstring | nullISO 8601 timestamp when archived, or null
createdAtstringISO 8601
updatedAtstringISO 8601

Code Examples

Use the id of one of your Chainies (AI personalities) as $CHAINY_ID in the filter example below.

bash
curl "https://api.chainabit.com/api/v1/ai/sessions?limit=20&offset=0" \
  -H "Authorization: Bearer $TOKEN"

# Archived sessions:
curl "https://api.chainabit.com/api/v1/ai/sessions?status=archived" \
  -H "Authorization: Bearer $TOKEN"

# Filter by Chainy (AI personality):
curl "https://api.chainabit.com/api/v1/ai/sessions?chainyId=$CHAINY_ID" \
  -H "Authorization: Bearer $TOKEN"

# Search sessions:
curl "https://api.chainabit.com/api/v1/ai/sessions?q=weekly%20planning" \
  -H "Authorization: Bearer $TOKEN"
javascript
const chainyId = process.env.CHAINY_ID; // id of the Chainy to filter by

const res = await fetch(`${BASE_URL}/ai/sessions?limit=20&offset=0`, {
  headers: { Authorization: `Bearer ${TOKEN}` },
});
const { data, meta } = await res.json();

// Archived sessions:
const archived = await fetch(`${BASE_URL}/ai/sessions?status=archived`, {
  headers: { Authorization: `Bearer ${TOKEN}` },
});

// Filter by Chainy:
const chainySpecific = await fetch(
  `${BASE_URL}/ai/sessions?chainyId=${chainyId}`,
  { headers: { Authorization: `Bearer ${TOKEN}` } }
);

// Search by title or date:
const search = await fetch(`${BASE_URL}/ai/sessions?q=weekly%20planning`, {
  headers: { Authorization: `Bearer ${TOKEN}` },
});
python
import os
import requests

chainy_id = os.environ["CHAINY_ID"]  # id of the Chainy to filter by

res = requests.get(
    f"{BASE_URL}/ai/sessions",
    params={"limit": 20, "offset": 0},
    headers={"Authorization": f"Bearer {TOKEN}"},
)
body = res.json()

# Archived sessions:
archived = requests.get(
    f"{BASE_URL}/ai/sessions",
    params={"status": "archived"},
    headers={"Authorization": f"Bearer {TOKEN}"},
)

# Filter by Chainy:
chainy_specific = requests.get(
    f"{BASE_URL}/ai/sessions",
    params={"chainyId": chainy_id},
    headers={"Authorization": f"Bearer {TOKEN}"},
)

search = requests.get(
    f"{BASE_URL}/ai/sessions",
    params={"q": "weekly planning"},
    headers={"Authorization": f"Bearer {TOKEN}"},
)

POST /ai/sessions

Description

Create a new AI session.

Authentication: JWT Bearer token + active AI entitlement required. Rate limit: 20/min

Request

Request Body

Unknown properties are rejected. The request body accepts only the fields below.

FieldTypeRequiredConstraintsDescription
titlestringNoMax 120 charsSession title
assistantTypestringNo"chat" | "chao"Session assistant type. Set to "chao" for Chao behavioral advisor sessions. Default: "chat".
assistantPresetstringNoNamed assistant preset to apply to this session
modelstringNoExact active model_key to use, e.g. gemini-2.5-flash
providerstringNoMax 64 charsPreferred provider key. Falls back to another active provider if unavailable.
effortModestringNo"basic" | "thinking" | "pro"High-level routing preference; the orchestrator picks a compatible model.
modestringNo"auto" | "approval" | "plan"Tool execution mode. Defaults to account preference (or "approval").
chainIdstringNoUUIDChain to scope this session to
chainyIdstringNoUUIDChainy to scope this session to
bitIdstringNoUUIDBit to scope this session to
agentInstanceIdstringNoUUIDAgent instance to use for this session
primaryContextTypestringNo"chainy" | "chain" | "workspace" | "account"Primary context scope (Chao sessions).
primaryContextIdstringNoUUIDUUID of the primary context entity. Required when primaryContextType is set.
metadataobjectNoFree-form key/value metadata stored with the session.

Response

Response Example
json
{
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "title": "Spanish Learning Coach",
    "status": "active",
    "sessionType": "chat",
    "mode": "approval",
    "startedAt": "2026-03-17T10:00:00.000Z",
    "lastActivityAt": "2026-03-17T10:00:00.000Z",
    "archivedAt": null,
    "pinnedAt": null,
    "metadata": {
      "title": "Spanish Learning Coach"
    }
  }
}

The metadata object in the response surfaces a curated set of keys (title, assistantPreset, selectedModelKey, selectedModelProvider); it is null when none are present.

Code Examples

Use the id of one of your Chains as $CHAIN_ID.

bash
curl -X POST https://api.chainabit.com/api/v1/ai/sessions \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Spanish Learning Coach",
    "chainId": "'"$CHAIN_ID"'",
    "metadata": {
      "goal": "Improve daily vocabulary retention"
    }
  }'
javascript
const chainId = process.env.CHAIN_ID; // id of the Chain to scope this session to

const res = await fetch(`${BASE_URL}/ai/sessions`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    title: "Spanish Learning Coach",
    chainId,
    metadata: {
      goal: "Improve daily vocabulary retention",
    },
  }),
});
const { data } = await res.json();
python
import os
import requests

chain_id = os.environ["CHAIN_ID"]  # id of the Chain to scope this session to

res = requests.post(
    f"{BASE_URL}/ai/sessions",
    headers={
        "Authorization": f"Bearer {TOKEN}",
        "Content-Type": "application/json",
    },
    json={
        "title": "Spanish Learning Coach",
        "chainId": chain_id,
        "metadata": {
            "goal": "Improve daily vocabulary retention",
        },
    },
)
data = res.json()["data"]
Creating a Chao Session

To create a session powered by Chao (Chainabit's behavioral advisor), pass assistantType: "chao" with a Chainy context. Use the id of the Chainy to coach with as $CHAINY_ID.

bash
curl -X POST https://api.chainabit.com/api/v1/ai/sessions \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "My Chainy Coach",
    "assistantType": "chao",
    "primaryContextType": "chainy",
    "primaryContextId": "'"$CHAINY_ID"'"
  }'
javascript
const chainyId = process.env.CHAINY_ID; // id of the Chainy this session is scoped to

const res = await fetch(`${BASE_URL}/ai/sessions`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    title: "My Chainy Coach",
    assistantType: "chao",
    primaryContextType: "chainy",
    primaryContextId: chainyId,
  }),
});
const { data } = await res.json();
python
import os
import requests

chainy_id = os.environ["CHAINY_ID"]  # id of the Chainy this session is scoped to

res = requests.post(
    f"{BASE_URL}/ai/sessions",
    headers={
        "Authorization": f"Bearer {TOKEN}",
        "Content-Type": "application/json",
    },
    json={
        "title": "My Chainy Coach",
        "assistantType": "chao",
        "primaryContextType": "chainy",
        "primaryContextId": chainy_id,
    },
)
data = res.json()["data"]

When assistantType is "chao", the session automatically loads Chainy-scoped memories and productivity context into all AI interactions.

Reasoning and Tool Activity

Chao sessions can stream safe activity summaries while a response is being generated. Use payload.activity on events such as tool.started, tool.progress, tool.approval_required, tool.completed, tool.failed, tool.degraded, plan.step_added, and capability.resolved to render status cards.

Raw private Chain-of-Thought is not a public UI contract. If diagnostic cot.* events appear in an internal/debug session, treat them as nonessential metadata and do not show raw reasoning to end users by default.

Persisted message.toolCalls is the stable source for reconstructing tool cards after reload. See AI Messages and SSE Streaming.


GET /ai/sessions/:id

Description

Get details of a single session.

Authentication: JWT Bearer token + active AI entitlement required. Rate limit: 60/min

Request

  • Path params: id (Session ID)

Response

Response Example
json
{
  "data": {
    "id": "cm5sess01",
    "title": "Spanish Learning Coach",
    "status": "active",
    "pinnedAt": null,
    "archivedAt": null,
    "messageCount": 5,
    "createdAt": "2026-03-17T10:00:00.000Z",
    "updatedAt": "2026-03-17T11:00:00.000Z"
  }
}

Code Examples

Use the id from Create a Session response as $SESSION_ID.

bash
curl https://api.chainabit.com/api/v1/ai/sessions/$SESSION_ID \
  -H "Authorization: Bearer $TOKEN"
javascript
const sessionId = process.env.SESSION_ID; // id from the Create a Session response

const res = await fetch(`${BASE_URL}/ai/sessions/${sessionId}`, {
  headers: { Authorization: `Bearer ${TOKEN}` },
});
const { data } = await res.json();
python
import os
import requests

session_id = os.environ["SESSION_ID"]  # id from the Create a Session response

res = requests.get(
    f"{BASE_URL}/ai/sessions/{session_id}",
    headers={"Authorization": f"Bearer {TOKEN}"},
)
data = res.json()["data"]

DELETE /ai/sessions/:id

Description

End a session. The session is soft-deleted and no longer accepts new messages. Sessions ended this way are permanently deleted after 30 days of inactivity.

Authentication: JWT Bearer token + active AI entitlement required. Rate limit: 30/min

Request

  • Path params: id (Session ID)

Response

Response Example
json
{
  "data": {
    "id": "cm5sess01",
    "ended": true,
    "endedAt": "2026-03-17T12:00:00.000Z"
  }
}

Code Examples

Use the id from Create a Session response as $SESSION_ID.

bash
curl -X DELETE https://api.chainabit.com/api/v1/ai/sessions/$SESSION_ID \
  -H "Authorization: Bearer $TOKEN"
javascript
const sessionId = process.env.SESSION_ID; // id from the Create a Session response

const res = await fetch(`${BASE_URL}/ai/sessions/${sessionId}`, {
  method: "DELETE",
  headers: { Authorization: `Bearer ${TOKEN}` },
});
python
import os
import requests

session_id = os.environ["SESSION_ID"]  # id from the Create a Session response

res = requests.delete(
    f"{BASE_URL}/ai/sessions/{session_id}",
    headers={"Authorization": f"Bearer {TOKEN}"},
)

PATCH /ai/sessions/:id/archive

Description

Archive an active session. Archived sessions are excluded from the default list but preserved indefinitely. Archived sessions are never auto-deleted.

Authentication: JWT Bearer token + active AI entitlement required. Rate limit: 30/min

Request

  • Path params: id (Session ID)

Response

Response Example
json
{
  "data": { "count": 1 }
}

Code Examples

Use the id from Create a Session response as $SESSION_ID.

bash
curl -X PATCH https://api.chainabit.com/api/v1/ai/sessions/$SESSION_ID/archive \
  -H "Authorization: Bearer $TOKEN"
javascript
const sessionId = process.env.SESSION_ID; // id from the Create a Session response

const res = await fetch(`${BASE_URL}/ai/sessions/${sessionId}/archive`, {
  method: "PATCH",
  headers: { Authorization: `Bearer ${TOKEN}` },
});
python
import os
import requests

session_id = os.environ["SESSION_ID"]  # id from the Create a Session response

res = requests.patch(
    f"{BASE_URL}/ai/sessions/{session_id}/archive",
    headers={"Authorization": f"Bearer {TOKEN}"},
)

PATCH /ai/sessions/:id/unarchive

Description

Restore an archived session back to active status.

Authentication: JWT Bearer token + active AI entitlement required. Rate limit: 30/min

Request

  • Path params: id (Session ID)

Response

Response Example
json
{
  "data": { "count": 1 }
}

Code Examples

Use the id from Create a Session response as $SESSION_ID.

bash
curl -X PATCH https://api.chainabit.com/api/v1/ai/sessions/$SESSION_ID/unarchive \
  -H "Authorization: Bearer $TOKEN"
javascript
const sessionId = process.env.SESSION_ID; // id from the Create a Session response

const res = await fetch(`${BASE_URL}/ai/sessions/${sessionId}/unarchive`, {
  method: "PATCH",
  headers: { Authorization: `Bearer ${TOKEN}` },
});
python
import os
import requests

session_id = os.environ["SESSION_ID"]  # id from the Create a Session response

res = requests.patch(
    f"{BASE_URL}/ai/sessions/{session_id}/unarchive",
    headers={"Authorization": f"Bearer {TOKEN}"},
)

PATCH /ai/sessions/:id/pin

Description

Pin a session. Pinning is a client-side organizational marker and does not change the session status. Both active and archived sessions can be pinned.

Authentication: JWT Bearer token + active AI entitlement required. Rate limit: 60/min

Request

  • Path params: id (Session ID)

Response

Response Example
json
{
  "data": { "count": 1 }
}

Code Examples

Use the id from Create a Session response as $SESSION_ID.

bash
curl -X PATCH https://api.chainabit.com/api/v1/ai/sessions/$SESSION_ID/pin \
  -H "Authorization: Bearer $TOKEN"
javascript
const sessionId = process.env.SESSION_ID; // id from the Create a Session response

const res = await fetch(`${BASE_URL}/ai/sessions/${sessionId}/pin`, {
  method: "PATCH",
  headers: { Authorization: `Bearer ${TOKEN}` },
});
python
import os
import requests

session_id = os.environ["SESSION_ID"]  # id from the Create a Session response

res = requests.patch(
    f"{BASE_URL}/ai/sessions/{session_id}/pin",
    headers={"Authorization": f"Bearer {TOKEN}"},
)

PATCH /ai/sessions/:id/unpin

Description

Unpin a pinned session.

Authentication: JWT Bearer token + active AI entitlement required. Rate limit: 60/min

Request

  • Path params: id (Session ID)

Response

Response Example
json
{
  "data": { "count": 1 }
}

Code Examples

Use the id from Create a Session response as $SESSION_ID.

bash
curl -X PATCH https://api.chainabit.com/api/v1/ai/sessions/$SESSION_ID/unpin \
  -H "Authorization: Bearer $TOKEN"
javascript
const sessionId = process.env.SESSION_ID; // id from the Create a Session response

const res = await fetch(`${BASE_URL}/ai/sessions/${sessionId}/unpin`, {
  method: "PATCH",
  headers: { Authorization: `Bearer ${TOKEN}` },
});
python
import os
import requests

session_id = os.environ["SESSION_ID"]  # id from the Create a Session response

res = requests.patch(
    f"{BASE_URL}/ai/sessions/{session_id}/unpin",
    headers={"Authorization": f"Bearer {TOKEN}"},
)

Sharing a Session

Only the session owner or an invited human participant (see Conversation Participants & Lenses) can create or revoke a share link. Sharing is scoped to Chao sessions accessed through a workspace.

POST /workspaces/:workspaceId/ai/sessions/:sessionId/share

Description

Creates a public, read-only share link for the session. Calling this again on an already-shared session reactivates the existing share rather than creating a duplicate — the urlShort does not change.

Authentication: JWT Bearer token + active AI entitlement required (session owner or invited human participant only). Rate limit: 20/min

Request

  • Path params: workspaceId, sessionId

No body.

Response

Response Example
json
{
  "data": {
    "shareUrl": "https://chainabit.com/cs/a3f2b9c1e4",
    "urlShort": "a3f2b9c1e4"
  }
}
Response Fields
FieldTypeDescription
shareUrlstringFull public URL for the share link
urlShortstringShort token used in the public URL and in GET /cs/:urlShort

Code Examples

Use the id from Create a Session response as $SESSION_ID.

bash
curl -X POST https://api.chainabit.com/api/v1/workspaces/$WORKSPACE_ID/ai/sessions/$SESSION_ID/share \
  -H "Authorization: Bearer $TOKEN"
javascript
const workspaceId = process.env.WORKSPACE_ID;
const sessionId = process.env.SESSION_ID; // id from the Create a Session response

const res = await fetch(
  `${BASE_URL}/workspaces/${workspaceId}/ai/sessions/${sessionId}/share`,
  { method: "POST", headers: { Authorization: `Bearer ${TOKEN}` } }
);
const { data } = await res.json();
console.log(data.shareUrl);
python
import os
import requests

workspace_id = os.environ["WORKSPACE_ID"]
session_id = os.environ["SESSION_ID"]  # id from the Create a Session response

res = requests.post(
    f"{BASE_URL}/workspaces/{workspace_id}/ai/sessions/{session_id}/share",
    headers={"Authorization": f"Bearer {TOKEN}"},
)
data = res.json()["data"]
print(data["shareUrl"])

DELETE /workspaces/:workspaceId/ai/sessions/:sessionId/share

Description

Revokes the session's active share link. The previous urlShort stops resolving immediately.

Authentication: JWT Bearer token + active AI entitlement required (session owner or invited human participant only). Rate limit: 20/min

Request

  • Path params: workspaceId, sessionId

Response

Response Example
json
{
  "data": { "revoked": true }
}

Code Examples

bash
curl -X DELETE https://api.chainabit.com/api/v1/workspaces/$WORKSPACE_ID/ai/sessions/$SESSION_ID/share \
  -H "Authorization: Bearer $TOKEN"
javascript
const workspaceId = process.env.WORKSPACE_ID;
const sessionId = process.env.SESSION_ID; // id from the Create a Session response

const res = await fetch(
  `${BASE_URL}/workspaces/${workspaceId}/ai/sessions/${sessionId}/share`,
  { method: "DELETE", headers: { Authorization: `Bearer ${TOKEN}` } }
);
const { data } = await res.json();
python
import os
import requests

workspace_id = os.environ["WORKSPACE_ID"]
session_id = os.environ["SESSION_ID"]  # id from the Create a Session response

res = requests.delete(
    f"{BASE_URL}/workspaces/{workspace_id}/ai/sessions/{session_id}/share",
    headers={"Authorization": f"Bearer {TOKEN}"},
)
data = res.json()["data"]

GET /cs/:urlShort

Description

Resolve a shared session by its short token. This endpoint is public and unauthenticated. Revoked, expired, and malformed tokens all resolve to a generic 404 Not Found — the response never distinguishes between those cases.

Authentication: None (public). Rate limit: 60 requests per 60 seconds.

Request

Response

Response Example
json
{
  "data": {
    "title": "Spanish Learning Coach",
    "messages": [
      {
        "id": "msg_01",
        "role": "user",
        "content": "How am I doing with my Morning Run chain?",
        "createdAt": "2026-03-17T10:00:00.000Z"
      }
    ]
  }
}
Response Fields
FieldTypeDescription
titlestring | nullSession title
messagesarraySession messages, each with id, role, content, createdAt

Code Examples

bash
curl https://api.chainabit.com/api/v1/cs/a3f2b9c1e4
javascript
const urlShort = "a3f2b9c1e4"; // from a share response's data.urlShort

const res = await fetch(`${BASE_URL}/cs/${urlShort}`);
const { data } = await res.json();
python
import requests

url_short = "a3f2b9c1e4"  # from a share response's data.urlShort

res = requests.get(f"{BASE_URL}/cs/{url_short}")
data = res.json()["data"]

Data Retention

Sessions ended via DELETE /ai/sessions/:id are soft-deleted and permanently removed after 30 days of inactivity. All messages within the session are deleted at the same time.

To preserve a session indefinitely, archive it with PATCH /ai/sessions/:id/archive before deleting — archived sessions are never auto-deleted.

Built with purpose.