Workspace AI Resources
Manage AI resources scoped to a specific workspace for team collaboration, including agents, twins, tools, skills, and twin policies.
Workspace Agents
Manage agents scoped to a specific workspace for team collaboration.
Endpoints
| Method | Path | Description | Auth | Rate Limit |
|---|---|---|---|---|
| GET | /workspaces/:workspaceId/ai/agents | List workspace agents | JWT + Entitlement | 60/min |
| POST | /workspaces/:workspaceId/ai/agents | Create a workspace agent | JWT + Entitlement | 10/min |
| GET | /workspaces/:workspaceId/ai/agents/:agentId/runs | List agent runs | JWT + Entitlement | 60/min |
| POST | /workspaces/:workspaceId/ai/agents/:agentId/runs | Create an agent run | JWT + Entitlement | 10/min |
Create Workspace Agent
Description
Creates a new agent scoped to the given workspace. Model selection is not configurable through this endpoint — every workspace agent uses the platform default model. If you need to choose a specific modelId, use the separate Agent Definitions resource instead, which represents reusable agent templates rather than workspace-bound agent instances.
Request
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Agent display name |
description | string | No | Human-readable description of the agent's purpose |
systemPrompt | string | No | System prompt that shapes the agent's behavior |
toolIds | string[] | No | IDs of workspace tools the agent is allowed to call |
There is no definitionId, config, modelId, or instructions field on this endpoint. Sending any of these returns 400 Bad Request (e.g. property instructions should not exist; property modelId should not exist).
Response
{
"data": {
"id": "cm5wagent01",
"workspaceId": "$WORKSPACE_ID",
"name": "Team Productivity Coach",
"description": "Reviews team chain activity and suggests focus areas",
"systemPrompt": "You are a productivity coach for a team workspace...",
"toolIds": ["cm5wtool01"],
"status": "active",
"createdAt": "2026-03-17T10:00:00.000Z"
}
}Code Examples
curl -X POST https://api.chainabit.com/api/v1/workspaces/$WORKSPACE_ID/ai/agents \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Team Productivity Coach",
"description": "Reviews team chain activity and suggests focus areas",
"systemPrompt": "You are a productivity coach for a team workspace...",
"toolIds": ["cm5wtool01"]
}'const BASE_URL = process.env.BASE_URL;
const TOKEN = process.env.TOKEN;
const workspaceId = process.env.WORKSPACE_ID; // from a workspace lookup call
const response = await fetch(`${BASE_URL}/workspaces/${workspaceId}/ai/agents`, {
method: "POST",
headers: {
Authorization: `Bearer ${TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "Team Productivity Coach",
description: "Reviews team chain activity and suggests focus areas",
systemPrompt: "You are a productivity coach for a team workspace...",
toolIds: ["cm5wtool01"],
}),
});
const { data } = await response.json();import requests, os
workspace_id = os.environ["WORKSPACE_ID"] # from a workspace lookup call
response = requests.post(
f"{os.environ['BASE_URL']}/workspaces/{workspace_id}/ai/agents",
headers={"Authorization": f"Bearer {os.environ['TOKEN']}"},
json={
"name": "Team Productivity Coach",
"description": "Reviews team chain activity and suggests focus areas",
"systemPrompt": "You are a productivity coach for a team workspace...",
"toolIds": ["cm5wtool01"],
},
)
data = response.json()["data"]Run Workspace Agent
Subscription required.
POST /workspaces/:workspaceId/ai/agents/:agentId/runsrequires an active paid subscription. Callers without a valid subscription receive403 Forbiddenwith{ "code": "subscription_inactive" }. Credit balance alone is not sufficient — an active plan must also be present.
agentIdmust come from the Create Workspace Agent response. TheagentIdpath segment must be the literalidfield returned indata.idfrom Create Workspace Agent above — never an unset, empty, or hardcoded placeholder variable. A blank or malformedagentIdpreviously returned an opaque500 Internal Server Error; it now correctly returns400 Bad Request.
Description
Starts a run for an existing workspace agent.
Request
| Field | Type | Required | Description |
|---|---|---|---|
input | object | Yes | Freeform task input passed to the agent |
Path params
| Param | Description |
|---|---|
workspaceId | From your workspace lookup or creation response's data.id |
agentId | From Create Workspace Agent's data.id |
Response
{
"data": {
"id": "cm5wrun01",
"agentId": "$AGENT_ID",
"workspaceId": "$WORKSPACE_ID",
"status": "running",
"input": {
"task": "Analyze team productivity patterns for the past week",
"scope": "all-members"
},
"startedAt": "2026-03-17T10:00:00.000Z",
"createdAt": "2026-03-17T10:00:00.000Z"
}
}Response cases
| Status | Condition |
|---|---|
201 Created | Run started successfully |
400 Bad Request | Malformed or empty agentId path segment |
403 Forbidden | No active paid subscription (subscription_inactive) |
Code Examples
curl -X POST https://api.chainabit.com/api/v1/workspaces/$WORKSPACE_ID/ai/agents/$AGENT_ID/runs \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"input": {
"task": "Analyze team productivity patterns for the past week",
"scope": "all-members"
}
}'const BASE_URL = process.env.BASE_URL;
const TOKEN = process.env.TOKEN;
const workspaceId = process.env.WORKSPACE_ID; // from a workspace lookup call
const agentId = createdAgent.data.id; // from Create Workspace Agent's response
const response = await fetch(
`${BASE_URL}/workspaces/${workspaceId}/ai/agents/${agentId}/runs`,
{
method: "POST",
headers: {
Authorization: `Bearer ${TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
input: {
task: "Analyze team productivity patterns for the past week",
scope: "all-members",
},
}),
}
);
const { data } = await response.json();import requests, os
workspace_id = os.environ["WORKSPACE_ID"] # from a workspace lookup call
agent_id = created_agent["data"]["id"] # from Create Workspace Agent's response
response = requests.post(
f"{os.environ['BASE_URL']}/workspaces/{workspace_id}/ai/agents/{agent_id}/runs",
headers={"Authorization": f"Bearer {os.environ['TOKEN']}"},
json={"input": {"task": "Analyze team productivity patterns for the past week", "scope": "all-members"}},
)
data = response.json()["data"]Get a Single Run
Workspace agent runs are not readable through a nested GET /workspaces/:workspaceId/ai/agents/:agentId/runs/:runId endpoint — only the list form above (GET /workspaces/:workspaceId/ai/agents/:agentId/runs) is nested under workspaces and agents. To fetch a single run by ID, use the top-level run endpoint documented in AI Features:
GET /ai/runs/{runId}Use the id field from the Run Workspace Agent response (data.id) as $RUN_ID:
curl https://api.chainabit.com/api/v1/ai/runs/$RUN_ID \
-H "Authorization: Bearer $TOKEN"Workspace Twins
Workspace-scoped digital twins for team-level AI personas.
Endpoints
| Method | Path | Description | Auth | Rate Limit |
|---|---|---|---|---|
| GET | /workspaces/:workspaceId/ai/twins | List workspace twins | JWT + Entitlement | 60/min |
| POST | /workspaces/:workspaceId/ai/twins | Create a workspace twin | JWT + Entitlement | 10/min |
| PUT | /workspaces/:workspaceId/ai/twins/:twinId/persona | Update twin persona | JWT + Entitlement | 30/min |
| POST | /workspaces/:workspaceId/ai/twins/:twinId/test | Test twin interaction | JWT + Entitlement | 20/min |
Create Workspace Twin
Request
Use the id of the workspace as $WORKSPACE_ID.
Response
{
"data": {
"id": "cm5wtwin01",
"workspaceId": "cm5ws01",
"name": "Team Standup Twin",
"persona": { "tone": "professional", "role": "scrum-master" },
"createdAt": "2026-03-17T10:00:00.000Z"
}
}Code Examples
curl -X POST https://api.chainabit.com/api/v1/workspaces/$WORKSPACE_ID/ai/twins \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Team Standup Twin",
"persona": {
"tone": "professional",
"role": "scrum-master"
}
}'const BASE_URL = process.env.BASE_URL;
const TOKEN = process.env.TOKEN;
const workspaceId = process.env.WORKSPACE_ID;
const response = await fetch(`${BASE_URL}/workspaces/${workspaceId}/ai/twins`, {
method: "POST",
headers: {
Authorization: `Bearer ${TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "Team Standup Twin",
persona: { tone: "professional", role: "scrum-master" },
}),
});
const { data } = await response.json();import requests, os
workspace_id = os.environ["WORKSPACE_ID"]
response = requests.post(
f"{os.environ['BASE_URL']}/workspaces/{workspace_id}/ai/twins",
headers={"Authorization": f"Bearer {os.environ['TOKEN']}"},
json={"name": "Team Standup Twin", "persona": {"tone": "professional", "role": "scrum-master"}},
)
data = response.json()["data"]Test Twin Interaction
Request
Use the id from Create Workspace Twin's response (data.id) as $TWIN_ID.
Response
{
"data": {
"response": "Here is the team standup summary for yesterday...",
"tokensUsed": 320,
"latencyMs": 1200
}
}Code Examples
curl -X POST https://api.chainabit.com/api/v1/workspaces/$WORKSPACE_ID/ai/twins/$TWIN_ID/test \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"message": "Summarize yesterday progress for the team"
}'const BASE_URL = process.env.BASE_URL;
const TOKEN = process.env.TOKEN;
const workspaceId = process.env.WORKSPACE_ID;
const twinId = process.env.TWIN_ID; // from Create Workspace Twin's response
const response = await fetch(
`${BASE_URL}/workspaces/${workspaceId}/ai/twins/${twinId}/test`,
{
method: "POST",
headers: {
Authorization: `Bearer ${TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ message: "Summarize yesterday progress for the team" }),
}
);
const { data } = await response.json();import requests, os
workspace_id = os.environ["WORKSPACE_ID"]
twin_id = os.environ["TWIN_ID"] # from Create Workspace Twin's response
response = requests.post(
f"{os.environ['BASE_URL']}/workspaces/{workspace_id}/ai/twins/{twin_id}/test",
headers={"Authorization": f"Bearer {os.environ['TOKEN']}"},
json={"message": "Summarize yesterday progress for the team"},
)
data = response.json()["data"]Workspace Tools
Register tools available to all agents within a workspace.
Endpoints
| Method | Path | Description | Auth | Rate Limit |
|---|---|---|---|---|
| GET | /workspaces/:workspaceId/ai/tools | List workspace tools | JWT + Entitlement | 60/min |
| POST | /workspaces/:workspaceId/ai/tools | Create a workspace tool | JWT + Entitlement | 10/min |
Webhook Tool Security Policy
Tools with executionType: "http_webhook" are subject to outbound request security enforcement:
- HTTPS only —
webhookUrlmust use thehttps:scheme. HTTP, file, and other schemes are rejected at execution time. - No private addresses — Requests to loopback (
127.x.x.x), RFC 1918 ranges (10.x,172.16–31.x,192.168.x), link-local / cloud metadata (169.254.x.x), and similar reserved ranges are blocked. - Header restrictions — The following headers supplied in
tool_schema.headersare stripped before the outbound request:Authorization,Cookie,Host,X-Forwarded-For,X-Forwarded-Host,X-Forwarded-Proto,X-Real-IP, and internal service headers.
Violations result in a failed tool step with an error message — they are not surfaced as HTTP errors to the caller.
List Workspace Tools
Request
Use the id of the workspace as $WORKSPACE_ID.
Response
{
"data": [
{
"id": "cm5wtool01",
"name": "Slack Notifier",
"type": "webhook",
"createdAt": "2026-03-17T10:00:00.000Z"
}
],
"meta": { "total": 1 }
}Code Examples
curl https://api.chainabit.com/api/v1/workspaces/$WORKSPACE_ID/ai/tools \
-H "Authorization: Bearer $TOKEN"const BASE_URL = process.env.BASE_URL;
const TOKEN = process.env.TOKEN;
const workspaceId = process.env.WORKSPACE_ID;
const response = await fetch(`${BASE_URL}/workspaces/${workspaceId}/ai/tools`, {
headers: { Authorization: `Bearer ${TOKEN}` },
});
const { data } = await response.json();import requests, os
workspace_id = os.environ["WORKSPACE_ID"]
response = requests.get(
f"{os.environ['BASE_URL']}/workspaces/{workspace_id}/ai/tools",
headers={"Authorization": f"Bearer {os.environ['TOKEN']}"},
)
data = response.json()["data"]Workspace Skills
Manage reusable skill modules that agents can invoke. Skills have versioned releases.
Endpoints
| Method | Path | Description | Auth | Rate Limit |
|---|---|---|---|---|
| GET | /workspaces/:workspaceId/ai/skills | List skills | JWT + Entitlement | 60/min |
| GET | /workspaces/:workspaceId/ai/skills/:id | Get a skill | JWT + Entitlement | 60/min |
| POST | /workspaces/:workspaceId/ai/skills | Create a skill | JWT + Entitlement | 10/min |
| PATCH | /workspaces/:workspaceId/ai/skills/:id | Update a skill | JWT + Entitlement | 30/min |
| DELETE | /workspaces/:workspaceId/ai/skills/:id | Delete a skill | JWT + Entitlement | 10/min |
Skill Versions
| Method | Path | Description | Auth | Rate Limit |
|---|---|---|---|---|
| GET | /workspaces/:workspaceId/ai/skills/:skillId/versions | List versions | JWT + Entitlement | 60/min |
| GET | /workspaces/:workspaceId/ai/skills/:skillId/versions/:versionId | Get a version | JWT + Entitlement | 60/min |
| POST | /workspaces/:workspaceId/ai/skills/:skillId/versions | Create a version | JWT + Entitlement | 10/min |
| POST | /workspaces/:workspaceId/ai/skills/:skillId/versions/:versionId/publish | Publish a version | JWT + Entitlement | 10/min |
Ownership enforced.
GET .../versions/:versionIdverifies that the:skillIdbelongs to your account before returning the version. Providing aversionIdthat exists but belongs to a different skill or account returns404 Not Found.
Create Skill
Request
Use the id of the workspace as $WORKSPACE_ID.
Response
{
"data": {
"id": "cm5skill01",
"workspaceId": "cm5ws01",
"name": "Streak Analysis",
"description": "Analyze chain streak patterns and provide insights",
"createdAt": "2026-03-17T10:00:00.000Z"
}
}Code Examples
curl -X POST https://api.chainabit.com/api/v1/workspaces/$WORKSPACE_ID/ai/skills \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Streak Analysis",
"description": "Analyze chain streak patterns and provide insights",
"inputSchema": {
"chainId": { "type": "string", "required": true }
}
}'const BASE_URL = process.env.BASE_URL;
const TOKEN = process.env.TOKEN;
const workspaceId = process.env.WORKSPACE_ID;
const response = await fetch(`${BASE_URL}/workspaces/${workspaceId}/ai/skills`, {
method: "POST",
headers: {
Authorization: `Bearer ${TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "Streak Analysis",
description: "Analyze chain streak patterns and provide insights",
inputSchema: { chainId: { type: "string", required: true } },
}),
});
const { data } = await response.json();import requests, os
workspace_id = os.environ["WORKSPACE_ID"]
response = requests.post(
f"{os.environ['BASE_URL']}/workspaces/{workspace_id}/ai/skills",
headers={"Authorization": f"Bearer {os.environ['TOKEN']}"},
json={
"name": "Streak Analysis",
"description": "Analyze chain streak patterns and provide insights",
"inputSchema": {"chainId": {"type": "string", "required": True}},
},
)
data = response.json()["data"]Workspace Twin Policies
Configure behavioral policies for all twins within a workspace.
Endpoints
| Method | Path | Description | Auth | Rate Limit |
|---|---|---|---|---|
| GET | /workspaces/:workspaceId/ai/twin-policies | Get twin policies | JWT + Entitlement | 60/min |
| PUT | /workspaces/:workspaceId/ai/twin-policies | Upsert twin policies | JWT + Entitlement | 10/min |
| DELETE | /workspaces/:workspaceId/ai/twin-policies | Remove twin policies | JWT + Entitlement | 10/min |
Upsert Twin Policies
Request
Use the id of the workspace as $WORKSPACE_ID.
Response
{
"data": {
"workspaceId": "cm5ws01",
"maxMemoryEntries": 100,
"allowedActions": ["notification", "suggest", "analyze"],
"restrictedTopics": [],
"dataRetentionDays": 90,
"updatedAt": "2026-03-17T10:00:00.000Z"
}
}Code Examples
curl -X PUT https://api.chainabit.com/api/v1/workspaces/$WORKSPACE_ID/ai/twin-policies \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"maxMemoryEntries": 100,
"allowedActions": ["notification", "suggest", "analyze"],
"restrictedTopics": [],
"dataRetentionDays": 90
}'const BASE_URL = process.env.BASE_URL;
const TOKEN = process.env.TOKEN;
const workspaceId = process.env.WORKSPACE_ID;
const response = await fetch(`${BASE_URL}/workspaces/${workspaceId}/ai/twin-policies`, {
method: "PUT",
headers: {
Authorization: `Bearer ${TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
maxMemoryEntries: 100,
allowedActions: ["notification", "suggest", "analyze"],
restrictedTopics: [],
dataRetentionDays: 90,
}),
});
const { data } = await response.json();import requests, os
workspace_id = os.environ["WORKSPACE_ID"]
response = requests.put(
f"{os.environ['BASE_URL']}/workspaces/{workspace_id}/ai/twin-policies",
headers={"Authorization": f"Bearer {os.environ['TOKEN']}"},
json={
"maxMemoryEntries": 100,
"allowedActions": ["notification", "suggest", "analyze"],
"restrictedTopics": [],
"dataRetentionDays": 90,
},
)
data = response.json()["data"]