Public API · v1

Build on Workel

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.

base URL https://api.workel.com/api/public/v1 auth Bearer wk_… format JSON
01

Quickstart

  1. Create an API key

    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.

  2. Confirm the key works
    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.

  3. Create your first task
    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.

02

Authentication & scopes

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:

ScopeGrants
read:projectsList/read projects and their board columns
read:tasksList/read tasks and their comments
read:eventsList calendar events
read:membersList active workspace members (incl. email, for identity mapping)
write:tasksCreate and update tasks
write:eventsCreate calendar events
write:commentsComment on tasks
Keep secrets server-side. Never ship a 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.
03

Endpoints

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).

PathScopeDescription
GET/me—Key + workspace + rate-limit state
GET/projectsread:projectsList visible projects with task counts
GET/projects/{id}read:projectsOne project
GET/projects/{id}/cardsread:projectsBoard columns — target one on task create
GET/membersread:membersActive members: id, name, role, email
GET/tasksread:tasksTasks across visible projects; filters: project_id, card_id, completed, due_before/after, updated_since
POST/taskswrite:tasksCreate a task (column optional — defaults to the first open one)
GET/tasks/{id}read:tasksOne task, incl. description and assignees
PATCH/tasks/{id}write:tasksUpdate title, description, priority, due date/time, progress
GET/tasks/{id}/commentsread:tasksA task's comments
POST/tasks/{id}/commentswrite:commentsComment on a task
GET/eventsread:eventsWorkspace + project calendar events in a date window
POST/eventswrite:eventsCreate a calendar event, with optional invitees

Conventions

Errors

Always {"error": {type, code, message, param?, request_id}}. Every response carries an X-Request-Id header — quote it in support requests.

Pagination

Cursor-based: {"data": […], "meta": {"next_cursor"}}. No offset pages — polling under concurrent writes stays consistent.

Idempotency

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.

Rate limits

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.

Tenancy

A key sees exactly one workspace. Resources outside it — or in private/personal spaces — return 404, never 403.

Attribution

Writes appear in Workel as the key's creator, labeled via API, so teammates always know a machine acted.

04

Webhooks

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.

Verify every delivery

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.

05

Model Context Protocol

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.

Connect from Claude

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?”

Your key is the boundary

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:

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.

Set it up

  1. Mint a dedicated read-only key

    Settings → Developers → Create key, granting only read:projects, read:tasks, read:members and read:events. Do not reuse a key another integration already holds.

  2. Point your client at it

    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.

  3. Confirm it connected

    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

Working across several workspaces

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.

This multiplexes addressing, not authority. Each request still goes out on exactly one workspace-bound key, and the API's tenancy check is unchanged. Scopes are per key, so a tool your Acme key can use may be refused in another workspace — the error names the workspace and the missing scope. Revoking one key removes one workspace and leaves the rest working.

Tools

Nine read tools register on any key with the matching scope. The four write tools additionally require WORKEL_ENABLE_WRITES=true.

ToolDoesScope
workel_whoamiWorkspace, scopes and rate-limit state for the current keyany
workel_list_projectsProjects in the workspaceread:projects
workel_get_projectOne projectread:projects
workel_list_project_columnsA project's board columnsread:projects
workel_list_membersWorkspace membersread:members
workel_list_tasksTasks, filtered by project, column, completion or dateread:tasks
workel_get_taskOne task in full — description, cover image and attachmentsread:tasks
workel_list_task_commentsComments on a taskread:tasks
workel_list_task_activityA task’s history — who changed what, and whenread:tasks
workel_list_eventsCalendar events in a windowread:events
workel_create_taskCreate a taskwrite:tasks
workel_update_taskUpdate a task, move it between columns, or change its assigneeswrite:tasks
workel_create_task_commentComment on a taskwrite:comments
workel_create_eventCreate a calendar eventwrite: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.

Environment

VariablePurpose
WORKEL_API_KEYRequired (or WORKEL_API_KEYS). Your wk_… key. Read only from the environment — never passed as an argument, never logged.
WORKEL_API_KEYSComma-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_WRITESSet to true to offer the four write tools. Off by default.
WORKEL_SKIP_STARTUP_CHECKSet 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_LEVELdebug, 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.

06

Go deeper

Full API reference →

Every operation with parameters, request bodies, response schemas, and error shapes.

OpenAPI 3.1 spec ↓

Machine-readable. Generate clients or diff releases.

Postman collection ↓

All 13 requests, grouped and pre-authed with {{apiKey}}; POSTs carry auto-generated idempotency keys.

Postman environment ↓

Base URL + key + id variables. Import both, paste your wk_… key, run Confirm a key is working.