Skip to content

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 ​

MethodPathDescriptionAuthRate Limit
GET/workspaces/:workspaceId/ai/agentsList workspace agentsJWT + Entitlement60/min
POST/workspaces/:workspaceId/ai/agentsCreate a workspace agentJWT + Entitlement10/min
GET/workspaces/:workspaceId/ai/agents/:agentId/runsList agent runsJWT + Entitlement60/min
POST/workspaces/:workspaceId/ai/agents/:agentId/runsCreate an agent runJWT + Entitlement10/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 ​

FieldTypeRequiredDescription
namestringYesAgent display name
descriptionstringNoHuman-readable description of the agent's purpose
systemPromptstringNoSystem prompt that shapes the agent's behavior
toolIdsstring[]NoIDs 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 ​

json
{
  "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 ​

bash
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"]
  }'
javascript
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();
python
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/runs requires an active paid subscription. Callers without a valid subscription receive 403 Forbidden with { "code": "subscription_inactive" }. Credit balance alone is not sufficient — an active plan must also be present.

agentId must come from the Create Workspace Agent response. The agentId path segment must be the literal id field returned in data.id from Create Workspace Agent above — never an unset, empty, or hardcoded placeholder variable. A blank or malformed agentId previously returned an opaque 500 Internal Server Error; it now correctly returns 400 Bad Request.

Description ​

Starts a run for an existing workspace agent.

Request ​

FieldTypeRequiredDescription
inputobjectYesFreeform task input passed to the agent

Path params

ParamDescription
workspaceIdFrom your workspace lookup or creation response's data.id
agentIdFrom Create Workspace Agent's data.id

Response ​

json
{
  "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

StatusCondition
201 CreatedRun started successfully
400 Bad RequestMalformed or empty agentId path segment
403 ForbiddenNo active paid subscription (subscription_inactive)

Code Examples ​

bash
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"
    }
  }'
javascript
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();
python
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 have a nested list resource, but no nested single-run resource. To fetch a 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:


Workspace Twins ​

Workspace-scoped digital twins for team-level AI personas.

Endpoints ​

MethodPathDescriptionAuthRate Limit
GET/workspaces/:workspaceId/ai/twinsList workspace twinsJWT + Entitlement60/min
POST/workspaces/:workspaceId/ai/twins/:twinId/testTest twin interactionJWT + Entitlement20/min

List Workspace Twins ​

Request ​

Use the id of the workspace as $WORKSPACE_ID.

Response ​

json
{
  "data": [
    {
      "id": "YOUR_TWIN_ID",
      "workspaceId": "YOUR_WORKSPACE_ID"
    }
  ]
}

Code Examples ​

