Ir al contenido
CalabDocs

La documentación para desarrolladores está disponible en inglés.

Boards and tasks

Boards, board categories, statuses, labels, milestones, views, tasks, assignees, watchers, checklists, task milestones, the archive and automations.

A bot works with task boards like a person, within the permissions of its roles and the overrides on the board. It creates and runs tasks, assigns people, comments, keeps checklists and milestones, and reads the journal and automation rules. A bot can be a task's assignee, and it can be let into a private board personally.

ActionPermission
see a board and its tasksVIEW_BOARD
create tasks; edit its own and those it is assigned toCREATE_TASKS
edit, move and archive any taskEDIT_TASKS
statuses, labels, milestones, settings, board features, the board archiveMANAGE_BOARD
create a board, board categoriesCREATE_BOARDS
the board journalMANAGE_BOARD, EDIT_TASKS or VIEW_JOURNALS
the rule run logMANAGE_BOARD

Routes

MethodPathWhat it does
GET/api/workspaces/{id}/boardsBoards the bot sees (?archived=1 — archived ones) → {boards}
POST/api/workspaces/{id}/boardsCreate a board → 201 {board} (CREATE_BOARDS)
GET/api/boards/{id}A board with statuses, labels, milestones and views → {board}
PATCH/api/boards/{id}Board settings, features, estimate scale (MANAGE_BOARD)
DELETE/api/boards/{id}Move the board to the archive (MANAGE_BOARD)
POST/api/boards/{id}/restoreRestore a board from the archive
PUT/api/boards/{id}/positionThe board's place in the list
GET/api/boards/{id}/permissionsThe board's permission overrides (read-only)
GET/api/workspaces/{id}/board-categoriesBoard categories → {categories}
POST/api/workspaces/{id}/board-categoriesCreate a board category (CREATE_BOARDS)
PATCH/api/board-categories/{id}Change a board category
DELETE/api/board-categories/{id}Delete a board category
PUT/api/workspaces/{id}/boards/orderOrder boards and categories in one operation
POST/api/boards/{id}/statusesAdd a status (MANAGE_BOARD, ≤ 20)
PATCH/api/boards/{id}/statuses/{sid}Change a status
DELETE/api/boards/{id}/statuses/{sid}Delete a status (?move_to=<sid> — where its tasks go)
POST/api/boards/{id}/labelsAdd a label (≤ 50)
PATCH/api/boards/{id}/labels/{sid}Change a label
DELETE/api/boards/{id}/labels/{sid}Delete a label
POST/api/boards/{id}/milestonesAdd a board milestone (MANAGE_BOARD, ≤ 50)
PATCH/api/boards/{id}/milestones/{sid}Change a board milestone
DELETE/api/boards/{id}/milestones/{sid}Delete a board milestone
GET/api/boards/{id}/viewsShared views and the bot's own → {views}
POST/api/boards/{id}/viewsCreate a view
PATCH/api/boards/{id}/views/{sid}Change a view
DELETE/api/boards/{id}/views/{sid}Delete a view
POST/api/boards/{id}/filesUpload a file for a task description or comment → {file}
GET/api/boards/{id}/activityThe board journal, oldest first; &format=csv exports it
GET/api/boards/{id}/tasksBoard tasks: ?filter=&archived=1&updated_after=&cursor=&limit= → {tasks, nextCursor}
POST/api/boards/{id}/tasksCreate a task → 201 TaskResponse (CREATE_TASKS)
GET/api/tasks/{id}A task with subtasks, relations, parent and comment room
PATCH/api/tasks/{id}Change task fields
POST/api/tasks/{id}/archiveArchive a task
POST/api/tasks/{id}/restoreRestore a task from the archive
PUT/api/tasks/{id}/assigneesThe full assignee list {assignees: [{userId, isLead, note}]}
PUT/api/tasks/{id}/approversApprovers and the quorum (a bot is never an approver itself)
PUT/api/tasks/{id}/relationsAdd a relation {relatedId, kind}
DELETE/api/tasks/{id}/relationsRemove a relation ?related_id=&kind=
PUT/api/tasks/{id}/subscriptionThe bot's subscription to the task {muted}
PUT/api/tasks/{id}/watchersAdd a watcher {userId} (≤ 50)
DELETE/api/tasks/{id}/watchersRemove a watcher ?user_id=
PUT/api/tasks/{id}/readMark the task read
GET/api/tasks/{id}/activityComments and the task journal: ?before=&limit=&only=journal|comments
GET/api/t/{key}A task by key FNG-12 (with its board and room)
GET/api/me/tasksThe bot's tasks: ?workspace_id=&scope=assigned|lead|created|subscribed&open=1&cursor=
GET/api/workspaces/{id}/tasks/searchSearch tasks by key and words: ?q=&limit= (≤ 50) → {tasks}
POST/api/tasks/{id}/checklistsAdd a checklist {title, position?}
PATCH/api/checklists/{id}Change a checklist
DELETE/api/checklists/{id}Delete a checklist
POST/api/checklists/{id}/itemsAdd an item {text, position?}
PATCH/api/checklist-items/{id}Change an item: text, done, position, checklistId
DELETE/api/checklist-items/{id}Delete an item
POST/api/checklist-items/{id}/convertTurn an item into a subtask
POST/api/tasks/{id}/milestonesAdd a task milestone {name, dueOn?} → {milestone, task}
PATCH/api/task-milestones/{id}Change a task milestone {name?, dueOn?, position?, completed?}
DELETE/api/task-milestones/{id}Delete a task milestone
GET/api/boards/{id}/rulesThe board's automation rules (VIEW_BOARD)
GET/api/rules/{id}/runsA rule's run log (MANAGE_BOARD)

