TasksBoard API reference
The TasksBoard REST API lets Team plan accounts read and write their Google Tasks (lists, tasks, boards) and receive webhooks when tasks change. Every list and task id is the Google Tasks id, so anything you build on the API stays compatible with Google Tasks itself.
Authentication. Create a personal API key on the Team page of the app
(/team?tab=api) and send it as a bearer token: Authorization: Bearer tb_live_….
A key acts as the user who created it. Keys carry scopes (read, write); a key
without write gets 403 forbidden on every write.
Errors are JSON: { "error": { "code": "…", "message": "…", "details": [...] } }.
code is stable, message is for humans. details lists field errors on 422.
Rate limit: 60 requests per minute per key. Responses carry X-RateLimit-Limit and
X-RateLimit-Remaining; a 429 carries Retry-After (seconds).
Pagination: list endpoints return meta.nextCursor when there is more; pass it back
as cursor with the same filters.
Task extras. Google Tasks only stores a title, notes and a due date. TasksBoard keeps
labels and the assignee inside the notes (labels on the first line, then
👤 Assigned to <email>), and the API reads and writes them as the labels and
assignee fields, so the free text you see in notes is what the user sees on the board.
Base URL: https://api.tasksboard.com/v1
Quick start
Section titled “Quick start”- In TasksBoard, open Team → API (
/team?tab=api) and create a key. Copy it: it is shown once. - Call the API with the key as a bearer token:
curl https://api.tasksboard.com/v1/lists -H "Authorization: Bearer tb_live_…"- Create a task on a list:
curl -X POST https://api.tasksboard.com/v1/lists/LIST_ID/tasks \ -H "Authorization: Bearer tb_live_…" -H "Content-Type: application/json" \ -d '{"title": "Send the Q3 report", "due": "2026-10-24", "labels": ["❗️Important"]}'Prefer no code? The TasksBoard app for Zapier uses the same keys.
Account
Section titled “Account”Who the key belongs to
Section titled “Who the key belongs to”GET /me
Responses
200The account and the key’s scopes.
{ "data": { "id": "u_123", "email": "ana@example.com", "displayName": "Ana", "plan": "team", "key": { "id": "k_1", "scopes": [ "read", "write" ] } }}401Missing, malformed, unknown or revoked API key.403team_plan_required(the key’s owner has no Team plan),forbidden(the key lacks thewritescope) orgoogle_access_expired(the user must sign in to TasksBoard again).
List task lists
Section titled “List task lists”GET /lists
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
cursor | query | string | Opaque cursor from a previous response’s meta.nextCursor. |
limit | query | integer, default 50 |
Responses
200The user’s Google Tasks lists. →data: array of List401Missing, malformed, unknown or revoked API key.
Create a task list
Section titled “Create a task list”POST /lists
Request body
| Field | Type | Description |
|---|---|---|
title (required) | string |
{ "title": "Q4 launch"}Responses
201Created.Locationpoints at the new list. →data: List403team_plan_required(the key’s owner has no Team plan),forbidden(the key lacks thewritescope) orgoogle_access_expired(the user must sign in to TasksBoard again).422The request body or query is invalid;detailsnames the fields.
Get a task list
Section titled “Get a task list”GET /lists/{listId}
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
listId (required) | path | string | Google Tasks list id. |
Responses
200The list. →data: List404No such list, task or webhook.
List tasks in a list
Section titled “List tasks in a list”GET /lists/{listId}/tasks
Open tasks by default. updatedAfter is the polling idiom: ask for tasks changed since
your last call (Zapier’s triggers do exactly this). Completed tasks that the user cleared
from the board are still returned with status=completed or all.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
listId (required) | path | string | Google Tasks list id. |
status | query | open | completed | all, default open | |
updatedAfter | query | string (date-time) | RFC 3339 timestamp; only tasks modified after it. |
dueAfter | query | string (date-time) | |
dueBefore | query | string (date-time) | |
includeDeleted | query | boolean, default false | |
cursor | query | string | Opaque cursor from a previous response’s meta.nextCursor. |
limit | query | integer, default 50 |
Responses
200Tasks, newest first as Google orders them. →data: array of Task404No such list, task or webhook.422The request body or query is invalid;detailsnames the fields.
Create a task
Section titled “Create a task”POST /lists/{listId}/tasks
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
listId (required) | path | string | Google Tasks list id. |
Request body
| Field | Type | Description |
|---|---|---|
title (required) | string | |
notes | string | |
due | string (date-time) | RFC 3339 date or date-time; the time is discarded. |
labels | array of string | A label name without whitespace |
assignee | string (email) | Must be a collaborator of the list (or the user). |
parent | string | Make it a subtask of this task. |
previous | string | Insert after this sibling. |
{ "title": "Send the Q3 report", "notes": "Numbers from finance first", "due": "2026-10-24", "labels": [ "❗️Important" ], "assignee": "bob@example.com"}Responses
201Created.Locationpoints at the task (with itslistId). →data: Task403team_plan_required(the key’s owner has no Team plan),forbidden(the key lacks thewritescope) orgoogle_access_expired(the user must sign in to TasksBoard again).404No such list, task or webhook.422The request body or query is invalid;detailsnames the fields.
Get a task
Section titled “Get a task”GET /tasks/{taskId}
Google Tasks addresses a task by list and id, so listId is required.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
taskId (required) | path | string | Google Tasks task id. |
listId (required) | query | string | The Google Tasks list the task belongs to. |
Responses
200The task. →data: Task404No such list, task or webhook.
Update a task
Section titled “Update a task”PATCH /tasks/{taskId}
Partial update: only the fields you send change. Send null to clear notes, due,
labels or assignee. status: "open" reopens a completed task.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
taskId (required) | path | string | Google Tasks task id. |
Request body
| Field | Type | Description |
|---|---|---|
listId (required) | string | |
title | string | |
notes | string | null | |
due | string | null (date-time) | |
labels | array | null | |
assignee | string | null (email) | |
status | open | completed |
{ "listId": "MTIzNDU2Nzg5", "due": "2026-10-31", "labels": [ "🔥Hot" ]}Responses
200The updated task. →data: Task403team_plan_required(the key’s owner has no Team plan),forbidden(the key lacks thewritescope) orgoogle_access_expired(the user must sign in to TasksBoard again).404No such list, task or webhook.422The request body or query is invalid;detailsnames the fields.
Delete a task
Section titled “Delete a task”DELETE /tasks/{taskId}
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
taskId (required) | path | string | Google Tasks task id. |
listId (required) | query | string | The Google Tasks list the task belongs to. |
Responses
204Deleted.403team_plan_required(the key’s owner has no Team plan),forbidden(the key lacks thewritescope) orgoogle_access_expired(the user must sign in to TasksBoard again).404No such list, task or webhook.
Complete a task
Section titled “Complete a task”POST /tasks/{taskId}/complete
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
taskId (required) | path | string | Google Tasks task id. |
listId (required) | query | string | The Google Tasks list the task belongs to. |
Responses
200The completed task. →data: Task403team_plan_required(the key’s owner has no Team plan),forbidden(the key lacks thewritescope) orgoogle_access_expired(the user must sign in to TasksBoard again).404No such list, task or webhook.
Boards
Section titled “Boards”List boards
Section titled “List boards”GET /boards
TasksBoard boards and the list ids each one shows. The main board is always first.
Responses
200Boards. →data: array of Board
Webhooks
Section titled “Webhooks”List webhooks
Section titled “List webhooks”GET /webhooks
Responses
200Webhooks (secrets are never returned here). →data: array of Webhook
Create a webhook
Section titled “Create a webhook”POST /webhooks
At most 10 webhooks per account, https URLs only. The signing secret is returned
once, in this response. Verify deliveries with the X-TasksBoard-Signature header
(t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>" with the secret>).
Where events come from: the API’s own writes, changes propagated on shared lists, and, when the user turns on “poll my lists every 15 minutes” on the Team page, any change made in Google’s own apps on lists that are not shared. Without polling, those changes are not observed.
Request body
| Field | Type | Description |
|---|---|---|
url (required) | string (uri) | |
events (required) | array of WebhookEvent | |
secret | string | Your own signing secret; generated when omitted. |
{ "url": "https://hooks.example.com/tasksboard", "events": [ "task.created", "task.completed" ]}Responses
201Created, with the secret.409A limit was reached.422The request body or query is invalid;detailsnames the fields.
Get a webhook
Section titled “Get a webhook”GET /webhooks/{webhookId}
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
webhookId (required) | path | string |
Responses
200The webhook. →data: Webhook404No such list, task or webhook.
Update a webhook
Section titled “Update a webhook”PATCH /webhooks/{webhookId}
Setting active to true re-enables a webhook switched off after repeated failures.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
webhookId (required) | path | string |
Request body
| Field | Type | Description |
|---|---|---|
url | string (uri) | |
events | array of WebhookEvent | |
active | boolean |
Responses
200The updated webhook. →data: Webhook404No such list, task or webhook.422The request body or query is invalid;detailsnames the fields.
Delete a webhook
Section titled “Delete a webhook”DELETE /webhooks/{webhookId}
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
webhookId (required) | path | string |
Responses
204Deleted.404No such list, task or webhook.
Webhook deliveries
Section titled “Webhook deliveries”Sent to each webhook subscribed to the event. Answer with any 2xx within 10 seconds.
Failed deliveries are retried up to 3 times with backoff; after 20 consecutive
failures the webhook is switched off (active: false) until you re-enable it.
Headers
| Parameter | In | Type | Description |
|---|---|---|---|
X-TasksBoard-Signature (required) | header | string | |
X-TasksBoard-Event | header | task.created | task.updated | task.completed | task.deleted | |
X-TasksBoard-Delivery | header | string |
Body
| Field | Type | Description |
|---|---|---|
id | string | Delivery id |
event | WebhookEvent | |
createdAt | string (date-time) | |
source | api | sync | poll | api: a REST call; sync: shared-list propagation; poll: the 15-minute poll. |
attempt | integer | |
data | object |
{ "id": "2b1f0c2e-…", "event": "task.completed", "createdAt": "2026-10-15T09:12:33.000Z", "source": "sync", "attempt": 1, "data": { "task": { "id": "aBc123", "listId": "MTIzNDU2Nzg5", "title": "Send the Q3 report", "status": "completed", "completed": "2026-10-15T09:12:30.000Z", "labels": [], "assignee": null, "notes": "", "due": null, "updated": "2026-10-15T09:12:33.000Z", "parent": null, "position": null, "deleted": false, "webViewLink": null } }}Verifying a signature (Node.js):
const crypto = require("crypto")
function verify(secret, rawBody, header, toleranceSeconds = 300) { const parts = Object.fromEntries(header.split(",").map((p) => p.split("="))) if (Math.abs(Date.now() / 1000 - Number(parts.t)) > toleranceSeconds) return false const expected = crypto.createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex") return crypto.timingSafeEqual(Buffer.from(parts.v1, "hex"), Buffer.from(expected, "hex"))}Compute the HMAC over the raw request body exactly as received (before any JSON parsing).
Schemas
Section titled “Schemas”One of: read, write
| Field | Type | Description |
|---|---|---|
error (required) | object |
PageMeta
Section titled “PageMeta”| Field | Type | Description |
|---|---|---|
nextCursor | string | null | |
hasNext | boolean |
| Field | Type | Description |
|---|---|---|
id | string | |
title | string | |
updated | string (date-time) | |
shared | boolean | Shared through TasksBoard with collaborators. |
boardId | string | The TasksBoard board the list is on (default = main board). |
| Field | Type | Description |
|---|---|---|
id | string | |
listId | string | |
title | string | |
notes | string | Free text |
status | open | completed | |
due | string | null (date-time) | Google keeps the date only; the time is always midnight UTC. |
completed | string | null (date-time) | |
updated | string (date-time) | |
parent | string | null | Parent task id for subtasks. |
position | string | null | |
labels | array of string | |
assignee | string | null | Email of the collaborator the task is assigned to. |
deleted | boolean | |
webViewLink | string | null |
{ "id": "aBc123", "listId": "MTIzNDU2Nzg5", "title": "Send the Q3 report", "notes": "Numbers from finance first", "status": "open", "due": "2026-10-24T00:00:00.000Z", "completed": null, "updated": "2026-10-15T09:12:33.000Z", "parent": null, "position": "00000000000000000001", "labels": [ "❗️Important" ], "assignee": "bob@example.com", "deleted": false, "webViewLink": "https://tasks.google.com/task/aBc123"}TaskCreate
Section titled “TaskCreate”| Field | Type | Description |
|---|---|---|
title (required) | string | |
notes | string | |
due | string (date-time) | RFC 3339 date or date-time; the time is discarded. |
labels | array of string | A label name without whitespace |
assignee | string (email) | Must be a collaborator of the list (or the user). |
parent | string | Make it a subtask of this task. |
previous | string | Insert after this sibling. |
TaskUpdate
Section titled “TaskUpdate”| Field | Type | Description |
|---|---|---|
listId (required) | string | |
title | string | |
notes | string | null | |
due | string | null (date-time) | |
labels | array | null | |
assignee | string | null (email) | |
status | open | completed |
| Field | Type | Description |
|---|---|---|
id | string | |
title | string | |
position | integer | |
listIds | array of string |
WebhookEvent
Section titled “WebhookEvent”One of: task.created, task.updated, task.completed, task.deleted
Webhook
Section titled “Webhook”| Field | Type | Description |
|---|---|---|
id | string | |
url | string (uri) | |
events | array of WebhookEvent | |
active | boolean | |
createdAt | string (date-time) | |
updatedAt | string (date-time) | |
lastDeliveryAt | string | null (date-time) | |
lastStatus | integer | null | HTTP status of the last delivery. |
failures | integer | Consecutive failed deliveries. |
WebhookPayload
Section titled “WebhookPayload”| Field | Type | Description |
|---|---|---|
id | string | Delivery id |
event | WebhookEvent | |
createdAt | string (date-time) | |
source | api | sync | poll | api: a REST call; sync: shared-list propagation; poll: the 15-minute poll. |
attempt | integer | |
data | object |
{ "id": "2b1f0c2e-…", "event": "task.completed", "createdAt": "2026-10-15T09:12:33.000Z", "source": "sync", "attempt": 1, "data": { "task": { "id": "aBc123", "listId": "MTIzNDU2Nzg5", "title": "Send the Q3 report", "status": "completed", "completed": "2026-10-15T09:12:30.000Z", "labels": [], "assignee": null, "notes": "", "due": null, "updated": "2026-10-15T09:12:33.000Z", "parent": null, "position": null, "deleted": false, "webViewLink": null } }}Zapier
Section titled “Zapier”The TasksBoard app for Zapier authenticates with the same personal API key and offers:
- Triggers: New Task, Task Completed (polling on
updatedAfter). - Actions: Create Task, Complete Task, Create List.
Search for TasksBoard in Zapier, paste your key when asked, and pick a list. The app’s source lives in packages/zapier of the TasksBoard repository.
Limits and notes
Section titled “Limits and notes”- The API is part of the Team plan; a key whose owner leaves the plan answers
403 team_plan_required. - 60 requests per minute per key; Google Tasks’ own quotas apply on top.
- A key acts as its owner: whatever that person can do in Google Tasks, the key can do. Revoke a key from the Team page the moment it may have leaked.
- Due dates are dates: Google Tasks drops the time of day.
- Changes made in Google’s own apps on lists that are not shared are only observed when “poll my lists every 15 minutes” is on (Team → API).