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
POST /ai/sessionsto create a session (setassistantType, optionalmetadata, and the primary context scope you need).- Persist the
session.idand pollGET /ai/sessions/:idto render the session title, status, and last activity. - Use
PATCH /ai/sessions/:id/pin/archive/unpin/unarchivefor the organization workflow you want, then callDELETE /ai/sessions/:idwhen the conversation is over. - Keep session IDs to load history (
GET /ai/sessions), resume SSE streams, or referencemessageCountin your UI.
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
| Method | Path | Description | Auth | Rate Limit |
|---|---|---|---|---|
| GET | /ai/sessions | List sessions | JWT + Entitlement | 60/min |
| POST | /ai/sessions | Create a session | JWT + Entitlement | 20/min |
| GET | /ai/sessions/:id | Get a session | JWT + Entitlement | 60/min |
| DELETE | /ai/sessions/:id | End a session | JWT + Entitlement | 30/min |
| PATCH | /ai/sessions/:id/archive | Archive a session | JWT + Entitlement | 30/min |
| PATCH | /ai/sessions/:id/unarchive | Restore an archived session | JWT + Entitlement | 30/min |
| PATCH | /ai/sessions/:id/pin | Pin a session | JWT + Entitlement | 60/min |
| PATCH | /ai/sessions/:id/unpin | Unpin a session | JWT + Entitlement | 60/min |
| POST | /workspaces/:workspaceId/ai/sessions/:sessionId/share | Create or reactivate a public share link | JWT + Entitlement | 20/min |
| DELETE | /workspaces/:workspaceId/ai/sessions/:sessionId/share | Revoke a share link | JWT + Entitlement | 20/min |
| GET | /cs/:urlShort | Resolve a shared session by short URL | None (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
| Parameter | Type | Description |
|---|---|---|
limit | integer | Number of sessions to return. Defaults to 20, max 50. |
offset | integer | Number of sessions to skip. Defaults to 0. |
status | "active" | "archived" | Filter by session status. Defaults to active. |
chainyId | string (uuid) | Optional. Filter to sessions associated with a specific Chainy (AI personality). |
q | string | Optional. Search session titles or date-like session timestamps. Max 200 characters. |
Response
Response Example
{
"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
| Field | Type | Description |
|---|---|---|
id | string | Session ID |
title | string | null | Session title |
status | string | active or archived |
messageCount | number | Number of messages in the session |
pinnedAt | string | null | ISO 8601 timestamp when pinned, or null |
archivedAt | string | null | ISO 8601 timestamp when archived, or null |
createdAt | string | ISO 8601 |
updatedAt | string | ISO 8601 |
Code Examples
Use the id of one of your Chainies (AI personalities) as $CHAINY_ID in the filter example below.
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"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}` },
});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.
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
title | string | No | Max 120 chars | Session title |
assistantType | string | No | "chat" | "chao" | Session assistant type. Set to "chao" for Chao behavioral advisor sessions. Default: "chat". |
assistantPreset | string | No | — | Named assistant preset to apply to this session |
model | string | No | — | Exact active model_key to use, e.g. gemini-2.5-flash |
provider | string | No | Max 64 chars | Preferred provider key. Falls back to another active provider if unavailable. |
effortMode | string | No | "basic" | "thinking" | "pro" | High-level routing preference; the orchestrator picks a compatible model. |
mode | string | No | "auto" | "approval" | "plan" | Tool execution mode. Defaults to account preference (or "approval"). |
chainId | string | No | UUID | Chain to scope this session to |
chainyId | string | No | UUID | Chainy to scope this session to |
bitId | string | No | UUID | Bit to scope this session to |
agentInstanceId | string | No | UUID | Agent instance to use for this session |
primaryContextType | string | No | "chainy" | "chain" | "workspace" | "account" | Primary context scope (Chao sessions). |
primaryContextId | string | No | UUID | UUID of the primary context entity. Required when primaryContextType is set. |
metadata | object | No | — | Free-form key/value metadata stored with the session. |
Response
Response Example
{
"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.
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"
}
}'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();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.
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"'"
}'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();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
{
"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.
curl https://api.chainabit.com/api/v1/ai/sessions/$SESSION_ID \
-H "Authorization: Bearer $TOKEN"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();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
{
"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.
curl -X DELETE https://api.chainabit.com/api/v1/ai/sessions/$SESSION_ID \
-H "Authorization: Bearer $TOKEN"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}` },
});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
{
"data": { "count": 1 }
}Code Examples
Use the id from Create a Session response as $SESSION_ID.
curl -X PATCH https://api.chainabit.com/api/v1/ai/sessions/$SESSION_ID/archive \
-H "Authorization: Bearer $TOKEN"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}` },
});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
{
"data": { "count": 1 }
}Code Examples
Use the id from Create a Session response as $SESSION_ID.
curl -X PATCH https://api.chainabit.com/api/v1/ai/sessions/$SESSION_ID/unarchive \
-H "Authorization: Bearer $TOKEN"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}` },
});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
{
"data": { "count": 1 }
}Code Examples
Use the id from Create a Session response as $SESSION_ID.
curl -X PATCH https://api.chainabit.com/api/v1/ai/sessions/$SESSION_ID/pin \
-H "Authorization: Bearer $TOKEN"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}` },
});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
{
"data": { "count": 1 }
}Code Examples
Use the id from Create a Session response as $SESSION_ID.
curl -X PATCH https://api.chainabit.com/api/v1/ai/sessions/$SESSION_ID/unpin \
-H "Authorization: Bearer $TOKEN"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}` },
});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
{
"data": {
"shareUrl": "https://chainabit.com/cs/a3f2b9c1e4",
"urlShort": "a3f2b9c1e4"
}
}Response Fields
| Field | Type | Description |
|---|---|---|
shareUrl | string | Full public URL for the share link |
urlShort | string | Short token used in the public URL and in GET /cs/:urlShort |
Code Examples
Use the id from Create a Session response as $SESSION_ID.
curl -X POST https://api.chainabit.com/api/v1/workspaces/$WORKSPACE_ID/ai/sessions/$SESSION_ID/share \
-H "Authorization: Bearer $TOKEN"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);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
{
"data": { "revoked": true }
}Code Examples
curl -X DELETE https://api.chainabit.com/api/v1/workspaces/$WORKSPACE_ID/ai/sessions/$SESSION_ID/share \
-H "Authorization: Bearer $TOKEN"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();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
- Path params:
urlShort(from a share response'sdata.urlShort)
Response
Response Example
{
"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
| Field | Type | Description |
|---|---|---|
title | string | null | Session title |
messages | array | Session messages, each with id, role, content, createdAt |
Code Examples
curl https://api.chainabit.com/api/v1/cs/a3f2b9c1e4const urlShort = "a3f2b9c1e4"; // from a share response's data.urlShort
const res = await fetch(`${BASE_URL}/cs/${urlShort}`);
const { data } = await res.json();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.