Boards and workspaces
Workspaces isolate each team's data — boards, agents, files, runtime configuration — from other teams. Boards are kanban-style task queues inside a workspace: tasks live in columns, and a task assigned to an agent can be dispatched as an agent run directly from the board.
All endpoints require authentication via X-API-Key: pk_live_... or Authorization: Bearer <jwt-or-key>. The active workspace is resolved from the X-Workspace-Id header (or the workspace_id query parameter) and falls back to your Personal workspace when omitted. Read endpoints require the API-key scope mcp:agents:read; write endpoints require mcp:agents:write — JWT callers are gated by workspace role instead, noted per endpoint. Board endpoints operate on the active workspace and return 404 for resources outside it.
Workspaces
Workspace object fields:
| Field | Type | Description |
|---|---|---|
id |
string (UUID) | Workspace identifier |
name |
string | Display name |
slug |
string | URL-safe unique slug |
plan |
string | Billing plan (free on creation) |
auto_approve |
boolean | Whether agent task proposals skip manual approval |
summoro_workspace_id |
string (UUID) | null | Mirrored control-plane workspace id |
created_at, updated_at |
string (ISO 8601) | Timestamps |
POST /v1/workspaces/ensure-personal
Return the caller's personal workspace, creating it (with an owner membership) when none exists. Despite being a POST, it requires only the read scope mcp:agents:read.
Request body (optional): name (string) — defaults to "<user-id-prefix> Personal".
Response — a workspace object.
curl -X POST "https://api.perceive8.com/v1/workspaces/ensure-personal" \
-H "X-API-Key: pk_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"name": "Ada Personal"}'
GET /v1/workspaces
List workspaces the caller is a member of, newest first. Response — an array of workspace objects.
POST /v1/workspaces
Create a workspace owned by the caller. JWT callers need the admin or owner role in the active workspace.
Request body
{
"name": "Sales",
"slug": "sales"
}
name (string, required, 1–255); slug (string, optional, max 63 — slugified from name when omitted; a random suffix is appended on collision).
Response — the created workspace object.
GET /v1/workspaces/{workspace_id}
Get a workspace by id. The caller must be a member (404 otherwise; 400 on a malformed UUID). Response — a workspace object.
PATCH /v1/workspaces/{workspace_id}
Rename a workspace. JWT callers need admin/owner; membership required. Body: name (string, required, 1–255). Response — the updated workspace object.
GET /v1/workspaces/{workspace_id}/runtime/config
Get the workspace's runtime environment defaults, applied to agent runtimes that don't override them. Query parameter: runtime_kind (string, default openclaw).
Response — a flat object of environment variable name → value; {} when unset.
PUT /v1/workspaces/{workspace_id}/runtime/config
Replace the env defaults for a (workspace, runtime_kind) pair. JWT callers need admin/owner.
Request body
{
"runtime_kind": "openclaw",
"env": { "LOG_LEVEL": "debug" }
}
runtime_kind (string, default openclaw); env (object of string → string, default {}) — stored as a full replacement.
Response — the stored env object.
Boards
Board object fields: id (integer), workspace_id (UUID), title, description (string | null), color (string), creator (string | null), created_at, updated_at (ISO 8601), columns (array of column objects on create, otherwise null).
GET /v1/boards
List boards in the active workspace, newest first. Response — an array of board objects.
POST /v1/boards
Create a board. Write access required.
Request body
{
"title": "Outbound Q3",
"description": "Sequences and follow-ups",
"color": "bg-blue-500",
"creator": "[email protected]",
"columns": [{ "title": "To do", "sort_order": 0 }]
}
title(string, required, 1–255)description,creator(string, optional)color(string, defaultbg-blue-500)workspace_id(UUID, optional — must equal the active workspace when sent, else403)columns(array of{title, sort_order}, optional — when omitted, five defaults are seeded: Backlog, Blocked, In Progress, In Review, Done)
Response — the board object including the created columns array.
curl -X POST "https://api.perceive8.com/v1/boards" \
-H "X-API-Key: pk_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"title": "Outbound Q3"}'
GET /v1/boards/{board_id}
Get a board with its columns, tasks, and workspace members.
Response — an object with:
| Field | Description |
|---|---|
board |
The board object |
columns |
Column objects ordered by sort_order, each with a tasks array of task objects |
users |
Workspace members as {id, email, full_name} |
PATCH /v1/boards/{board_id}
Update a board. Write access required. Body: any subset of title, description, color (400 when empty). Response — the updated board object.
DELETE /v1/boards/{board_id}
Delete a board together with its columns and tasks. Write access required. Response — {"ok": true}.
Columns
Column object fields: id (integer), board_id (integer), title, sort_order (integer), created_at, updated_at (ISO 8601).
POST /v1/boards/{board_id}/columns
Create a column. Write access required. Body: title (string, required, 1–255), sort_order (integer, default 0). Response — the created column object.
PATCH /v1/boards/{board_id}/columns/{column_id}
Update title and/or sort_order. Write access required; 400 when no fields are sent. Response — the updated column object.
DELETE /v1/boards/{board_id}/columns/{column_id}
Delete a column and all tasks in it. Write access required. Response — {"ok": true}.
Tasks
Task object fields:
| Field | Type | Description |
|---|---|---|
id |
integer | Task identifier |
column_id |
integer | Column the task sits in |
title |
string | Task title |
description |
string | null | — |
assignee |
string | null | Free-form display assignee (send "" on PATCH to clear) |
due_date |
string (date) | null | YYYY-MM-DD |
priority |
string | Free-form, default medium |
sort_order |
integer | Position within the column |
assignee_type |
string | null | user or agent |
assignee_agent_id |
string (UUID) | null | Owning agent; required when assignee_type is agent |
trigger |
object | {"type": "manual"} (default) or {"type": "schedule"} |
schedule_cron |
string | null | 5-field cron expression for recurring runs |
status |
string | queued (default), running, needs_review, done, failed, cancelled |
current_run_id |
string (UUID) | null | Latest agent run |
analysis_id, report_id |
string (UUID) | null | Linked analysis / report |
source_action_item_id |
string | null | Originating action item id |
category |
string | null | Server-populated category |
risk_tier |
string | null | low, internal, outward, pipeline (server-populated) |
integration_kind |
string | null | Server-populated integration type |
integration_payload |
object | null | Server-populated integration data |
execution_result |
object | null | Last run outcome (last_run_outcome, reply_preview, error, finished_at) |
approval_status |
string | not_required (default), pending, approved, rejected |
approval_decided_by |
string (UUID) | null | User who decided |
approval_decided_at |
string (ISO 8601) | null | Decision timestamp |
created_at, updated_at |
string (ISO 8601) | Timestamps |
POST /v1/boards/{board_id}/tasks
Create a task in a column of the board. Write access required; 404 when the column is not on this board.
Request body
{
"column_id": 41,
"title": "Draft renewal email for Acme",
"priority": "high",
"due_date": "2026-08-29",
"assignee_type": "agent",
"assignee_agent_id": "a12b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"trigger": { "type": "schedule" },
"schedule_cron": "0 9 * * 1"
}
column_id(integer, required),title(string, required, 1–500)description,assignee(string, optional);due_date(date, optional)priority(string, defaultmedium),sort_order(integer, default0)assignee_type(user|agent) withassignee_agent_id(UUID of a non-archived agent in the workspace; required whenagent)trigger—{"type": "manual"}(default) or{"type": "schedule"}; other types are rejected with400schedule_cron— must be a 5-field cron expression, else400
Response — the created task object.
PATCH /v1/boards/{board_id}/tasks/{task_id}
Update a task. Write access required. Accepts any subset of the create fields plus status; 400 when no fields are sent.
assignee: ""clears the display assignee (stored asnull).assignee_agent_idwithoutassignee_type: "agent"returns400.- Reassigning away from an agent clears
schedule_cronand resetstriggertomanual(explicit values in the same request win). - Setting
triggerto{"type": "manual"}clearsschedule_cron.
Response — the updated task object.
POST /v1/boards/{board_id}/tasks/{task_id}/move
Move a task to another column and/or position. Write access required. Body: column_id (integer, required) and sort_order (integer, required). Response — the updated task object.
DELETE /v1/boards/{board_id}/tasks/{task_id}
Delete a task. Write access required. Response — {"ok": true}.
POST /v1/boards/{board_id}/tasks/{task_id}/dispatch
Dispatch an agent-assigned task as an agent run. Any workspace member can dispatch (JWT); API keys need mcp:agents:write. The run executes synchronously: the response returns after the run finishes, with the task's status and execution_result written back. A done task is auto-moved to the board's Done column.
Response — {"task": <updated task object>, "run_id": "<agent-run UUID>"}.
Errors: 400 when the task is not agent-assigned; 409 when it is already running, its approval was rejected, or approval is pending and the caller is not an approver (an owner/admin/manager's dispatch doubles as the approval); 503 when the dispatch backend is not configured.
curl -X POST "https://api.perceive8.com/v1/boards/12/tasks/87/dispatch" \
-H "X-API-Key: pk_live_xxxxxxxxxxxx"
POST /v1/boards/{board_id}/tasks/{task_id}/approve
Approve a task with approval_status: "pending" so it can be dispatched. JWT callers need the owner, admin, or manager role; API keys need mcp:agents:write. 409 when the task is not awaiting approval. Response — the updated task object with approval_status: "approved" and approval_decided_by/approval_decided_at set.
POST /v1/boards/{board_id}/tasks/{task_id}/reject
Reject an approval-pending task; rejected tasks cannot be dispatched. Same authorization and 409 semantics as approve. Response — the updated task object with approval_status: "rejected".
GET /v1/boards/{board_id}/tasks/{task_id}/comments
List comments on a task, oldest first.
Response — an array of comment objects: id (UUID), task_id (integer), author_type (user | agent), author_user_id (UUID | null), author_agent_id (UUID | null), author_email, author_full_name, author_agent_name (string | null), body (string), created_at (ISO 8601).
POST /v1/boards/{board_id}/tasks/{task_id}/comments
Add a comment as the current user. Write access required. Agent comments are written by the platform, never through this endpoint. Body: body (string, required, 1–10000). Response — the created comment object.
Aria goal check
Aria goal check is a periodic sweep that reviews a workspace's boards and proposes — and optionally creates and dispatches — tasks toward the workspace's goals. These endpoints manage the sweep settings, trigger runs manually, and inspect run history. The path workspace_id must match the caller's active workspace (403 otherwise).
GET /v1/workspaces/{workspace_id}/aria-goal-check/settings
Get the goal-check settings. Any workspace member.
Response
{
"enabled": true,
"frequency_hours": 4,
"mode": "balanced",
"last_run_at": "2026-08-18T06:00:00+00:00"
}
Defaults when never configured: enabled: true, frequency_hours: 4, mode: "balanced", last_run_at: null.
PATCH /v1/workspaces/{workspace_id}/aria-goal-check/settings
Update the settings. Requires the admin or owner role. Body: any subset of enabled (boolean), frequency_hours (integer, 1–168), mode (conservative | balanced | aggressive); 400 when empty. Response — the settings object, same shape as the GET.
POST /v1/workspaces/{workspace_id}/aria-goal-check/trigger
Manually queue a goal-check run. Requires the admin or owner role. The background worker skips the run when the sweep is disabled, when the last run is newer than frequency_hours, or when the workspace is not a main workspace.
Response — {"task_id": "<UUID>", "message": "Goal check triggered"}.
GET /v1/workspaces/{workspace_id}/aria-goal-check/runs
List recent goal-check runs, newest first. Any workspace member. Query parameters: limit (integer, default 20), offset (integer, default 0).
Response — an array of run summaries:
| Field | Type | Description |
|---|---|---|
id |
string (UUID) | Run identifier |
started_at |
string (ISO 8601) | Start timestamp |
finished_at |
string (ISO 8601) | null | End timestamp (null while running) |
status |
string | running, completed, or failed |
proposed_actions_count |
integer | Proposed task actions |
created_task_count |
integer | Tasks created |
dispatched_task_count |
integer | Tasks dispatched to agents |
model |
string | null | LLM used for proposals |
error |
string | null | Failure detail when failed |
GET /v1/workspaces/{workspace_id}/aria-goal-check/runs/{run_id}
Get one run with its snapshot and proposed tasks. Any workspace member; 400 on a malformed run_id, 404 when the run belongs to another workspace.
Response — the shared run fields (id, started_at, finished_at, status, model, error) plus:
| Field | Type | Description |
|---|---|---|
workspace_id |
string (UUID) | Workspace the run swept |
snapshot |
object | null | Workspace state captured at run start |
proposed_tasks |
array | Proposed task actions (objects) |
created_task_ids |
array (integer) | Created task ids |
dispatched_task_ids |
array (integer) | Dispatched task ids |
input_tokens, output_tokens |
integer | null | LLM token usage |