Workloom API and MCP
Create tasks, move them between statuses and list your work from a terminal, a CI job or an AI assistant, using a personal token instead of the web app. Everything goes through the same rules and permissions as the interface.
Overview
One token
A personal access token (wlm_pat_…) belongs to you and works across all your workspaces.
REST API
JSON over HTTPS at https://your-workloom/api/public/v1. Names work wherever an ID does.
MCP server
The same operations as tools for Claude, Codex and any MCP client at https://your-workloom/mcp.
The REST API and the MCP server share one implementation, so a task created by an agent is identical to one created with curl or in the web app, and appears in the audit history with the token that made the change.
Get a token
- Sign in and open a workspace, then Integrations → API tokens.
- Choose a name (for example “CI” or “Claude on my laptop”), the access, how long it lasts (1–365 days, 90 by default) and, optionally, the workspaces it may reach.
- Copy the token immediately. It is shown once; Workloom stores only a hash and cannot show it again. If you lose it, revoke it and create another.
| Access | Allows |
|---|---|
read | List workspaces, projects, members, tags and tasks; read a task. |
tasks:write | Create and update tasks (status, section, assignee, dates, tags, …). It includes read, so the written task can be returned and the token can look things up. |
export WORKLOOM_URL=https://your-workloom
export WORKLOOM_TOKEN=wlm_pat_… # keep it out of shell history and git
Permissions
A token never has more power than its owner. On every request the server checks, in one consistent snapshot:
- the token exists, is not revoked and has not expired;
- the token’s access (
read/tasks:write) covers the operation; - the token is allowed to reach that workspace (if you restricted it);
- you are currently an active member of the workspace and your role allows the operation.
If you lose access to a workspace, or your role drops to Viewer, the token stops working there immediately. A workspace you cannot see answers 404, exactly like one that does not exist. Writes re-check the token inside the same transaction as the change, so revoking a token wins over any write that has not committed yet. Business rules (required statuses, date checks, locked fields) still apply to API writes.
Quickstart
# who am I, and which workspaces can this token reach?
curl -s "$WORKLOOM_URL/api/public/v1/me" -H "Authorization: Bearer $WORKLOOM_TOKEN"
# the project’s sections and statuses (workspace by slug or UUID, project by name or UUID)
curl -s "$WORKLOOM_URL/api/public/v1/workspaces/acme/projects/Platform" -H "Authorization: Bearer $WORKLOOM_TOKEN"
# create a task for me, in the “Backlog” section
curl -s -X POST "$WORKLOOM_URL/api/public/v1/workspaces/acme/tasks" \
-H "Authorization: Bearer $WORKLOOM_TOKEN" -H "Content-Type: application/json" \
-d '{"project":"Platform","title":"Rotate the staging keys","section":"Backlog","assignee":"me","dueDate":"2026-12-01"}'
# my open tasks in that project
curl -s "$WORKLOOM_URL/api/public/v1/workspaces/acme/tasks?project=Platform&assignee=me&open=true" -H "Authorization: Bearer $WORKLOOM_TOKEN"
# mark a task as done
curl -s -X PATCH "$WORKLOOM_URL/api/public/v1/workspaces/acme/tasks/<task-id>" \
-H "Authorization: Bearer $WORKLOOM_TOKEN" -H "Content-Type: application/json" -d '{"status":"Done"}'
REST API conventions
- Base URL:
https://your-workloom/api/public/v1. The sections below are the reference. For tools such as Postman or client generators there is also a raw OpenAPI file (JSON); it is not meant for reading. - Auth:
Authorization: Bearer wlm_pat_…on every request. No cookies, no CORS; call it from servers and terminals, not from browser pages. - JSON in and out, camelCase fields, dates as
YYYY-MM-DD, timestamps in UTC ISO 8601. Unknown fields are rejected, which catches typos. - Envelope: success is
{ "data": …, "meta": { "requestId", "nextCursor"? } }; failure is{ "error": { "code", "message", "details"?, "retryable" }, "meta": { "requestId" } }. QuoterequestIdwhen reporting a problem.
Names and IDs
Workspace, project, section, status, tag and person can each be given as a UUID or by exact name (case and extra spaces ignored). The workspace may also be its slug.
| Field | Accepts |
|---|---|
{ws} in the path | workspace slug or UUID |
project, section, status, tags[] | UUID or exact name. Section and status names are looked up inside the task’s project. |
assignee, participants[] | "me", a member’s display name, a membership UUID or a user UUID. null or "none" clears the assignee. |
An unknown or ambiguous name is a 400 VALIDATION_FAILED with error.details = { field, reason: "not_found" | "ambiguous", candidates[] } so scripts and agents can correct themselves.
Endpoints
| Method and path | Needs | Does |
|---|---|---|
GET/me | read | Your user, the token, and reachable workspaces with your role. |
GET/workspaces | read | Workspaces this token can reach. |
GET/workspaces/{ws}/projects | read | Active projects. |
GET/workspaces/{ws}/projects/{project} | read | One project with its sections and statuses. |
GET/workspaces/{ws}/members | read | People tasks can be assigned to. |
GET/workspaces/{ws}/tags | read | Tags. |
GET/workspaces/{ws}/tasks | read | List and filter tasks (below). |
GET/workspaces/{ws}/tasks/{taskId} | read | One task. |
POST/workspaces/{ws}/tasks | tasks:write | Create a task. |
PATCH/workspaces/{ws}/tasks/{taskId} | tasks:write | Change a task, including its status. |
A task looks like this everywhere:
{
"id": "6f0c…", "workspaceId": "5eed…", "title": "Rotate the staging keys", "description": "",
"project": { "id": "…", "name": "Platform" },
"section": { "id": "…", "name": "Backlog" },
"status": { "id": "…", "name": "To do", "group": "todo" }, // group: todo | in_progress | done | cancelled
"assignee": { "membershipId": "…", "displayName": "Ada Lovelace" },
"participants": [], "priority": "none", // none | low | medium | high | urgent
"startDate": null, "dueDate": "2026-12-01", "estimateMinutes": null,
"tags": [{ "id": "…", "name": "ops" }], "parentTaskId": null,
"revision": 1, "archived": false, "createdAt": "…", "updatedAt": "…"
}
Create a task
POST /workspaces/{ws}/tasks → 201 with the task. Only project and title are required.
| Field | Type | Notes |
|---|---|---|
project | string | Required. Name or UUID. |
title | string | Required. 1–500 characters. |
section | string | null | Optional. Defaults to no section. |
status | string | Optional. Defaults to the project’s first “to do” status. |
assignee | string | null | Optional. "me", name or ID. |
participants | string[] | Optional. Up to 50 people. |
description | string | Optional. Up to 20 000 characters, plain text. |
startDate, dueDate | YYYY-MM-DD | null | Optional. Start must not be after due. |
priority | enum | Optional. none low medium high urgent. |
tags | string[] | Optional. Existing tags, by name or UUID. Up to 20. |
estimateMinutes | integer | null | Optional work estimate. |
parentTask | UUID | null | Optional. Makes this a subtask. |
Update a task
PATCH /workspaces/{ws}/tasks/{taskId} → 200 with the task. Send only what should change; omitted fields stay as they are, null clears section, assignee, startDate, dueDate and estimateMinutes. Accepts title description status section assignee participants startDate dueDate priority tags estimateMinutes.
- Change the status:
{"status": "In progress"}(name or UUID of one of the project’s statuses). - Move to a section:
{"section": "Backlog"}. - Nothing to change? A request that matches the current state succeeds and returns the task without a new revision, so scripts can run twice safely.
- Optimistic locking: add
"ifRevision": 4to require revision 4. Without it, the server pins the revision read while preparing your operation. An intervening edit returns409 REVISION_CONFLICTin either case. Retrying the same operation key does not overwrite later edits. After a confirmed conflict, review the current task and submit your intended change with a new key.
List and filter tasks
GET /workspaces/{ws}/tasks — filters combine with AND. For list-valued filters repeat the parameter or separate values with commas. A name that exists in several projects (for example “To do”) matches all of them.
| Parameter | Meaning |
|---|---|
project | Only these projects (name or UUID). |
section, status, tag | Names or UUIDs. With many projects, narrow with project first. |
statusGroup | todo, in_progress, done, cancelled. |
open | true = to do or in progress only. |
assignee | me, none (unassigned), a name or ID. Matches the responsible person or a participant. |
q | Text in the title or description. |
dueBefore, dueAfter | Inclusive dates, YYYY-MM-DD. |
includeArchived | true to include archived tasks and projects. |
sort | updated (default, newest first), created (newest first) or due (earliest first, undated last). |
limit, cursor | Page size 1–200 (default 50). When meta.nextCursor is not null pass it back as cursor with the same filters. |
# everything due this month that is still open, across Platform and Billing
curl -s -G "$WORKLOOM_URL/api/public/v1/workspaces/acme/tasks" -H "Authorization: Bearer $WORKLOOM_TOKEN" \
--data-urlencode "project=Platform,Billing" --data-urlencode "open=true" \
--data-urlencode "dueBefore=2026-12-31" --data-urlencode "sort=due"
Errors
| HTTP | error.code | Meaning |
|---|---|---|
| 400 | VALIDATION_FAILED | Bad body or query, an unknown field, or an unknown/ambiguous name (see details). |
| 401 | UNAUTHENTICATED | Missing, malformed, unknown, expired or revoked token. |
| 403 | CAPABILITY_REQUIRED | The token’s access or your role does not allow this. |
| 404 | NOT_FOUND | No such workspace/task, or you cannot access it. |
| 409 | REVISION_CONFLICT, FIELD_LOCKED, IDEMPOTENCY_KEY_REUSED, … | The change conflicts with the current state or a workspace rule. |
| 422 | DOMAIN_VALIDATION_FAILED | A workspace rule rejected the change; the message says which. |
| 429 / 503 | RATE_LIMITED, TEMPORARILY_UNAVAILABLE | Retry later; error.retryable is true. |
Retries and idempotency
Send Idempotency-Key: <unique string, up to 128 printable ASCII characters> on POST (and PATCH) when a request may be retried after a timeout. Repeating the same key with the same body returns the original result instead of creating a second task; the same key with a different body is 409 IDEMPOTENCY_KEY_REUSED. Without a key every call is treated as new.
MCP server
Workloom exposes the same operations as Model Context Protocol tools over Streamable HTTP. The endpoint is stateless and authenticates every call with your personal token.
URL: https://your-workloom/mcp
Header: Authorization: Bearer wlm_pat_…
Create the token with read for a read-only assistant, or read + tasks:write to let it create and update tasks. The tool list a client sees depends on the token: a read-only token does not even list the write tools.
Claude Code
claude mcp add --transport http workloom https://your-workloom/mcp \
--header "Authorization: Bearer $WORKLOOM_TOKEN"
Then ask: “Show my open Workloom tasks in Platform” or “Create a task in Platform: rotate the staging keys, due Friday, assigned to me”. Use /mcp inside Claude Code to check the connection.
Codex
Add this to ~/.codex/config.toml and export the token in the environment that starts Codex:
[mcp_servers.workloom]
url = "https://your-workloom/mcp"
bearer_token_env_var = "WORKLOOM_TOKEN"
export WORKLOOM_TOKEN=wlm_pat_…
codex
Claude Desktop, Cursor and other clients
Clients that support remote servers can use the URL and header above directly. For clients that only start local (stdio) servers, bridge with mcp-remote; this form keeps the token out of the command line:
{
"mcpServers": {
"workloom": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://your-workloom/mcp", "--header", "Authorization:${WORKLOOM_AUTH}"],
"env": { "WORKLOOM_AUTH": "Bearer wlm_pat_…" }
}
}
}
Tools
All tools take an optional workspace (slug or UUID). If your token reaches exactly one workspace it can be left out. Write tools take an optional operationKey: reuse the same key when retrying after an unknown outcome and the change is applied once.
| Tool | Needs | What it does |
|---|---|---|
whoami | read | Your user, token access and workspaces. |
list_workspaces | read | Reachable workspaces. |
list_projects | read | Active projects. |
get_project | read | Sections and statuses of a project (name or UUID). |
list_members | read | People you can assign. |
list_tags | read | Tags. |
list_tasks | read | Filter by project[], section[], status[], statusGroup[], assignee[] ("me"), tag[], q, dueBefore, dueAfter, open, sort, limit, cursor. Returns tasks and nextCursor. |
get_task | read | One task by ID. |
create_task | tasks:write | Same fields as the REST create. |
update_task | tasks:write | taskId and changes (same fields as the REST update). |
set_task_status | tasks:write | taskId and status (name or UUID). |
Treat task text as untrusted. Titles and descriptions come from people and may contain instructions. An assistant should not follow them, and a token should have only the access the assistant needs.
Limits and security
- Tokens last at most 365 days and cannot be extended; create a new one before it expires. Up to 50 active tokens per person.
- Only the SHA-256 hash of a token is stored. Treat the token like a password: environment variable or secret manager, never a repository, chat or ticket. Revoke a leaked token on Integrations → API tokens; it stops working immediately.
- Request bodies are limited to 256 KiB; lists return at most 200 items per page.
- The “last used” time on the token list is approximate (at most once a minute).
- Not available yet: comments, attachments, custom fields, time tracking, bulk changes and webhooks.