javascript
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`, {
  headers: { Authorization: `Bearer ${TOKEN}` },
});
const { data } = await response.json();
python
import requests, os

workspace_id = os.environ["WORKSPACE_ID"]
response = requests.get(
    f"{os.environ['BASE_URL']}/workspaces/{workspace_id}/ai/twins",
    headers={"Authorization": f"Bearer {os.environ['TOKEN']}"},
)
data = response.json()["data"]

Test Twin Interaction ​

Request ​

Use an id from List Workspace Twins as $TWIN_ID.

Response ​

json
{
  "data": {
    "twinId": "YOUR_TWIN_ID",
    "prompt": "Summarize yesterday's progress for the team",
    "output": "Twin Team Standup would answer in persona mode.",
    "persona": {}
  }
}

Code Examples ​

bash
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 '{
    "prompt": "Summarize yesterday's progress for the team"
  }'
javascript
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 List Workspace Twins

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({ prompt: "Summarize yesterday's progress for the team" }),
  }
);
const { data } = await response.json();
python
import requests, os

workspace_id = os.environ["WORKSPACE_ID"]
twin_id = os.environ["TWIN_ID"]  # from List Workspace Twins
response = requests.post(
    f"{os.environ['BASE_URL']}/workspaces/{workspace_id}/ai/twins/{twin_id}/test",
    headers={"Authorization": f"Bearer {os.environ['TOKEN']}"},
    json={"prompt": "Summarize yesterday's progress for the team"},
)
data = response.json()["data"]

Workspace Tools ​

Register tools available to all agents within a workspace.

Endpoints ​

MethodPathDescriptionAuthRate Limit
GET/workspaces/:workspaceId/ai/toolsList workspace toolsJWT + Entitlement60/min
POST/workspaces/:workspaceId/ai/toolsCreate a workspace toolJWT + Entitlement10/min

Webhook Tool Security Policy ​

Tools with executionType: "http_webhook" are subject to outbound request security enforcement:

  • HTTPS only — webhookUrl must use the https: 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.headers are 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 ​

json
{
  "data": [
    {
      "id": "cm5wtool01",
      "name": "Slack Notifier",
      "type": "webhook",
      "createdAt": "2026-03-17T10:00:00.000Z"
    }
  ],
  "meta": { "total": 1 }
}

Code Examples ​

bash
curl https://api.chainabit.com/api/v1/workspaces/$WORKSPACE_ID/ai/tools \
  -H "Authorization: Bearer $TOKEN"
javascript
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();
python
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 ​

MethodPathDescriptionAuthRate Limit
GET/workspaces/:workspaceId/ai/skillsList skillsJWT + Entitlement60/min
GET/workspaces/:workspaceId/ai/skills/:idGet a skillJWT + Entitlement60/min
POST/workspaces/:workspaceId/ai/skillsCreate a skillJWT + Entitlement10/min
PATCH/workspaces/:workspaceId/ai/skills/:idUpdate a skillJWT + Entitlement30/min
DELETE/workspaces/:workspaceId/ai/skills/:idDelete a skillJWT + Entitlement10/min

Skill Versions ​

MethodPathDescriptionAuthRate Limit
GET/workspaces/:workspaceId/ai/skills/:skillId/versionsList versionsJWT + Entitlement60/min
GET/workspaces/:workspaceId/ai/skills/:skillId/versions/:versionIdGet a versionJWT + Entitlement60/min
POST/workspaces/:workspaceId/ai/skills/:skillId/versionsCreate a versionJWT + Entitlement10/min
POST/workspaces/:workspaceId/ai/skills/:skillId/versions/:versionId/publishPublish a versionJWT + Entitlement10/min

Ownership enforced. GET .../versions/:versionId verifies that the :skillId belongs to your account before returning the version. Providing a versionId that exists but belongs to a different skill or account returns 404 Not Found.

Create Skill ​

Request ​

Use the id of the workspace as $WORKSPACE_ID.

Response ​

json
{
  "data": {
    "id": "cm5skill01",
    "workspaceId": "cm5ws01",
    "name": "completion history Analysis",
    "description": "Analyze chain completion history patterns and provide insights",
    "createdAt": "2026-03-17T10:00:00.000Z"
  }
}

Code Examples ​

bash
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": "completion history Analysis",
    "description": "Analyze chain completion history patterns and provide insights",
    "inputSchema": {
      "chainId": { "type": "string", "required": true }
    }
  }'
javascript
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: "completion history Analysis",
    description: "Analyze chain completion history patterns and provide insights",
    inputSchema: { chainId: { type: "string", required: true } },
  }),
});
const { data } = await response.json();
python
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": "completion history Analysis",
        "description": "Analyze chain completion history 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 ​

MethodPathDescriptionAuthRate Limit
GET/workspaces/:workspaceId/ai/twin-policiesGet twin policiesJWT + Entitlement60/min
PUT/workspaces/:workspaceId/ai/twin-policiesUpsert twin policiesJWT + Entitlement10/min
DELETE/workspaces/:workspaceId/ai/twin-policiesRemove twin policiesJWT + Entitlement10/min

Upsert Twin Policies ​

Request ​

Use the id of the workspace as $WORKSPACE_ID.

Response ​

json
{
  "data": {
    "workspaceId": "cm5ws01",
    "maxMemoryEntries": 100,
    "allowedActions": ["notification", "suggest", "analyze"],
    "restrictedTopics": [],
    "dataRetentionDays": 90,
    "updatedAt": "2026-03-17T10:00:00.000Z"
  }
}

Code Examples ​

bash
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
  }'
javascript
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();
python
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"]

Built with purpose.