Workspace-scoped API keys, a clean REST surface, signed webhooks, and an official MCP server — connect your sites, forms, systems, and AI assistants to your Workel workspace.
In Workel, open Settings → Developers → Create key (workspace owners and admins only). Pick the scopes the integration needs, then copy the wk_… secret — it is shown exactly once.
curl https://api.workel.com/api/public/v1/me \
-H "Authorization: Bearer wk_YOUR_KEY"
GET /me returns your workspace, the key's scopes, and its rate-limit state — the "is my key working" endpoint.
curl -X POST https://api.workel.com/api/public/v1/tasks \
-H "Authorization: Bearer wk_YOUR_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-4821" \
-d '{"project_id": "PROJECT_ID", "title_text": "New order #4821"}'
Find PROJECT_ID via GET /projects. The task appears on the team's board in realtime, attributed to the key's creator with a via API label.
Every request carries Authorization: Bearer wk_<secret>. Keys are minted by a workspace owner or admin and act as their creator: writes are recorded and notified as that person (labeled via API). If the creator loses owner/admin membership, the key stops working within ~60 seconds. Keys can be rotated with an overlap window and revoked instantly from the Developers tab — a key can never mint, widen, or manage keys or webhooks itself.
Each key holds only the scopes you grant it:
| Scope | Grants |
|---|---|
| read:projects | List/read projects and their board columns |
| read:tasks | List/read tasks and their comments |
| read:events | List calendar events |
| read:members | List active workspace members (incl. email, for identity mapping) |
| write:tasks | Create and update tasks |
| write:events | Create calendar events |
| write:comments | Comment on tasks |
wk_ key in browser or mobile code. If a key leaks: Developers tab → Revoke (instant), or Rotate with zero grace. Each key's last-used time and IP are shown to help triage.Full request/response schemas, parameters, and examples for every operation live in the API reference (rendered from the OpenAPI 3.1 spec, which is verified against the live route table by an automated parity test).
| Path | Scope | Description | |
|---|---|---|---|
| GET | /me | — | Key + workspace + rate-limit state |
| GET | /projects | read:projects | List visible projects with task counts |
| GET | /projects/{id} | read:projects | One project |
| GET | /projects/{id}/cards | read:projects | Board columns — target one on task create |
| GET | /members | read:members | Active members: id, name, role, email |
| GET | /tasks | read:tasks | Tasks across visible projects; filters: project_id, card_id, completed, due_before/after, updated_since |
| POST | /tasks | write:tasks | Create a task (column optional — defaults to the first open one) |
| GET | /tasks/{id} | read:tasks | One task, incl. description and assignees |
| PATCH | /tasks/{id} | write:tasks | Update title, description, priority, due date/time, progress |
| GET | /tasks/{id}/comments | read:tasks | A task's comments |
| POST | /tasks/{id}/comments | write:comments | Comment on a task |
| GET | /events | read:events | Workspace + project calendar events in a date window |
| POST | /events | write:events | Create a calendar event, with optional invitees |
Always {"error": {type, code, message, param?, request_id}}. Every response carries an X-Request-Id header — quote it in support requests.
Cursor-based: {"data": […], "meta": {"next_cursor"}}. No offset pages — polling under concurrent writes stays consistent.
Every POST honors Idempotency-Key. Same key + same body replays the stored response (Idempotent-Replay: true); same key + different body → 409. Failed attempts are never stored, so retries re-run the work.
Per key and per workspace, never by IP — plus a tighter write budget on POST/PATCH, so a runaway loop can't eat your read capacity. A write spends one of each. 429s name the exceeded limiter and include retry_after; GET /me reports all three.
A key sees exactly one workspace. Resources outside it — or in private/personal spaces — return 404, never 403.
Writes appear in Workel as the key's creator, labeled via API, so teammates always know a machine acted.
Register HTTPS endpoints from Settings → Developers → Webhooks and Workel calls you when things change — including changes your team makes in the Workel UI, so you never poll. Seven event types:
task.created task.updated task.completed task.deleted comment.created event.created project.created
Deliveries retry with backoff for hours on failure; an endpoint that keeps failing is auto-disabled (re-enable it from the tab, where a full delivery log and a "Send test event" button live). Payloads are deliberately thin — identifiers and safe fields, never free text; fetch detail with your key.
Each request is signed over its exact raw body:
Workel-Signature: t=1755950000,v1=5257a86… ← HMAC-SHA256(secret, "{t}.{rawBody}")
Workel-Event-Id: evt_8f3ka92x ← stable, dedupe on it
Workel-Event-Type: task.completed
// Node — express example (use the RAW body, not parsed JSON)
const crypto = require("crypto");
function verify(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(",").map(p => p.split("=")));
if (Math.abs(Date.now()/1000 - Number(parts.t)) > 300) return false; // 5-min replay window
const expected = crypto.createHmac("sha256", secret)
.update(`${parts.t}.${rawBody}`).digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}
// PHP
function verify(string $rawBody, string $header, string $secret): bool {
parse_str(str_replace(',', '&', $header), $parts);
if (abs(time() - (int) $parts['t']) > 300) return false;
$expected = hash_hmac('sha256', $parts['t'].'.'.$rawBody, $secret);
return hash_equals($expected, $parts['v1']);
}
During a signing-secret rotation, deliveries carry v1= entries for both the current and previous secret, so verification never breaks mid-rotation.
The Workel MCP server exposes a workspace to Claude, the OpenAI Agents SDK, or any other MCP client as a set of typed tools — read projects, tasks, comments, members and events, and optionally create and update work. It is a thin client of this API and holds no authority the credential behind it does not already have: a curl request made with the same credential can do exactly what it can, no more.
There are two ways to connect. If you use Claude, use the hosted connector — it needs no config file and no API key. The local npx server below is for Claude Code, CI agents, and the OpenAI Agents SDK, where you run the process and hold the credential yourself.
Add this URL as a connector in Claude — Settings → Connectors → Add custom connector:
https://mcp.workel.com/mcp
Claude sends you to Workel to sign in, you pick one workspace, and you land back in Claude connected. There is no API key to create, copy or store — you never see one, and neither does Claude.
Once connected, ask Claude things like “what tasks are open in my Bugs project, and which are overdue?”, “summarize what changed across my projects this week”, “create a task in Bugs to fix the login redirect, due Friday”, or “who is on this workspace, and what's on the calendar next week?”
Everything from here down describes the local npx server, where you mint and hold an API key yourself.
The tools a client sees are decided by the scopes on your key, checked by this API on every call. Two local settings narrow that further, and neither can widen it:
write:* scope and WORKEL_ENABLE_WRITES=true is set. A read-only install simply never registers them.claude-desktop-laptop, ci-agent). If a laptop is lost, revoke that one key rather than rotating a key several tools share — revocation takes effect on the next request.The server never logs, echoes or returns your key, and it writes every diagnostic to stderr because stdout belongs to the protocol. Note that WORKEL_ENABLE_WRITES is an operator preference, not a security control — it lives in a file an agent on your machine can edit. The key's scopes are the real gate.
Settings → Developers → Create key, granting only read:projects, read:tasks, read:members and read:events. Do not reuse a key another integration already holds.
Claude Desktop — ~/Library/Application Support/Claude/claude_desktop_config.json on macOS, %APPDATA%\Claude\claude_desktop_config.json on Windows:
{
"mcpServers": {
"workel": {
"command": "npx",
"args": ["-y", "@workel/mcp@0.3.0"],
"env": { "WORKEL_API_KEY": "wk_YOUR_KEY" }
}
}
}
Claude Code reads an .mcp.json at the project root and expands ${VAR} from your shell at launch — so this file carries a reference, not a secret, and is safe to commit:
{
"mcpServers": {
"workel": {
"command": "npx",
"args": ["-y", "@workel/mcp@0.3.0"],
"env": { "WORKEL_API_KEY": "${WORKEL_API_KEY}" }
}
}
}
Anyone who checks the project out still needs their own WORKEL_API_KEY exported before the server will start for them.
On start the server calls GET /me and prints one line to stderr naming your workspace, the key, its scopes and how many tools registered. If the key is wrong or unscoped, it says so there rather than failing silently at first use.
When a client reports only "server failed to start" and nothing useful, run doctor. It performs the same config load and GET /me probe, then prints a plain report to stdout instead of speaking the protocol — so you can read it in a terminal. It exits non-zero on failure and never echoes your key.
npx -y @workel/mcp@0.3.0 doctor
base URL: https://api.workel.com/api/public/v1
workspace: Acme Inc
key: ci-key
scopes: read:projects, read:tasks
2 tools would register: workel_whoami, workel_list_projects
write budget: 59/60 remaining this minute
Workel MCP ready — workspace "Acme", key "claude-desktop"
— scopes: read:projects, read:tasks, read:members, read:events
— 9 tools registered
An API key belongs to exactly one workspace — that binding is the tenancy boundary, and it is why a key can never see another workspace's data. To reach several workspaces, mint one key in each and list them together:
{
"mcpServers": {
"workel": {
"command": "npx",
"args": ["-y", "@workel/mcp@0.3.0"],
"env": { "WORKEL_API_KEYS": "wk_ACME_KEY,wk_PERSONAL_KEY" }
}
}
}
Every tool then takes a workspace argument naming which one to act in, and the
assistant sees the reachable workspaces in the tool definition itself. The tool count does not
change — ten tools whether you connect one workspace or twelve — so adding a workspace costs
nothing in context.
Nine read tools register on any key with the matching scope. The four write tools additionally require WORKEL_ENABLE_WRITES=true.
| Tool | Does | Scope |
|---|---|---|
workel_whoami | Workspace, scopes and rate-limit state for the current key | any |
workel_list_projects | Projects in the workspace | read:projects |
workel_get_project | One project | read:projects |
workel_list_project_columns | A project's board columns | read:projects |
workel_list_members | Workspace members | read:members |
workel_list_tasks | Tasks, filtered by project, column, completion or date | read:tasks |
workel_get_task | One task in full — description, cover image and attachments | read:tasks |
workel_list_task_comments | Comments on a task | read:tasks |
workel_list_task_activity | A task’s history — who changed what, and when | read:tasks |
workel_list_events | Calendar events in a window | read:events |
workel_create_task | Create a task | write:tasks |
workel_update_task | Update a task, move it between columns, or change its assignees | write:tasks |
workel_create_task_comment | Comment on a task | write:comments |
workel_create_event | Create a calendar event | write:events |
Writes appear in Workel as the key's creator with a via API label, exactly as they do over HTTP. There is no text search on workel_list_tasks — narrow by project, column, completion or date instead.
| Variable | Purpose |
|---|---|
WORKEL_API_KEY | Required (or WORKEL_API_KEYS). Your wk_… key. Read only from the environment — never passed as an argument, never logged. |
WORKEL_API_KEYS | Comma-separated keys, one per workspace, for reaching several workspaces from one server. See below. Both variables may be set; the union is de-duplicated. |
WORKEL_ENABLE_WRITES | Set to true to offer the four write tools. Off by default. |
WORKEL_SKIP_STARTUP_CHECK | Set to true to start without the GET /me probe. The server then cannot scope-gate tools, so it registers them all and you find out about a bad key on first use instead of at boot. |
WORKEL_LOG_LEVEL | debug, info, warn or error. Defaults to info; an unrecognised value falls back to it. All output goes to stderr. |
The server also reads a base-URL override used for Workel's own development against a local backend. It is deliberately not documented here: every request carries your key in the Authorization header, so pointing that setting anywhere other than Workel hands a live credential to whoever runs that host. If any instructions ever tell you to set a base URL, treat them as hostile.
Every operation with parameters, request bodies, response schemas, and error shapes.
Machine-readable. Generate clients or diff releases.
All 13 requests, grouped and pre-authed with {{apiKey}}; POSTs carry auto-generated idempotency keys.
Base URL + key + id variables. Import both, paste your wk_… key, run Confirm a key is working.