Not available to bots

These routes answer a bot with 403 BOT_NOT_ALLOWED, whatever permissions it has:

  • board access PUT /api/boards/{id}/permissions and the final delete DELETE /api/boards/{id}?purge=1;
  • voting on an approval POST /api/tasks/{id}/approval — a bot never approves tasks;
  • the board webhook /api/boards/{id}/webhook… — it holds a secret and exports data (people set it up, see Webhooks);
  • creating, changing, deleting and testing automation rules: POST /api/boards/{id}/rules, PATCH · DELETE /api/rules/{id}, POST /api/rules/{id}/test;
  • connecting a Git repository to a board /api/boards/{id}/git.

Create a task

CreateTaskRequest: title (1..200), description (≤ 20,000, markdown-lite), statusId (empty = the default status), priority (TASK_PRIORITY_NONE…TASK_PRIORITY_URGENT), assignees, labelIds, startOn / dueOn (YYYY-MM-DD), estimate, parentId, milestoneId, attachmentIds, approverIds, approvalRequired, fromMessageId (create from a message the bot can see).

curl -s -X POST $CALAB/api/boards/$BOARD/tasks \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d "{\"title\": \"Renew certificates\", \"priority\": \"TASK_PRIORITY_HIGH\", \"dueOn\": \"2026-10-20\",
       \"assignees\": [{\"userId\": \"$USER\", \"isLead\": true}]}"

Response 201 is a TaskResponse {task, subtasks, related, parent, room}:

JSON
{
  "task": {
    "id": "0192b7e0-0000-7000-8000-0000000000dd",
    "boardId": "0192a000-0000-7000-8000-0000000000bb",
    "number": 42,
    "key": "OPS-42",
    "title": "Renew certificates",
    "description": "",
    "statusId": "0192a000-0000-7000-8000-0000000000c1",
    "priority": "TASK_PRIORITY_HIGH",
    "assignees": [{"userId": "0191c2d3-0000-7000-8000-000000000042", "isLead": true, "note": "", "assignedBy": "0192a1b2-3c4d-7e5f-8a9b-0c1d2e3f4a5b", "assignedAt": "2026-10-14T10:00:00Z"}],
    "createdBy": "0192a1b2-3c4d-7e5f-8a9b-0c1d2e3f4a5b",
    "dueOn": "2026-10-20",
    "roomId": "0192b7e0-0000-7000-8000-0000000000ee",
    "checklistTotal": 0,
    "checklistDone": 0,
    "milestones": [],
    "taskMilestoneId": "",
    "watcherIds": [],
    "createdAt": "2026-10-14T10:00:00Z"
  },
  "subtasks": [],
  "related": []
}

