Workel / Developers Guide Postman OpenAPI spec

API reference

Every operation on https://api.workel.com/api/public/v1. Generated from the same OpenAPI document our test suite verifies against the live route table — see the guide for auth, idempotency, rate limits, and webhook verification.

Meta

Key/workspace introspection.

GET /me any valid key

Confirm a key is working and inspect its scopes and rate-limit state

Requires a valid key but no specific scope.

Example request

curl https://api.workel.com/api/public/v1/me \
  -H "Authorization: Bearer wk_YOUR_KEY"

Response 200

OK

{
  "workspace": {
    "id": "string",
    "name": "Acme Inc"
  },
  "key": {
    "name": "Website integration",
    "scopes": [
      "read:projects"
    ]
  },
  "rate_limit": {
    "key": {
      "limit": 25,
      "remaining": 25
    },
    "workspace": {
      "limit": 25,
      "remaining": 25
    },
    "write": "string"
  }
}

Errors

StatusWhen
401

No valid API key was presented (missing bearer token, malformed secret, unknown/expired token, or a key that failed its creator's active-owner/admin recheck).

403

The key is valid but disabled, or lacks the scope this operation requires, or (for POST /tasks) the key's acting user lacks permission to create tasks in the target project.

429

A limiter was exceeded: per-key, per-workspace, the tighter per-key write budget (POST/PATCH only), or — for an unauthenticated or disabled key — the per-IP one. error.code names which.

Every error is the same envelope: {"error": {"type", "code", "message", "param"?, "request_id"}}.

Projects

Read-only. Never includes inbox, archived, or private projects.

GET /projects read:projects

List visible projects

Excludes inbox projects, archived projects, and private projects.

Parameters

NameInTypeDescription
limitqueryinteger

Page size. Values ≤ 0 fall back to the default; values above the maximum are silently clamped, never rejected with a 422. Default 25, maximum 100 — both launch placeholders pending ratification, not a contract value.

cursorquerystring

Opaque pagination cursor, taken verbatim from a previous response's meta.next_cursor. Never construct one by hand.

Example request

curl https://api.workel.com/api/public/v1/projects \
  -H "Authorization: Bearer wk_YOUR_KEY"

Response 200

OK

{
  "data": [
    {
      "id": "string",
      "name": "Website integration",
      "description": "Created from the website order form.",
      "created_at": "2026-08-23T12:00:00Z",
      "updated_at": "2026-08-23T12:00:00Z"
    }
  ],
  "meta": {
    "next_cursor": "eyJpZCI6ImExYjJjM2Q0In0"
  }
}

Errors

StatusWhen
401

No valid API key was presented (missing bearer token, malformed secret, unknown/expired token, or a key that failed its creator's active-owner/admin recheck).

403

The key is valid but disabled, or lacks the scope this operation requires, or (for POST /tasks) the key's acting user lacks permission to create tasks in the target project.

429

A limiter was exceeded: per-key, per-workspace, the tighter per-key write budget (POST/PATCH only), or — for an unauthenticated or disabled key — the per-IP one. error.code names which.

Every error is the same envelope: {"error": {"type", "code", "message", "param"?, "request_id"}}.

GET /projects/{id} read:projects

Fetch a single visible project

Example request

curl https://api.workel.com/api/public/v1/projects/:id \
  -H "Authorization: Bearer wk_YOUR_KEY"

Response 200

OK

{
  "data": {
    "id": "string",
    "name": "Website integration",
    "description": "Created from the website order form.",
    "created_at": "2026-08-23T12:00:00Z",
    "updated_at": "2026-08-23T12:00:00Z"
  }
}

Errors

StatusWhen
401

No valid API key was presented (missing bearer token, malformed secret, unknown/expired token, or a key that failed its creator's active-owner/admin recheck).

403

The key is valid but disabled, or lacks the scope this operation requires, or (for POST /tasks) the key's acting user lacks permission to create tasks in the target project.

404

The id does not resolve to a visible resource. This is intentionally indistinguishable from "exists but belongs to another workspace", "exists but is private/archived/inbox", and "does not exist at all" — the API never confirms the existence of a resource the key cannot see.

429

A limiter was exceeded: per-key, per-workspace, the tighter per-key write budget (POST/PATCH only), or — for an unauthenticated or disabled key — the per-IP one. error.code names which.

Every error is the same envelope: {"error": {"type", "code", "message", "param"?, "request_id"}}.

GET /projects/{id}/cards read:projects

List a project's board columns

A Workel "card" here is a board COLUMN (e.g. "To Do"/"In Progress"), not a task — so integrators can target a column id on task create.

Parameters

NameInTypeDescription
limitqueryinteger

Page size. Values ≤ 0 fall back to the default; values above the maximum are silently clamped, never rejected with a 422. Default 25, maximum 100 — both launch placeholders pending ratification, not a contract value.

cursorquerystring

Opaque pagination cursor, taken verbatim from a previous response's meta.next_cursor. Never construct one by hand.

Example request

curl https://api.workel.com/api/public/v1/projects/:id/cards \
  -H "Authorization: Bearer wk_YOUR_KEY"

Response 200

OK

{
  "data": [
    {
      "id": "string",
      "name": "Website integration",
      "is_done": false,
      "order": 1
    }
  ],
  "meta": {
    "next_cursor": "eyJpZCI6ImExYjJjM2Q0In0"
  }
}

Errors

StatusWhen
401

No valid API key was presented (missing bearer token, malformed secret, unknown/expired token, or a key that failed its creator's active-owner/admin recheck).

403

The key is valid but disabled, or lacks the scope this operation requires, or (for POST /tasks) the key's acting user lacks permission to create tasks in the target project.

404

The id does not resolve to a visible resource. This is intentionally indistinguishable from "exists but belongs to another workspace", "exists but is private/archived/inbox", and "does not exist at all" — the API never confirms the existence of a resource the key cannot see.

429

A limiter was exceeded: per-key, per-workspace, the tighter per-key write budget (POST/PATCH only), or — for an unauthenticated or disabled key — the per-IP one. error.code names which.

Every error is the same envelope: {"error": {"type", "code", "message", "param"?, "request_id"}}.

Members

Active workspace members. The one endpoint on this surface that exposes email addresses.

GET /members read:members

List active workspace members

Only members with an active (not pending/suspended) membership are listed.

Parameters

NameInTypeDescription
limitqueryinteger

Page size. Values ≤ 0 fall back to the default; values above the maximum are silently clamped, never rejected with a 422. Default 25, maximum 100 — both launch placeholders pending ratification, not a contract value.

cursorquerystring

Opaque pagination cursor, taken verbatim from a previous response's meta.next_cursor. Never construct one by hand.

Example request

curl https://api.workel.com/api/public/v1/members \
  -H "Authorization: Bearer wk_YOUR_KEY"

Response 200

OK

{
  "data": [
    {
      "id": "string",
      "name": "Website integration",
      "email": "alex@example.com",
      "role": "owner",
      "joined_at": "2026-08-23T12:00:00Z"
    }
  ],
  "meta": {
    "next_cursor": "eyJpZCI6ImExYjJjM2Q0In0"
  }
}

Errors

StatusWhen
401

No valid API key was presented (missing bearer token, malformed secret, unknown/expired token, or a key that failed its creator's active-owner/admin recheck).

403

The key is valid but disabled, or lacks the scope this operation requires, or (for POST /tasks) the key's acting user lacks permission to create tasks in the target project.

429

A limiter was exceeded: per-key, per-workspace, the tighter per-key write budget (POST/PATCH only), or — for an unauthenticated or disabled key — the per-IP one. error.code names which.

Every error is the same envelope: {"error": {"type", "code", "message", "param"?, "request_id"}}.

Tasks

Read and write. Reachable only through a visible project's board.

GET /tasks read:tasks

List tasks across every visible project

Parameters

NameInTypeDescription
limitqueryinteger

Page size. Values ≤ 0 fall back to the default; values above the maximum are silently clamped, never rejected with a 422. Default 25, maximum 100 — both launch placeholders pending ratification, not a contract value.

cursorquerystring

Opaque pagination cursor, taken verbatim from a previous response's meta.next_cursor. Never construct one by hand.

project_idquerystring

Narrow to one project. A foreign/private/inbox/nonexistent id simply yields zero rows.

card_idquerystring

Narrow to one board column.

completedquerystring true "1" "false" "0"

true for progress===100, false for progress<100 or null (never-progressed). Any other value is silently ignored — the filter is simply not applied, never a 422.

due_beforequerystring (date)

Strict YYYY-MM-DD. A value not shaped like this is silently ignored.

due_afterquerystring (date)

Strict YYYY-MM-DD. A value not shaped like this is silently ignored.

updated_sincequerystring (date-time)

Any parseable date/time string. Unparseable values are silently ignored.

Example request

curl https://api.workel.com/api/public/v1/tasks \
  -H "Authorization: Bearer wk_YOUR_KEY"

Response 200

OK

{
  "data": [
    {
      "id": "string",
      "title": "New order #4821",
      "description": "Created from the website order form.",
      "project_id": "a1b2c3d4",
      "card": {
        "id": "string",
        "name": "Website integration",
        "is_done": false
      },
      "priority": "high",
      "due_date": "2026-08-30",
      "due_time": "14:30",
      "progress": 40,
      "completed": false,
      "assignee_ids": [
        "Alex Rivera"
      ],
      "created_at": "2026-08-23T12:00:00Z",
      "updated_at": "2026-08-23T12:00:00Z"
    }
  ],
  "meta": {
    "next_cursor": "eyJpZCI6ImExYjJjM2Q0In0"
  }
}

Errors

StatusWhen
401

No valid API key was presented (missing bearer token, malformed secret, unknown/expired token, or a key that failed its creator's active-owner/admin recheck).

403

The key is valid but disabled, or lacks the scope this operation requires, or (for POST /tasks) the key's acting user lacks permission to create tasks in the target project.

429

A limiter was exceeded: per-key, per-workspace, the tighter per-key write budget (POST/PATCH only), or — for an unauthenticated or disabled key — the per-IP one. error.code names which.

Every error is the same envelope: {"error": {"type", "code", "message", "param"?, "request_id"}}.

POST /tasks write:tasks

Create a task

Runs the full task-create recipe (tenant re-check, permission check against the target project, default-column resolution, assignee membership re-verification, activity/notification dispatch, realtime broadcast) — not a thinned-down write.

Parameters

NameInTypeDescription
Idempotency-Keyheaderstring

Caller-chosen opaque token. See the "Idempotency" section of this document's description for the full contract. Stored responses expire after 24 hours — a launch placeholder pending ratification, not a contract value.

Request body

FieldTypeDescription
card_idstring · nullable

The board column to create the task in. Exactly one of card_id/project_id should be given; when card_id is omitted, project_id is required and the task lands in that project's first non-done column (422 no_open_column if none exists).

project_idstring · nullable
title_text requiredstring
descriptionstring · nullable
prioritystring · nullable "low" "medium" "high" "urgent" "none" null
progressinteger · nullable
end_datestring · nullable (date)
end_timestring · nullable

"HH:mm". Dropped if end_date is not also given.

user_idsarray<string>

Must all be members of the bound workspace, or the whole request is rejected with 422 invalid_assignee.

Example request

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: YOUR_UNIQUE_KEY" \
  -d '{"project_id":"a1b2c3d4","title_text":"New order #4821"}'

Response 201

Created

{
  "data": {
    "id": "string",
    "title": "New order #4821",
    "description": "Created from the website order form.",
    "project_id": "a1b2c3d4",
    "card": {
      "id": "string",
      "name": "Website integration",
      "is_done": false
    },
    "priority": "high",
    "due_date": "2026-08-30",
    "due_time": "14:30",
    "progress": 40,
    "completed": false,
    "assignee_ids": [
      "Alex Rivera"
    ],
    "created_at": "2026-08-23T12:00:00Z",
    "updated_at": "2026-08-23T12:00:00Z"
  }
}

Errors

StatusWhen
401

No valid API key was presented (missing bearer token, malformed secret, unknown/expired token, or a key that failed its creator's active-owner/admin recheck).

403

Missing write:tasks scope, OR the key's acting user lacks TASK_CREATE permission in the target project.

404

card_id/project_id does not resolve to a visible, writable project — same collapsing rule as every other read on this surface.

409

POST only. The Idempotency-Key header was already used, with a request body that hashes differently from this one.

422

Validation failure, OR the target project is an inbox project (inbox_project), OR it has no open column (no_open_column), OR one or more user_ids are not members of this workspace (invalid_assignee).

429

A limiter was exceeded: per-key, per-workspace, the tighter per-key write budget (POST/PATCH only), or — for an unauthenticated or disabled key — the per-IP one. error.code names which.

Every error is the same envelope: {"error": {"type", "code", "message", "param"?, "request_id"}}.

GET /tasks/{id} read:tasks

Fetch a single task

Example request

curl https://api.workel.com/api/public/v1/tasks/:id \
  -H "Authorization: Bearer wk_YOUR_KEY"

Response 200

OK

{
  "data": {
    "id": "string",
    "title": "New order #4821",
    "description": "Created from the website order form.",
    "project_id": "a1b2c3d4",
    "card": {
      "id": "string",
      "name": "Website integration",
      "is_done": false
    },
    "priority": "high",
    "due_date": "2026-08-30",
    "due_time": "14:30",
    "progress": 40,
    "completed": false,
    "assignee_ids": [
      "Alex Rivera"
    ],
    "created_at": "2026-08-23T12:00:00Z",
    "updated_at": "2026-08-23T12:00:00Z"
  }
}

Errors

StatusWhen
401

No valid API key was presented (missing bearer token, malformed secret, unknown/expired token, or a key that failed its creator's active-owner/admin recheck).

403

The key is valid but disabled, or lacks the scope this operation requires, or (for POST /tasks) the key's acting user lacks permission to create tasks in the target project.

404

The id does not resolve to a visible resource. This is intentionally indistinguishable from "exists but belongs to another workspace", "exists but is private/archived/inbox", and "does not exist at all" — the API never confirms the existence of a resource the key cannot see.

429

A limiter was exceeded: per-key, per-workspace, the tighter per-key write budget (POST/PATCH only), or — for an unauthenticated or disabled key — the per-IP one. error.code names which.

Every error is the same envelope: {"error": {"type", "code", "message", "param"?, "request_id"}}.

PATCH /tasks/{id} write:tasks

Update a task

NOT idempotency-key aware (PublicApiIdempotency only inspects POST requests) — a retried PATCH with the same body is safe only because the writable fields are themselves idempotent, not because of Idempotency-Key support. Every accepted field is applied through the same TaskUpdateService the web UI's task-update path uses, so the reminder re-arm, orphaned-end_time cleanup, and recurring-task roll-forward invariants apply identically here.

Request body

FieldTypeDescription
title_textstring · nullable
descriptionstring · nullable
prioritystring · nullable "low" "medium" "high" "urgent" "none" null
progressinteger · nullable
end_datestring · nullable (date)
end_timestring · nullable
reminder_datestring · nullable (date-time)

Example request

curl -X PATCH https://api.workel.com/api/public/v1/tasks/:id \
  -H "Authorization: Bearer wk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title_text":"New order #4821","description":"Created from the website order form."}'

Response 200

OK

{
  "data": {
    "id": "string",
    "title": "New order #4821",
    "description": "Created from the website order form.",
    "project_id": "a1b2c3d4",
    "card": {
      "id": "string",
      "name": "Website integration",
      "is_done": false
    },
    "priority": "high",
    "due_date": "2026-08-30",
    "due_time": "14:30",
    "progress": 40,
    "completed": false,
    "assignee_ids": [
      "Alex Rivera"
    ],
    "created_at": "2026-08-23T12:00:00Z",
    "updated_at": "2026-08-23T12:00:00Z"
  }
}

Errors

StatusWhen
401

No valid API key was presented (missing bearer token, malformed secret, unknown/expired token, or a key that failed its creator's active-owner/admin recheck).

403

The key is valid but disabled, or lacks the scope this operation requires, or (for POST /tasks) the key's acting user lacks permission to create tasks in the target project.

404

The id does not resolve to a visible resource. This is intentionally indistinguishable from "exists but belongs to another workspace", "exists but is private/archived/inbox", and "does not exist at all" — the API never confirms the existence of a resource the key cannot see.

422

The request body or query string failed validation. error.param names the first field that failed — only the first failing field is reported, even if several are invalid.

429

A limiter was exceeded: per-key, per-workspace, the tighter per-key write budget (POST/PATCH only), or — for an unauthenticated or disabled key — the per-IP one. error.code names which.

Every error is the same envelope: {"error": {"type", "code", "message", "param"?, "request_id"}}.

Task comments

Read and write. Plain text only — no @-mention syntax in v1.

GET /tasks/{id}/comments read:tasks

List a task's comments

Every comment on the task, top-level and replies alike, oldest first. Exists mainly so a comment.created webhook consumer (a thin, ids-only payload) can fetch the comment it was told about.

Parameters

NameInTypeDescription
limitqueryinteger

Page size. Values ≤ 0 fall back to the default; values above the maximum are silently clamped, never rejected with a 422. Default 25, maximum 100 — both launch placeholders pending ratification, not a contract value.

cursorquerystring

Opaque pagination cursor, taken verbatim from a previous response's meta.next_cursor. Never construct one by hand.

Example request

curl https://api.workel.com/api/public/v1/tasks/:id/comments \
  -H "Authorization: Bearer wk_YOUR_KEY"

Response 200

OK

{
  "data": [
    {
      "id": "string",
      "body": "Created from the website order form.",
      "author": {
        "id": "string",
        "name": "Alex Rivera"
      },
      "created_at": "2026-08-23T12:00:00Z"
    }
  ],
  "meta": {
    "next_cursor": "eyJpZCI6ImExYjJjM2Q0In0"
  }
}

Errors

StatusWhen
401

No valid API key was presented (missing bearer token, malformed secret, unknown/expired token, or a key that failed its creator's active-owner/admin recheck).

403

The key is valid but disabled, or lacks the scope this operation requires, or (for POST /tasks) the key's acting user lacks permission to create tasks in the target project.

404

The id does not resolve to a visible resource. This is intentionally indistinguishable from "exists but belongs to another workspace", "exists but is private/archived/inbox", and "does not exist at all" — the API never confirms the existence of a resource the key cannot see.

429

A limiter was exceeded: per-key, per-workspace, the tighter per-key write budget (POST/PATCH only), or — for an unauthenticated or disabled key — the per-IP one. error.code names which.

Every error is the same envelope: {"error": {"type", "code", "message", "param"?, "request_id"}}.

POST /tasks/{id}/comments write:comments

Comment on a task

Parameters

NameInTypeDescription
Idempotency-Keyheaderstring

Caller-chosen opaque token. See the "Idempotency" section of this document's description for the full contract. Stored responses expire after 24 hours — a launch placeholder pending ratification, not a contract value.

Request body

FieldTypeDescription
body requiredstring
mention_user_idsany

Must not be sent at all — including as an empty array or null — or the request is rejected with 422. No @-mention syntax is parsed in v1.

Example request

curl -X POST https://api.workel.com/api/public/v1/tasks/:id/comments \
  -H "Authorization: Bearer wk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: YOUR_UNIQUE_KEY" \
  -d '{"body":"Created from the website order form."}'

Response 201

Created

{
  "data": {
    "id": "string",
    "body": "Created from the website order form.",
    "author": {
      "id": "string",
      "name": "Alex Rivera"
    },
    "created_at": "2026-08-23T12:00:00Z"
  }
}

Errors

StatusWhen
401

No valid API key was presented (missing bearer token, malformed secret, unknown/expired token, or a key that failed its creator's active-owner/admin recheck).

403

The key is valid but disabled, or lacks the scope this operation requires, or (for POST /tasks) the key's acting user lacks permission to create tasks in the target project.

404

The id does not resolve to a visible resource. This is intentionally indistinguishable from "exists but belongs to another workspace", "exists but is private/archived/inbox", and "does not exist at all" — the API never confirms the existence of a resource the key cannot see.

409

POST only. The Idempotency-Key header was already used, with a request body that hashes differently from this one.

422

Validation failure — including sending mention_user_ids at all (@-mentions are not parsed in v1; the field is prohibited, not silently ignored).

429

A limiter was exceeded: per-key, per-workspace, the tighter per-key write budget (POST/PATCH only), or — for an unauthenticated or disabled key — the per-IP one. error.code names which.

Every error is the same envelope: {"error": {"type", "code", "message", "param"?, "request_id"}}.

Events

Read and write. Workspace-level and project-level calendar events.

GET /events read:events

List workspace and project events

Includes events bound directly to the workspace and events on any visible project.

Parameters

NameInTypeDescription
limitqueryinteger

Page size. Values ≤ 0 fall back to the default; values above the maximum are silently clamped, never rejected with a 422. Default 25, maximum 100 — both launch placeholders pending ratification, not a contract value.

cursorquerystring

Opaque pagination cursor, taken verbatim from a previous response's meta.next_cursor. Never construct one by hand.

fromquerystring (date)

Inclusive lower bound. Any parseable date. Unparseable → 422 invalid_date.

toquerystring (date)

Inclusive upper bound. Any parseable date. Unparseable → 422 invalid_date. Combined with from, the window may not exceed 400 days — a wider window is a hard 422 window_too_large, never silently clamped.

Example request

curl https://api.workel.com/api/public/v1/events \
  -H "Authorization: Bearer wk_YOUR_KEY"

Response 200

OK

{
  "data": [
    {
      "id": "string",
      "title": "New order #4821",
      "description": "Created from the website order form.",
      "date": "2026-08-30",
      "start_time": "14:30",
      "end_time": "14:30",
      "location": "string",
      "project_id": "a1b2c3d4",
      "workspace_id": "a1b2c3d4",
      "color": "blue",
      "repeat": "none",
      "created_at": "2026-08-23T12:00:00Z",
      "updated_at": "2026-08-23T12:00:00Z"
    }
  ],
  "meta": {
    "next_cursor": "eyJpZCI6ImExYjJjM2Q0In0"
  }
}

Errors

StatusWhen
401

No valid API key was presented (missing bearer token, malformed secret, unknown/expired token, or a key that failed its creator's active-owner/admin recheck).

403

The key is valid but disabled, or lacks the scope this operation requires, or (for POST /tasks) the key's acting user lacks permission to create tasks in the target project.

422

The request body or query string failed validation. error.param names the first field that failed — only the first failing field is reported, even if several are invalid.

429

A limiter was exceeded: per-key, per-workspace, the tighter per-key write budget (POST/PATCH only), or — for an unauthenticated or disabled key — the per-IP one. error.code names which.

Every error is the same envelope: {"error": {"type", "code", "message", "param"?, "request_id"}}.

POST /events write:events

Create an event

Unconditionally logged to the workspace/project activity feed on success, even with zero invitees — an API-originated write is exactly the case this audit trail exists for.

Parameters

NameInTypeDescription
Idempotency-Keyheaderstring

Caller-chosen opaque token. See the "Idempotency" section of this document's description for the full contract. Stored responses expire after 24 hours — a launch placeholder pending ratification, not a contract value.

Request body

FieldTypeDescription
title requiredstring
descriptionstring · nullable
date requiredstring (date)
start_time requiredstring

"HH:mm".

end_time requiredstring

"HH:mm", strictly after start_time.

reminder_atstring · nullable (date-time)
timezonestring · nullable

A valid IANA timezone name.

reminder_minutes_beforeinteger · nullable
repeat requiredstring "none" "daily" "weekly" "monthly" "yearly"
repeat_intervalinteger · nullable

Required unless repeat is none.

locationstring · nullable
meet_linkstring · nullable (uri)
colorstring · nullable
orderinteger · nullable
project_idstring · nullable

When given, must resolve to a project visible in the bound workspace (not inbox, not archived, not private) or the request is rejected with 422 — a foreign, private, inbox, or nonexistent id are all indistinguishable. Omitted → the event is workspace-level.

invited_usersarray<string>

User ids to invite. Existence is checked; an id outside the bound workspace's membership is silently dropped rather than rejected (unlike a task's user_ids).

Example request

curl -X POST https://api.workel.com/api/public/v1/events \
  -H "Authorization: Bearer wk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: YOUR_UNIQUE_KEY" \
  -d '{"title":"New order #4821","date":"2026-08-30","start_time":"14:30","end_time":"14:30","repeat":"none","project_id":"a1b2c3d4"}'

Response 201

Created

{
  "data": {
    "id": "string",
    "title": "New order #4821",
    "description": "Created from the website order form.",
    "date": "2026-08-30",
    "start_time": "14:30",
    "end_time": "14:30",
    "location": "string",
    "project_id": "a1b2c3d4",
    "workspace_id": "a1b2c3d4",
    "color": "blue",
    "repeat": "none",
    "created_at": "2026-08-23T12:00:00Z",
    "updated_at": "2026-08-23T12:00:00Z"
  }
}

Errors

StatusWhen
401

No valid API key was presented (missing bearer token, malformed secret, unknown/expired token, or a key that failed its creator's active-owner/admin recheck).

403

The key is valid but disabled, or lacks the scope this operation requires, or (for POST /tasks) the key's acting user lacks permission to create tasks in the target project.

409

POST only. The Idempotency-Key header was already used, with a request body that hashes differently from this one.

422

Validation failure, including an unresolvable project_id (foreign, private, inbox, and nonexistent all collapse to this — no existence oracle).

429

A limiter was exceeded: per-key, per-workspace, the tighter per-key write budget (POST/PATCH only), or — for an unauthenticated or disabled key — the per-IP one. error.code names which.

Every error is the same envelope: {"error": {"type", "code", "message", "param"?, "request_id"}}.

Webhooks

Deliveries are HTTPS POSTs signed over the exact raw body — see the guide for the Workel-Signature verification scheme, retry/backoff behavior, and endpoint management. Dedupe on Workel-Event-Id.

EVENTtask.created

Fired from the Task model's created Eloquent hook — every origin (this API, the web UI, an import), not only writes made through this API. Suppressed for the duration of a bulk CSV import.

Payload

{
  "id": "a1b2c3d4",
  "type": "task.created",
  "created_at": "2026-08-23T12:00:00Z",
  "data": {}
}
EVENTtask.updated

Fired from the shared task-update service (the same seam PATCH /tasks/{id} and the web UI's task editor both go through) whenever a field actually changes. A plain column-move drag in the web UI (no field edit) does NOT emit this — a known v1 limitation.

Payload

{
  "id": "a1b2c3d4",
  "type": "task.created",
  "created_at": "2026-08-23T12:00:00Z",
  "data": {}
}
EVENTtask.completed

Fired from the same seam as task.updated, specifically on the progress <100 → ≥100 transition.

Payload

{
  "id": "a1b2c3d4",
  "type": "task.created",
  "created_at": "2026-08-23T12:00:00Z",
  "data": {}
}
EVENTtask.deleted

Ids-only payload (id, card_id, project_id) — deliberately not built from TaskResource, since a deleted task's title/description are exactly what a mirror-keeping consumer does not need.

Payload

{
  "id": "a1b2c3d4",
  "type": "task.created",
  "created_at": "2026-08-23T12:00:00Z",
  "data": {}
}
EVENTcomment.created

body is stripped from the payload (free-text fields are never sent over webhooks) — fetch the full comment with GET /tasks/{id}/comments using your own scoped key.

Payload

{
  "id": "a1b2c3d4",
  "type": "task.created",
  "created_at": "2026-08-23T12:00:00Z",
  "data": {}
}
EVENTevent.created

Payload

{
  "id": "a1b2c3d4",
  "type": "task.created",
  "created_at": "2026-08-23T12:00:00Z",
  "data": {}
}
EVENTproject.created

Payload

{
  "id": "a1b2c3d4",
  "type": "task.created",
  "created_at": "2026-08-23T12:00:00Z",
  "data": {}
}