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 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:

bash
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

MethodPathDescriptionAuthRate Limit
GET/workspaces/:workspaceId/ai/twinsList workspace twinsJWT + Entitlement60/min
POST/workspaces/:workspaceId/ai/twinsCreate a workspace twinJWT + Entitlement10/min
PUT/workspaces/:workspaceId/ai/twins/:twinId/personaUpdate twin personaJWT + Entitlement30/min
POST/workspaces/:workspaceId/ai/twins/:twinId/testTest twin interactionJWT + Entitlement20/min

Create Workspace Twin

Request

Use the id of the workspace as $WORKSPACE_ID.

Response

json
{
  "data": {
    "id": "cm5wtwin01",
    "workspaceId": "cm5ws01",
    "name": "Team Standup Twin",
    "persona": { "tone": "professional", "role": "scrum-master" },
    "createdAt": "2026-03-17T10:00:00.000Z"
  }
}

Code Examples

bash
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"
    }
  }'
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`, {
  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();
python
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

json
{
  "data": {
    "response": "Here is the team standup summary for yesterday...",
    "tokensUsed": 320,
    "latencyMs": 1200
  }
}

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 '{
    "message": "Summarize yesterday 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 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();
python
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

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 onlywebhookUrl 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": "Streak Analysis",
    "description": "Analyze chain streak 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": "Streak Analysis",
    "description": "Analyze chain streak 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: "Streak Analysis",
    description: "Analyze chain streak 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": "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

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.