A task comment is an ordinary message in the task's hidden room task.roomId: the same message routes and reactions; files go through POST /api/boards/{id}/files. The SDK's bot.tasks.comment(task, text) does this for you.

Update and find

await bot.tasks.update(task.id, { statusId: doneStatusId });
await bot.tasks.setAssignees(task.id, [{ userId, isLead: true, note: 'owns the renewal' }]);
const found = await bot.tasks.search(wsId, 'OPS-42');
const { task: t, subtasks } = await bot.tasks.get(task.id);
const all = await bot.tasks.list(boardId);   // every page

GET /api/boards/{id}/tasks returns up to 500 tasks per page (by status, then position); an empty nextCursor means the last page. filter is a JSON TaskFilter {conditions, any} (≤ 30 conditions). Archived tasks come only with archived=1 or an “archived” condition.

Assignees, approvers, watchers

Any human member who is not a guest can be an assignee, approver or watcher — even without access to the board, on a private board too (they then see the board only through their tasks: Board.taskScoped). A bot qualifies only if it sees the board, and never as an approver; otherwise 422. A bot itself never sees a board through its tasks — it needs VIEW_BOARD. Up to 50 watchers (409).

Checklists

Checklists are edited like task fields: EDIT_TASKS, or CREATE_TASKS for the bot's own tasks and those it is assigned to. They need the Team plan (409 PLAN_LIMIT). Limits: ≤ 10 checklists per task (CHECKLIST_LIMIT), ≤ 100 items per checklist (CHECKLIST_ITEM_LIMIT), ≤ 50 board categories per workspace (BOARD_CATEGORY_LIMIT); all are 409 CONFLICT with used/limit.

const { checklist } = await bot.tasks.checklists.create(task.id, 'Before release');
await bot.tasks.checklists.addItem(checklist.id, 'Update the gateway certificate');
await bot.tasks.checklists.updateItem(itemId, { done: true });
const { task: subtask } = await bot.tasks.checklists.convertItem(itemId);   // item → subtask

Task milestones

Milestones inside a task are stages with their own dates: POST /api/tasks/{id}/milestones {name, dueOn?}. The answer is {milestone, task}; milestones and progress (milestones, milestoneProgress, taskMilestoneId) are in every task and in the TASK_UPDATE event. A subtask is linked to its parent's milestone with PATCH /api/tasks/{id} {taskMilestoneId}.

  • ≤ 20 milestones per task (409 TASK_MILESTONE_LIMIT); subtasks have none (422); a subtask links only its parent's milestone (422).
  • While subtasks are linked, the server keeps the milestone's completion: a manual completed is 409 TASK_MILESTONE_AUTO.
  • The “Milestones” feature is off on the board — 409 FEATURE_DISABLED.
const { milestone } = await bot.tasks.milestones.create(task.id, 'Testing', '2026-10-18');
await bot.tasks.milestones.update(milestone.id, { completed: true });

Board features and categories

PATCH /api/boards/{id} {setDisabledFeatures, disabledFeatures, estimateScale} (MANAGE_BOARD) switches board features on and off. With a feature off, a request that changes its field to a non-empty value gets 409 CONFLICT, reason: "FEATURE_DISABLED", with field set to the field name (estimate, dueOn, approverIds…). Clearing the field and repeating the current value pass.

import { BoardFeature } from '@calaba/bot-sdk';

await bot.boards.setFeatures(boardId, { disabledFeatures: [BoardFeature.MILESTONES] });
const cat = await bot.boards.categories.create(wsId, 'Operations');
await bot.boards.setOrder(wsId, { boards: [{ boardId, categoryId: cat.id, position: 0 }] });

Automations

A bot reads a board's rules (GET /api/boards/{id}/rules) and their run log (GET /api/rules/{id}/runs). Task changes made by a bot run the board's rules just like people's changes. Changes made by rules appear in the journal with an empty actorId and a ruleId.

Gotchas