/meToken introspection
| Status | Meaning | Body |
|---|---|---|
| 200 | Who am I | |
| 401 | Unauthorized | Error |
| 429 | Rate limited | Error |
Developers
A REST API over your workspace: projects, lists, tasks, comments and members, plus signed event deliveries for your own endpoints and for automation tools. JSON in, JSON out, one token per workspace.
Three calls and you have read your first task.
curl https://www.traqos.com/api/v1/me \
-H "Authorization: Bearer traqos_pat_…"{
"user": { "id": "…", "email": "you@example.com" },
"tenant": { "id": "…", "name": "Your workspace" },
"authKind": "pat",
"scopes": ["projects:read", "lists:read", "tasks:read"],
"plan": "pro"
}GET /projects → GET /projects/{projectId}/lists → GET /lists/{listId}/tasks?expand=display. Every object carries the ids you need for the next call.A personal access token (PAT) acts as you, limited to the scopes you picked.
Authorization: Bearer traqos_pat_… on every request. Tokens are tied to one workspace and one user.404, never 403.402.Pick the smallest set that does the job. Read scopes can never change anything.
| Scope | Class | Grants |
|---|---|---|
projects:read | read | Read projects |
projects:write | write | Create projects |
lists:read | read | Read task lists |
lists:write | write | Create task lists |
| Scope | Class | Grants |
|---|---|---|
tasks:read | read | Read tasks |
tasks:write | write | Create, edit and delete tasks |
| Scope | Class | Grants |
|---|---|---|
comments:read | read | Read comments |
comments:write | write | Add comments |
| Scope | Class | Grants |
|---|---|---|
custom-fields:read | read | Read custom fields |
custom-fields:write | write | Edit custom field values |
| Scope | Class | Grants |
|---|---|---|
docs:read | read | Read docs |
| Scope | Class | Grants |
|---|---|---|
members:read | read | Read workspace members |
| Scope | Class | Grants |
|---|---|---|
time:read | read | Read time entries |
time:write | write | Create and edit time entries |
| Scope | Class | Grants |
|---|---|---|
webhooks:manage | write | Manage webhook subscriptions |
hooks:manage | read | Receive instant events for lists you can see (Zapier and similar tools) |
Every error is JSON. Two envelopes exist: the ordinary one and the plan envelope.
{ "error": { "code": "not_found", "message": "Task not found", "details": { } } }| Status | code | When |
|---|---|---|
| 400 | validation_failed | A field is missing, unknown or malformed. The message names it. |
| 401 | unauthorized | No token, an unknown token, or a revoked or expired one. |
| 403 | insufficient_scope | The token lacks the scope; details.scope names it. |
| 403 | forbidden | Your role does not allow this (for example managing webhooks needs the developer-admin permission). |
| 403 | workspace_read_only | Billing put the workspace in read-only mode. Reads still work. |
| 403 | workspace_suspended | The workspace is suspended. Nothing works until it is restored. |
| 404 | not_found | Does not exist, or you cannot see it. The two are indistinguishable on purpose. |
| 429 | rate_limited | Slow down. Retry-After says how many seconds. |
| 500 | internal | Our fault. Retry later; tell us if it persists. |
Plan limits answer 402 with a different, flat envelope. key is the limit that was hit and requiredPlan the smallest plan that lifts it.
{
"error": "Instant triggers are not included in your plan.",
"code": "entitlement_denied",
"key": "apiHooksMax",
"current": 0,
"max": 0,
"requiredPlan": "pro"
}Lists are cursor-paged and every token has a per-minute budget set by the workspace plan.
{ "data": [...], "nextCursor": "…" | null }. Pass cursor back to get the next page; limit is 1 to 100, default 50. Order is by creation unless the endpoint offers order=desc.X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (unix seconds). Over budget you get 429 with Retry-After.| Plan | API access | Requests per minute | Workspace webhooks | Instant triggers (member hooks) |
|---|---|---|---|---|
| Free | read only | 30 | 0 | 0 |
| Basic | read only | 60 | 0 | 0 |
| Pro | read and write | 100 | 10 | 25 |
| Pro+ | read and write | 200 | 25 | 100 |
| Enterprise | read and write | 1000 | 100 | 500 |
/me| Status | Meaning | Body |
|---|---|---|
| 200 | Who am I | |
| 401 | Unauthorized | Error |
| 429 | Rate limited | Error |
/projectsRequires scope projects:read.
| Name | In | Type | Notes |
|---|---|---|---|
| cursor | query | string | |
| limit | query | integer | Default 50. |
| Status | Meaning | Body |
|---|---|---|
| 200 | Page of projects | { data: Project[], nextCursor } |
| 401 | Unauthorized | Error |
| 403 | Missing scope | Error |
| 429 | Rate limited | Error |
/projectsRequires scope projects:write. Defaults to the General board when boardId is omitted.
| Field | Type | Notes |
|---|---|---|
| name* | string | |
| description | string | |
| boardId | string | |
| privacy | "public" | "private" |
| Status | Meaning | Body |
|---|---|---|
| 201 | Created project | { data: Project } |
| 400 | Validation failed | Error |
| 401 | Unauthorized | Error |
| 402 | Plan limit (locked envelope) | EntitlementError |
| 403 | Missing scope / insufficient permissions / workspace read-only | Error |
| 429 | Rate limited | Error |
/projects/{projectId}Requires scope projects:read. 404 when missing OR not visible to you.
| Name | In | Type | Notes |
|---|---|---|---|
| projectId* | path | string |
| Status | Meaning | Body |
|---|---|---|
| 200 | Project | { data: Project } |
| 401 | Unauthorized | Error |
| 403 | Missing scope | Error |
| 404 | Not found | Error |
| 429 | Rate limited | Error |
/projects/{projectId}/listsRequires scope lists:read. Private lists you can't view are omitted; folder fences apply.
| Name | In | Type | Notes |
|---|---|---|---|
| projectId* | path | string |
| Status | Meaning | Body |
|---|---|---|
| 200 | Task lists | { data: TaskList[], nextCursor } |
| 401 | Unauthorized | Error |
| 403 | Missing scope | Error |
| 404 | Not found | Error |
| 429 | Rate limited | Error |
/projects/{projectId}/listsRequires scope lists:write. v1 creates public lists (privacy/grants are in-app flows).
| Name | In | Type | Notes |
|---|---|---|---|
| projectId* | path | string |
| Field | Type | Notes |
|---|---|---|
| name* | string | |
| description | string | |
| color | string | |
| icon | string |
| Status | Meaning | Body |
|---|---|---|
| 201 | Created list | { data: TaskList } |
| 400 | Validation failed | Error |
| 401 | Unauthorized | Error |
| 402 | Plan limit (locked envelope) | EntitlementError |
| 403 | Missing scope / insufficient permissions / workspace read-only | Error |
| 404 | Not found | Error |
| 429 | Rate limited | Error |
/lists/{listId}/custom-fieldsRequires scope custom-fields:read. fieldId is the key Task.customFieldValues uses. Private fields you can't view are omitted.
| Name | In | Type | Notes |
|---|---|---|---|
| listId* | path | string |
| Status | Meaning | Body |
|---|---|---|
| 200 | Custom fields | { data: CustomField[], nextCursor } |
| 401 | Unauthorized | Error |
| 403 | Missing scope / insufficient permissions | Error |
| 404 | Not found | Error |
| 429 | Rate limited | Error |
/lists/{listId}/tasksRequires scope tasks:read.
| Name | In | Type | Notes |
|---|---|---|---|
| listId* | path | string | |
| status | query | string | |
| assigneeId | query | string | |
| updatedSince | query | string | |
| order | query | "asc" | "desc" | By creation time. desc = newest first. |
| expand | query | "display" | display = add Task.display (link, status name, assignee names, list, project, plain-text description) |
| cursor | query | string | |
| limit | query | integer | Default 50. |
| Status | Meaning | Body |
|---|---|---|
| 200 | Page of tasks | { data: Task[], nextCursor } |
| 400 | Bad filter | Error |
| 401 | Unauthorized | Error |
| 403 | Missing scope | Error |
| 404 | Not found | Error |
| 429 | Rate limited | Error |
/lists/{listId}/tasksRequires scope tasks:write. Fires the same realtime/automation events as in-app creation.
| Name | In | Type | Notes |
|---|---|---|---|
| listId* | path | string | |
| expand | query | "display" | display = add Task.display (link, status name, assignee names, list, project, plain-text description) |
A TaskInput object with name required.
| Status | Meaning | Body |
|---|---|---|
| 201 | Created task | { data: Task } |
| 400 | Validation failed | Error |
| 401 | Unauthorized | Error |
| 402 | Plan limit (locked envelope) | EntitlementError |
| 403 | Missing scope / insufficient permissions / workspace read-only | Error |
| 404 | Not found | Error |
| 429 | Rate limited | Error |
/tasks/{taskId}taskId is the canonical id (Task.id). Requires scope tasks:read.
| Name | In | Type | Notes |
|---|---|---|---|
| taskId* | path | string | |
| expand | query | "display" | display = add Task.display (link, status name, assignee names, list, project, plain-text description) |
| Status | Meaning | Body |
|---|---|---|
| 200 | Task | { data: Task } |
| 401 | Unauthorized | Error |
| 403 | Missing scope | Error |
| 404 | Not found | Error |
| 429 | Rate limited | Error |
/tasks/{taskId}Requires scope tasks:write. Formula fields recompute; realtime/automation events fire.
| Name | In | Type | Notes |
|---|---|---|---|
| taskId* | path | string | |
| expand | query | "display" | display = add Task.display (link, status name, assignee names, list, project, plain-text description) |
A TaskInput object.
| Status | Meaning | Body |
|---|---|---|
| 200 | Updated task | { data: Task } |
| 400 | Validation failed | Error |
| 401 | Unauthorized | Error |
| 402 | Plan limit (locked envelope) | EntitlementError |
| 403 | Missing scope / insufficient permissions / workspace read-only | Error |
| 404 | Not found | Error |
| 429 | Rate limited | Error |
/tasks/{taskId}Requires scope tasks:write. Full cascade (time entries, comments, files) — same as in-app deletion.
| Name | In | Type | Notes |
|---|---|---|---|
| taskId* | path | string |
| Status | Meaning | Body |
|---|---|---|
| 200 | Deleted | |
| 401 | Unauthorized | Error |
| 403 | Missing scope / insufficient permissions / workspace read-only | Error |
| 404 | Not found | Error |
| 429 | Rate limited | Error |
/tasks/{taskId}/commentsRequires scope comments:read. Newest first. Same thread the app UI shows.
| Name | In | Type | Notes |
|---|---|---|---|
| taskId* | path | string | |
| limit | query | integer | Default 50. |
| Status | Meaning | Body |
|---|---|---|
| 200 | Comments | { data: Comment[], nextCursor } |
| 401 | Unauthorized | Error |
| 403 | Missing scope / insufficient permissions | Error |
| 404 | Not found | Error |
| 429 | Rate limited | Error |
/tasks/{taskId}/commentsRequires scope comments:write. Mentioned member ids are validated and notified (same machinery as in-app comments).
| Name | In | Type | Notes |
|---|---|---|---|
| taskId* | path | string |
| Field | Type | Notes |
|---|---|---|
| content* | string | |
| mentions | array of string | |
| parentId | string |
| Status | Meaning | Body |
|---|---|---|
| 201 | Created comment | { data: Comment } |
| 400 | Validation failed | Error |
| 401 | Unauthorized | Error |
| 403 | Missing scope / insufficient permissions / workspace read-only | Error |
| 404 | Not found | Error |
| 429 | Rate limited | Error |
/membersRequires scope members:read AND the member-view role permission. Use ids for assigneeIds and mentions. Guests are never listed.
| Status | Meaning | Body |
|---|---|---|
| 200 | Members | { data: Member[], nextCursor } |
| 401 | Unauthorized | Error |
| 403 | Missing scope / insufficient permissions | Error |
| 429 | Rate limited | Error |
/searchRequires scope tasks:read. Name search across tasks, projects, lists; docs included only when the token carries docs:read. Same access fences as the in-app search.
| Name | In | Type | Notes |
|---|---|---|---|
| q* | query | string |
| Status | Meaning | Body |
|---|---|---|
| 200 | Grouped results | |
| 401 | Unauthorized | Error |
| 403 | Missing scope | Error |
| 429 | Rate limited | Error |
/webhooksRequires scope webhooks:manage and the developer-admin permission.
| Status | Meaning | Body |
|---|---|---|
| 200 | Webhooks | { data: Webhook[], nextCursor } |
| 401 | Unauthorized | Error |
| 403 | Missing scope / not a developer-admin | Error |
| 429 | Rate limited | Error |
/webhooksRequires scope webhooks:manage and the developer-admin permission. The signing secret is returned once, in this response only. Counts toward the plan's webhooksMax.
A WebhookInput object.
| Status | Meaning | Body |
|---|---|---|
| 201 | Created webhook, with its secret (shown only here) | { data: Webhook + secret } |
| 400 | Validation failed (name, URL, events) | Error |
| 401 | Unauthorized | Error |
| 402 | Plan limit (locked envelope) | EntitlementError |
| 403 | Missing scope / not a developer-admin / workspace read-only | Error |
| 429 | Rate limited | Error |
/webhooks/{webhookId}Requires scope webhooks:manage and the developer-admin permission.
| Name | In | Type | Notes |
|---|---|---|---|
| webhookId* | path | string |
| Status | Meaning | Body |
|---|---|---|
| 200 | Webhook | { data: Webhook } |
| 401 | Unauthorized | Error |
| 403 | Missing scope / not a developer-admin | Error |
| 404 | Not found | Error |
| 429 | Rate limited | Error |
/webhooks/{webhookId}Requires scope webhooks:manage and the developer-admin permission. Send only the fields to change; status accepts active or paused (active also clears auto_disabled).
| Name | In | Type | Notes |
|---|---|---|---|
| webhookId* | path | string |
A WebhookPatch object.
| Status | Meaning | Body |
|---|---|---|
| 200 | Updated webhook | { data: Webhook } |
| 400 | Validation failed | Error |
| 401 | Unauthorized | Error |
| 403 | Missing scope / not a developer-admin / workspace read-only | Error |
| 404 | Not found | Error |
| 429 | Rate limited | Error |
/webhooks/{webhookId}Requires scope webhooks:manage and the developer-admin permission. Its delivery log goes with it.
| Name | In | Type | Notes |
|---|---|---|---|
| webhookId* | path | string |
| Status | Meaning | Body |
|---|---|---|
| 200 | Deleted | { data: { id, deleted } } |
| 401 | Unauthorized | Error |
| 403 | Missing scope / not a developer-admin | Error |
| 404 | Not found | Error |
| 429 | Rate limited | Error |
/webhooks/{webhookId}/deliveriesThe last 50 deliveries, newest first: status, attempts, your response code, the payload that was sent.
| Name | In | Type | Notes |
|---|---|---|---|
| webhookId* | path | string |
| Status | Meaning | Body |
|---|---|---|
| 200 | Deliveries | { data: WebhookDelivery[], nextCursor } |
| 401 | Unauthorized | Error |
| 403 | Missing scope / not a developer-admin | Error |
| 404 | Not found | Error |
| 429 | Rate limited | Error |
/webhooks/{webhookId}/testPosts a webhook.test event to the endpoint right now and returns the outcome.
| Name | In | Type | Notes |
|---|---|---|---|
| webhookId* | path | string |
| Status | Meaning | Body |
|---|---|---|
| 200 | Outcome of the attempt | { data: { status, responseStatus, durationMs, error } } |
| 401 | Unauthorized | Error |
| 403 | Missing scope / not a developer-admin | Error |
| 404 | Not found | Error |
| 429 | Rate limited | Error |
/hooksRequires scope hooks:manage plus the read scope of each event family (tasks:read, comments:read). The list must be readable by you now (otherwise 404), and every delivery is checked again when it is sent. Repeating the same request (same URL, list and events) answers 200 with the existing hook instead of creating another. Counts toward the plan's apiHooksMax.
A HookInput object.
| Status | Meaning | Body |
|---|---|---|
| 200 | The same hook already existed; returned again with its secret | { data: Hook + secret } |
| 201 | Created hook, with its secret (shown only here) | { data: Hook + secret } |
| 400 | Validation failed (events, URL) | Error |
| 401 | Unauthorized | Error |
| 402 | Plan limit (locked envelope) | EntitlementError |
| 403 | Missing scope / events not allowed for your role | Error |
| 404 | List not found or not visible to you | Error |
| 429 | Rate limited | Error |
/hooks/{hookId}Only your own hooks; anyone else's answer 404. Never blocked by the plan.
| Name | In | Type | Notes |
|---|---|---|---|
| hookId* | path | string |
| Status | Meaning | Body |
|---|---|---|
| 200 | Deleted | { data: { id, deleted } } |
| 401 | Unauthorized | Error |
| 403 | Missing scope | Error |
| 404 | Not found | Error |
| 429 | Rate limited | Error |
/openapino token needed| Status | Meaning | Body |
|---|---|---|
| 200 | OpenAPI 3.1 spec |
TraqOS can call your endpoint when something happens. There are two kinds of subscription.
| Workspace webhook | Member hook (instant trigger) | |
|---|---|---|
| Who creates it | A developer-admin, in Settings → Developer or with POST /webhooks | Any member with a token carrying hooks:manage, with POST /hooks (this is what Zapier-style tools use) |
| What it covers | Every event in the workspace, or one project | The events of one task list |
| What it carries | The full event | Only what the token's owner can read at send time: private custom-field values are removed, and the event is dropped if the owner lost access |
| Plan allowance | webhooksMax | apiHooksMax |
| When it stops | Paused by an admin, or auto-disabled after 20 failed deliveries in a row | Token revoked or expired, owner leaves, your endpoint answers 410, or 20 failed deliveries in a row |
| Task lifecycle | Meaning | Member hooks |
|---|---|---|
task.created | Task created | yes |
task.updated | Task updated | yes |
task.deleted | Task deleted | yes |
| Task field changes | Meaning | Member hooks |
|---|---|---|
task.status_changed | Status changed | yes |
task.assignee_added | Assignee added | yes |
task.assignee_removed | Assignee removed | yes |
task.due_date_changed | Due date changed | yes |
task.start_date_changed | Start date changed | yes |
task.custom_field_changed | Custom field changed | no |
| Work intake | Meaning | Member hooks |
|---|---|---|
task.form_submitted | Form response received | yes |
task.email_received | Email received | yes |
| Comments | Meaning | Member hooks |
|---|---|---|
comment.created | Comment added | yes |
One HTTP POST per event, JSON body, within seconds of the event. Task events carry data.task (a Task) and, when something changed, data.changes with from and to per field; comment events carry data.comment (a Comment). Member-hook deliveries always include the task's display block, name both statuses of a status change (changes.status.fromName, toName) and point a comment at its task (comment.display.taskRef). The test button sends webhook.test.
{
"id": "0f2e6a9e-4a1c-4a40-9a0b-5a0f7a1c2d3e",
"type": "task.status_changed",
"createdAt": "2026-10-06T09:30:00.000Z",
"tenantId": "…", "projectId": "…", "listId": "…",
"actor": { "userId": "…" },
"data": {
"task": { "id": "…", "taskId": 42, "name": "Write the launch announcement", "status": "status_done", "display": { "status": "Done", "url": "https://www.traqos.com/…", "…": "…" } },
"changes": { "status": { "from": "status_in_progress", "to": "status_done", "fromName": "In progress", "toName": "Done" } }
}
}| Delivery | Summary | Headers | Your answer |
|---|---|---|---|
taskEvent | Sent for every task.* event the subscription carries. | X-TraqOS-Event, X-TraqOS-Delivery, X-TraqOS-Signature |
|
commentEvent | Sent for comment.created. | X-TraqOS-Event, X-TraqOS-Delivery, X-TraqOS-Signature |
|
testEvent | Sent by the test button or POST /webhooks/{webhookId}/test; data is a small sample. | X-TraqOS-Event, X-TraqOS-Delivery, X-TraqOS-Signature |
|
2xx within 10 seconds. Anything else is retried after 1, 5, 30, 120 and 360 minutes (5 attempts in all); 429 is retried after your Retry-After and never counts as a failure.task.created, task.updated, task.deleted, task.status_changed, task.assignee_added, task.assignee_removed, task.due_date_changed, task.start_date_changed, task.form_submitted, task.email_received, comment.created.Every delivery is signed with the subscription's secret, which you received once when you created it.
The header X-TraqOS-Signature: t=<unix seconds>,v1=<hex> carries a timestamp and an HMAC-SHA256 of the string <t>.<raw body> with the secret. Compute it over the exact bytes you received, compare in constant time, and reject timestamps older than 5 minutes.
import crypto from "node:crypto";
export function verifyTraqOS(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(",").map((kv) => kv.split("=")));
const expected = crypto.createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
const equal = expected.length === parts.v1.length &&
crypto.timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(parts.v1, "hex"));
return fresh && equal;
}Two more headers help with logging and deduplication: X-TraqOS-Event (the event type) and X-TraqOS-Delivery (a unique id per delivery; a retry keeps the same id). The user agent is TraqOS-Webhooks/1.0.
The objects the endpoints exchange. A star marks a required field on input.
| Field | Type | Notes |
|---|---|---|
| id | string | Canonical task id |
| taskId | integer | Per-list numeric id (display) |
| name | string | |
| description | string | Tiptap JSON serialized as a string |
| descriptionPinned | boolean | |
| status | string | null | |
| type | string | |
| color | string | null | |
| startDate | string | null (date-time) | |
| endDate | string | null (date-time) | |
| duration | number | null | |
| progress | number | |
| timeEstimate | number | null | |
| timeTracked | number | |
| assigneeIds | array of string | |
| dependencies | array of string | Predecessor strings, e.g. "12FS" |
| parentId | string | null | |
| sectionId | string | null | |
| listId | string | |
| projectId | string | |
| boardId | string | null | |
| customFieldValues | object | |
| checklists | array of object | |
| isRecurring | boolean | |
| createdAt | string | null (date-time) | |
| updatedAt | string | null (date-time) | |
| display | object | Human-readable companions. Present only with ?expand=display, and on member-owned hook deliveries. |
| ↳ url | string | Link to the task in the app |
| ↳ status | string | null | The status NAME (Task.status holds its value) |
| ↳ assignees | array of object | |
| ↳ id | string | |
| ↳ name | string | null | |
| ↳ list | string | null | List name |
| ↳ project | string | null | Project name |
| ↳ description | string | The description as plain text |
Public write fields. Unknown keys are rejected.
| Field | Type | Notes |
|---|---|---|
| name | string | |
| description | string | |
| descriptionText | string | null | The description as plain text, one paragraph per line. Send this OR description, not both. "" clears it. |
| descriptionPinned | boolean | |
| startDate | string | null | |
| endDate | string | null | |
| duration | number | null | |
| progress | number | |
| status | string | null | |
| type | string | |
| color | string | null | |
| sectionId | string | null | |
| assigneeIds | array of string | |
| dependencies | array of string | |
| timeEstimate | number | null | |
| customFieldValues | object | |
| isRecurring | boolean | |
| recurrence | object | |
| checklists | array of object |
| Field | Type | Notes |
|---|---|---|
| id | string | |
| name | string | |
| description | string | |
| boardId | string | null | |
| color | string | null | |
| icon | string | null | |
| privacy | "public" | "private" | |
| isDefault | boolean | |
| createdAt | string | null (date-time) | |
| updatedAt | string | null (date-time) |
| Field | Type | Notes |
|---|---|---|
| id | string | |
| name | string | |
| color | string | null | |
| icon | string | null | |
| isDefault | boolean | |
| folderId | string | null | |
| locked | boolean | true = metadata stub (no access to contents) |
| description | string | |
| statuses | array of object | Status options for this list. `value` is what Task.status stores. |
| ↳ value | string | |
| ↳ label | string | |
| ↳ color | string | null | |
| ↳ category | string | null | not_started | active | closed |
| ↳ order | integer | |
| taskTypes | array of object | Task types for this list. `id` is what Task.type stores. |
| ↳ id | string | |
| ↳ name | string | |
| ↳ icon | string | null | |
| ↳ color | string | null | |
| ↳ isEnabled | boolean | |
| ↳ order | integer | |
| defaultTaskType | string | null | |
| canEdit | boolean | |
| createdAt | string | null (date-time) | |
| updatedAt | string | null (date-time) |
| Field | Type | Notes |
|---|---|---|
| id | string | |
| name | string | null | |
| string | null | ||
| role | string |
| Field | Type | Notes |
|---|---|---|
| id | string | |
| taskId | integer | null | Numeric TaskID (thread key) |
| parentId | string | null | |
| threadDepth | integer | |
| content | string | Sanitized HTML |
| plainText | string | |
| author | object | null | |
| ↳ id | string | |
| ↳ name | string | null | |
| mentions | array of object | |
| ↳ id | string | |
| ↳ name | string | null | |
| replyCount | integer | |
| createdAt | string | null (date-time) | |
| updatedAt | string | null (date-time) |
| Field | Type | Notes |
|---|---|---|
| fieldId | string | The key used inside Task.customFieldValues |
| name | string | |
| type | string | |
| description | string | null | |
| options | array of object | |
| order | integer | |
| createdAt | string | null (date-time) | |
| updatedAt | string | null (date-time) |
A workspace webhook: every event of the workspace, or of one project. Managed by developer-admins.
| Field | Type | Notes |
|---|---|---|
| id | string | |
| name | string | |
| url | string (uri) | |
| events | array of "task.created" | "task.updated" | "task.deleted" | "task.status_changed" | "task.assignee_added" | "task.assignee_removed" | "task.due_date_changed" | "task.start_date_changed" | "task.custom_field_changed" | "task.form_submitted" | "task.email_received" | "comment.created" | |
| status | "active" | "paused" | "auto_disabled" | auto_disabled = 20 failed deliveries in a row. Fix the endpoint, then PATCH status to active. |
| projectId | string | null | Set = only this project's events |
| consecutiveFailures | integer | |
| lastDeliveryAt | string | null (date-time) | |
| lastDeliveryStatus | string | null | |
| createdAt | string | null (date-time) | |
| updatedAt | string | null (date-time) |
| Field | Type | Notes |
|---|---|---|
| name* | string | |
| url* | string (uri) | https URL on a public host. Private, loopback and link-local addresses are refused. |
| events* | array of "task.created" | "task.updated" | "task.deleted" | "task.status_changed" | "task.assignee_added" | "task.assignee_removed" | "task.due_date_changed" | "task.start_date_changed" | "task.custom_field_changed" | "task.form_submitted" | "task.email_received" | "comment.created" | |
| projectId | string | null | Limit the webhook to one project |
Send only the fields to change.
| Field | Type | Notes |
|---|---|---|
| name | string | |
| url | string (uri) | |
| events | array of "task.created" | "task.updated" | "task.deleted" | "task.status_changed" | "task.assignee_added" | "task.assignee_removed" | "task.due_date_changed" | "task.start_date_changed" | "task.custom_field_changed" | "task.form_submitted" | "task.email_received" | "comment.created" | |
| projectId | string | null | |
| status | "active" | "paused" | paused = keep the subscription, send nothing. active = resume, also after auto_disabled. |
| Field | Type | Notes |
|---|---|---|
| id | string | |
| eventType | string | |
| status | "pending" | "processing" | "succeeded" | "failed" | "dead" | |
| attempts | integer | |
| responseStatus | integer | null | |
| responseBodySnippet | string | null | First 1024 characters of your answer |
| durationMs | integer | null | |
| lastError | string | null | |
| nextAttemptAt | string | null (date-time) | |
| createdAt | string | null (date-time) | |
| payload | object | The EventEnvelope that was sent |
A member hook: the events of ONE task list, owned by the token's user, carrying only what that user can read.
| Field | Type | Notes |
|---|---|---|
| id | string | |
| name | string | |
| url | string (uri) | |
| events | array of "task.created" | "task.updated" | "task.deleted" | "task.status_changed" | "task.assignee_added" | "task.assignee_removed" | "task.due_date_changed" | "task.start_date_changed" | "task.form_submitted" | "task.email_received" | "comment.created" | |
| listId | string | |
| projectId | string | null | |
| status | "active" | "paused" | "auto_disabled" | |
| createdAt | string | null (date-time) |
| Field | Type | Notes |
|---|---|---|
| url* | string (uri) | Where to POST deliveries. https, public host. |
| events* | array of "task.created" | "task.updated" | "task.deleted" | "task.status_changed" | "task.assignee_added" | "task.assignee_removed" | "task.due_date_changed" | "task.start_date_changed" | "task.form_submitted" | "task.email_received" | "comment.created" | |
| listId* | string | The task list to watch. Must be readable by the token's owner, otherwise 404. |
| name | string |
The body of every delivery. `data` holds `task` (+ `changes` with from/to per field) for task events and `comment` for comment events.
| Field | Type | Notes |
|---|---|---|
| id* | string | Unique per event; a retry keeps the same id |
| type* | "task.created" | "task.updated" | "task.deleted" | "task.status_changed" | "task.assignee_added" | "task.assignee_removed" | "task.due_date_changed" | "task.start_date_changed" | "task.custom_field_changed" | "task.form_submitted" | "task.email_received" | "comment.created" | "webhook.test" | |
| createdAt* | string (date-time) | |
| tenantId* | string | |
| projectId | string | null | |
| listId | string | null | |
| actor | object | |
| ↳ userId | string | null | Who caused the event |
| data* | object | task.* → { task: Task, changes?: { <field>: { from, to } } }. comment.created → { comment: Comment }. Member-hook deliveries always include task.display; a status change adds changes.status.fromName and toName; a comment adds comment.display.taskRef { id, taskId }. |
| Field | Type | Notes |
|---|---|---|
| error | object | |
| ↳ code* | string | |
| ↳ message* | string | |
| ↳ details | object |
| Field | Type | Notes |
|---|---|---|
| error | string | Human-readable reason |
| code | "entitlement_denied" | |
| key | string | |
| current | integer | null | |
| max | integer | null | |
| requiredPlan | string |