Taskaid API
The REST API at taskaid.ai/api/v1, the sign-in, the rules every request follows, the errors, and every endpoint.
The Taskaid API reads and changes the same tasks, lists and daily plans you see in the app, over HTTPS and JSON.
- Base URL:
https://taskaid.ai/api/v1 - Description:
/openapi.json, in OpenAPI 3.1, also listed in the API catalog. Import it into an API client, such as Postman, Bruno or others, to get every request ready to send.
The endpoints at the end of this page are generated from that description, so they say exactly what the server accepts.
Signing in
Every request carries a key in the Authorization header:
Authorization: Bearer <key>There are two kinds of key. The API treats them the same way.
API keys, for your own scripts
Create one in Settings, under API keys. Give it a name you'll recognise, and choose its access:
- Read only: view tasks, lists and daily plans.
- Read and write: also create, change and delete them.
The key is shown once, so copy it then; only a hash of it is kept. It works until you revoke it in the same place, which takes effect immediately. Keys start with taid_api_, so a leaked one is easy to recognise. Treat one like a password and keep it out of code you share.
OAuth, for apps other people use
An app that acts for other people signs each of them in through Taskaid's OAuth 2.1 authorization server, the same one assistants connect through: the authorization code flow with PKCE, the person approving access in their browser, and a refresh token to renew it without asking again. A client identifies itself with a client ID metadata document, or registers on first use. Everything a client needs to start is in the authorization server metadata.
Scopes
Two scopes cover the API:
read: view tasks, lists and daily plans.write: create and update them, and delete tasks and lists.
A read-only API key holds read; a read-and-write key holds both. Each endpoint names the scope it needs. A request without a valid key gets 401. A key without the scope gets 403, with a WWW-Authenticate header naming the scope.
Requests
- Bodies are JSON, sent as
Content-Type: application/json. Anything else gets415. - Unknown fields and query parameters are refused with
400, so a misspelt name fails instead of being quietly ignored. - A task is named by its reference, such as
TASKAID-45or45. A list is named by itsid. - Dates are calendar days,
YYYY-MM-DD, in your own time zone. Until you've opened the web app once, that's UTC. Task filters also take a day counted from today, as an ISO 8601 duration:P0Dis today,P1Dtomorrow,-P1Dyesterday. - A reminder is a time of day,
HH:MMin 24-hour time, on the task'swhenday in your time zone. Setting one needs awhen, a time zone, and a time that hasn't passed; otherwise it gets400.
Responses
- Creating a task or a list answers
201 Created, with the new one's address inLocation. - Saving a day's plan with
PUTanswers201when the plan is new and200when it replaces one.PUTwrites the whole plan, so a title left out is cleared. - Deleting answers
204, with no body. - Tasks come a page at a time. When there are more, the response has
nextCursor. Send it back ascursor, with the same filters, for the next page. NonextCursormeans the last page.
Errors
Every error is a problem details object (RFC 9457), sent as application/problem+json:
{
"type": "https://taskaid.ai/problems/task-not-found",
"title": "Task not found",
"status": 404,
"detail": "Task TASKAID-999 not found"
}Branch on type. title is fixed for each type and detail is written for people, so don't parse either. Handle the types below, fall back to the status for any other, and ignore members you don't recognise. An error the status already explains, such as a malformed request, a missing scope or an unknown endpoint, has "type": "about:blank".
task-not-found
404. The task doesn't exist, or isn't yours.
list-not-found
404. The list doesn't exist, or isn't yours. A task write that names such a list gets it too.
plan-not-found
404. The day has no plan. Saving one with PUT creates it.
note-too-long
400. The note is longer than one write may send. The limit is on the note field below.
note-changed
412. The note changed after you read it, so nothing was written. The current note is in the note member. See Changing a note safely.
Changing a note safely
A note can change from more than one place: the app, an assistant, your own script. To update a task without overwriting a change you haven't seen, send If-Match with the note as you last read it:
- Hash the note with SHA-256, as lowercase hex. Treat a null note as an empty string before hashing.
- Send it in quotes,
If-Match: "<hash>", with yourPATCH.
If the note has changed since, nothing is written, and the 412 carries the current note to merge and try again. Without If-Match, the write goes through as sent.
Versioning
The version is in the path. Within v1, changes only add: new endpoints, optional parameters, response fields and error types. Ignore fields you don't recognise. A change that would break a client comes as v2, served alongside v1 for a stated period, announced in the changelog and marked with Deprecation and Sunset headers on v1.
Tasks
List tasks
- Method:
GET - Path:
/api/v1/tasks - Operation ID:
listTasks - Tags: Tasks
Effective servers
/
Authentication
- oauth2: OAuth 2.0: authorization code, authorize
/oauth/authorize, token/oauth/token, refresh/oauth/token, scopes:read
Or:
-
apiKey: HTTP bearer, scopes:
readAn API key from Settings.
Query parameters
-
completed:booleanNarrow to finished (true) or open (false) tasks. Omit for both.
-
listId:stringNarrow to a single list. Omit to span every list.
-
limit:integer, default:50, minimum:1, maximum:100Max results. Defaults to 50, up to 100.
-
cursor:stringContinue from a previous response's nextCursor, with the same filters. Omit for the first page.
-
query:stringNarrow to tasks matching this text. Every whitespace-separated word must appear in the task's name or note (case-insensitive, any order), so extra words narrow the results rather than widening them — pass distinctive keywords, not a full sentence.
-
when[gte]:stringNarrow by scheduled day. Each bound is a day, "2026-09-19", or an ISO 8601 duration from today in the user's timezone: "P0D" is today, "P1D" tomorrow, "-P1D" yesterday, "P2W" two weeks out. Inbox tasks have no day and never match. Inclusive lower bound. Omit it to include every earlier day.
-
when[lte]:stringNarrow by scheduled day. Each bound is a day, "2026-09-19", or an ISO 8601 duration from today in the user's timezone: "P0D" is today, "P1D" tomorrow, "-P1D" yesterday, "P2W" two weeks out. Inbox tasks have no day and never match. Inclusive upper bound. With
completed: false, "P0D" alone reads today and everything overdue.
Responses
200 A page of tasks.
Content type: application/json
-
tasks(required):array of Task -
nextCursor:stringPass as cursor, with the same filters, to read the next page. Absent on the last page.
400, 401, 403
400The request is malformed or breaks a rule.401No valid credential.403The credential lacks the scope.
Content type: application/problem+json
schema: Problem
Problem details (RFC 9457).
-
detail(required):stringWhat went wrong this time, for people. Don't parse it.
-
status(required):integer -
title(required):stringFixed for each type. Don't branch on it.
-
type(required):stringNames the problem: a type under https://taskaid.ai/problems/, or about:blank when the status says it all. Handle the types you recognise and fall back to the status for any other; ignore members you don't recognise.
Create a task
- Method:
POST - Path:
/api/v1/tasks - Operation ID:
createTask - Tags: Tasks
Effective servers
/
Authentication
- oauth2: OAuth 2.0: authorization code, authorize
/oauth/authorize, token/oauth/token, refresh/oauth/token, scopes:write
Or:
-
apiKey: HTTP bearer, scopes:
writeAn API key from Settings.
Request body
Required: true
Content type: application/json
no additional properties
-
name(required):string, minLength:1, maxLength:500Required when creating. On update, omit to keep the current name. A few words naming the action; the day goes in
when, the detail innote. -
completed:booleanMarks the task done. True stamps
completedAtwith the time of the call, never the time the work finished. -
duration:integer | null, minimum:1, maximum:1440Estimated duration in minutes, up to a day. Null clears the estimate.
-
listId:string | nullPass null to unlink the task from its list.
-
note:string | nullTask notes, as Markdown, up to 15,000 characters — rendered as rich text in the app. Headings, lists, bold, and inline code are supported. Send literal newlines and characters, not escape sequences.
-
reminder:string | null, format:timeA time of day for a reminder, "HH:MM" in 24-hour time (e.g. "15:00"), on the task's
whenday in the user's time zone. Moving the task moves it; null removes it. At that time Taskaid sends thetask.remindedevent to subscribed assistants. -
starred:boolean -
when:string | nullThe day the task is scheduled for, as "YYYY-MM-DD" (e.g. "2026-05-21"). Omit or pass null when creating to leave the task in Inbox; pass null when updating to move it back there.
Responses
201 The created task.
Headers:
-
Location(required)Where the new resource can be read.
stringWhere the new resource can be read.
Content type: application/json
schema: Task
-
completed(required):boolean -
completedAt(required):string | null -
duration(required):number | null -
id(required):string -
list(required):object | nullemoji(required):stringid(required):stringname(required):string
-
name(required):string -
note(required):string | null -
reminder(required):string | nullTime of day on the
whenday, "HH:MM", or null for none. -
source(required):string | null -
starred(required):boolean -
updatedAt(required):string -
url(required):string -
when(required):string | nullCalendar date, "YYYY-MM-DD", or null for Inbox.
400, 401, 403, 404, 415
400The request is malformed or breaks a rule.401No valid credential.403The credential lacks the scope.404The list it names does not exist.415The body is not JSON.
Content type: application/problem+json
schema: Problem
Problem details (RFC 9457).
-
detail(required):stringWhat went wrong this time, for people. Don't parse it.
-
status(required):integer -
title(required):stringFixed for each type. Don't branch on it.
-
type(required):stringNames the problem: a type under https://taskaid.ai/problems/, or about:blank when the status says it all. Handle the types you recognise and fall back to the status for any other; ignore members you don't recognise.
Get a task
- Method:
GET - Path:
/api/v1/tasks/{id} - Operation ID:
getTask - Tags: Tasks
Effective servers
/
Authentication
- oauth2: OAuth 2.0: authorization code, authorize
/oauth/authorize, token/oauth/token, refresh/oauth/token, scopes:read
Or:
-
apiKey: HTTP bearer, scopes:
readAn API key from Settings.
Path parameters
-
id(required):stringTask reference, such as TASKAID-45 or 45.
Responses
200 The task.
Content type: application/json
schema: Task
-
completed(required):boolean -
completedAt(required):string | null -
duration(required):number | null -
id(required):string -
list(required):object | nullemoji(required):stringid(required):stringname(required):string
-
name(required):string -
note(required):string | null -
reminder(required):string | nullTime of day on the
whenday, "HH:MM", or null for none. -
source(required):string | null -
starred(required):boolean -
updatedAt(required):string -
url(required):string -
when(required):string | nullCalendar date, "YYYY-MM-DD", or null for Inbox.
400, 401, 403, 404
400The request is malformed or breaks a rule.401No valid credential.403The credential lacks the scope.404The task does not exist.
Content type: application/problem+json
schema: Problem
Problem details (RFC 9457).
-
detail(required):stringWhat went wrong this time, for people. Don't parse it.
-
status(required):integer -
title(required):stringFixed for each type. Don't branch on it.
-
type(required):stringNames the problem: a type under https://taskaid.ai/problems/, or about:blank when the status says it all. Handle the types you recognise and fall back to the status for any other; ignore members you don't recognise.
Update a task
- Method:
PATCH - Path:
/api/v1/tasks/{id} - Operation ID:
updateTask - Tags: Tasks
Effective servers
/
Authentication
- oauth2: OAuth 2.0: authorization code, authorize
/oauth/authorize, token/oauth/token, refresh/oauth/token, scopes:write
Or:
-
apiKey: HTTP bearer, scopes:
writeAn API key from Settings.
Path parameters
-
id(required):stringTask reference, such as TASKAID-45 or 45.
Header parameters
-
if-match:stringThe note as you last read it, as an entity tag: the SHA-256 of the note in hex, quoted ("…"). The write fails with 412 if the note has changed since.
Request body
Required: true
Content type: application/json
no additional properties
-
completed:booleanMarks the task done. True stamps
completedAtwith the time of the call, never the time the work finished. -
duration:integer | null, minimum:1, maximum:1440Estimated duration in minutes, up to a day. Null clears the estimate.
-
listId:string | nullPass null to unlink the task from its list.
-
name:string, minLength:1, maxLength:500Required when creating. On update, omit to keep the current name. A few words naming the action; the day goes in
when, the detail innote. -
note:string | nullTask notes, as Markdown, up to 15,000 characters — rendered as rich text in the app. Headings, lists, bold, and inline code are supported. Send literal newlines and characters, not escape sequences.
-
reminder:string | null, format:timeA time of day for a reminder, "HH:MM" in 24-hour time (e.g. "15:00"), on the task's
whenday in the user's time zone. Moving the task moves it; null removes it. At that time Taskaid sends thetask.remindedevent to subscribed assistants. -
starred:boolean -
when:string | nullThe day the task is scheduled for, as "YYYY-MM-DD" (e.g. "2026-05-21"). Omit or pass null when creating to leave the task in Inbox; pass null when updating to move it back there.
Responses
200 The updated task.
Content type: application/json
schema: Task
-
completed(required):boolean -
completedAt(required):string | null -
duration(required):number | null -
id(required):string -
list(required):object | nullemoji(required):stringid(required):stringname(required):string
-
name(required):string -
note(required):string | null -
reminder(required):string | nullTime of day on the
whenday, "HH:MM", or null for none. -
source(required):string | null -
starred(required):boolean -
updatedAt(required):string -
url(required):string -
when(required):string | nullCalendar date, "YYYY-MM-DD", or null for Inbox.
400, 401, 403, 404, 415
400The request is malformed or breaks a rule.401No valid credential.403The credential lacks the scope.404The task, or the list it moves to, does not exist.415The body is not JSON.
Content type: application/problem+json
schema: Problem
Problem details (RFC 9457).
-
detail(required):stringWhat went wrong this time, for people. Don't parse it.
-
status(required):integer -
title(required):stringFixed for each type. Don't branch on it.
-
type(required):stringNames the problem: a type under https://taskaid.ai/problems/, or about:blank when the status says it all. Handle the types you recognise and fall back to the status for any other; ignore members you don't recognise.
412 The note changed since it was read; nothing was written.
Content type: application/problem+json
schema: NoteChangedProblem
The note changed since it was read.
-
detail(required):stringWhat went wrong this time, for people. Don't parse it.
-
note(required):string | nullThe note as stored now. Nothing was written.
-
status(required):integer -
title(required):stringFixed for each type. Don't branch on it.
-
type(required):string, possible values:"https://taskaid.ai/problems/note-changed"
Delete a task
- Method:
DELETE - Path:
/api/v1/tasks/{id} - Operation ID:
deleteTask - Tags: Tasks
Effective servers
/
Authentication
- oauth2: OAuth 2.0: authorization code, authorize
/oauth/authorize, token/oauth/token, refresh/oauth/token, scopes:write
Or:
-
apiKey: HTTP bearer, scopes:
writeAn API key from Settings.
Path parameters
-
id(required):stringTask reference, such as TASKAID-45 or 45.
Responses
204 The task is deleted.
400, 401, 403, 404
400The request is malformed or breaks a rule.401No valid credential.403The credential lacks the scope.404The task does not exist.
Content type: application/problem+json
schema: Problem
Problem details (RFC 9457).
-
detail(required):stringWhat went wrong this time, for people. Don't parse it.
-
status(required):integer -
title(required):stringFixed for each type. Don't branch on it.
-
type(required):stringNames the problem: a type under https://taskaid.ai/problems/, or about:blank when the status says it all. Handle the types you recognise and fall back to the status for any other; ignore members you don't recognise.
Lists
List lists
- Method:
GET - Path:
/api/v1/lists - Operation ID:
listLists - Tags: Lists
Every list, oldest first.
Effective servers
/
Authentication
- oauth2: OAuth 2.0: authorization code, authorize
/oauth/authorize, token/oauth/token, refresh/oauth/token, scopes:read
Or:
-
apiKey: HTTP bearer, scopes:
readAn API key from Settings.
Responses
200 The lists.
Content type: application/json
lists(required):array of List
400, 401, 403
400The request is malformed or breaks a rule.401No valid credential.403The credential lacks the scope.
Content type: application/problem+json
schema: Problem
Problem details (RFC 9457).
-
detail(required):stringWhat went wrong this time, for people. Don't parse it.
-
status(required):integer -
title(required):stringFixed for each type. Don't branch on it.
-
type(required):stringNames the problem: a type under https://taskaid.ai/problems/, or about:blank when the status says it all. Handle the types you recognise and fall back to the status for any other; ignore members you don't recognise.
Create a list
- Method:
POST - Path:
/api/v1/lists - Operation ID:
createList - Tags: Lists
Effective servers
/
Authentication
- oauth2: OAuth 2.0: authorization code, authorize
/oauth/authorize, token/oauth/token, refresh/oauth/token, scopes:write
Or:
-
apiKey: HTTP bearer, scopes:
writeAn API key from Settings.
Request body
Required: true
Content type: application/json
no additional properties
-
name(required):string, minLength:1, maxLength:120Required when creating. On update, omit to keep the current name.
-
emoji:string, minLength:1, maxLength:8A single emoji shown before the list name. Defaults to 📋 when creating; omit on update to keep the current one.
-
note:string | nullA note about the list, as Markdown, up to 500 characters: what it is for and how its tasks are handled. Replaces the whole note; null clears it.
Responses
201 The created list.
Headers:
-
Location(required)Where the new resource can be read.
stringWhere the new resource can be read.
Content type: application/json
schema: List
emoji(required):stringid(required):stringname(required):stringnote(required):string | null
400, 401, 403, 415
400The request is malformed or breaks a rule.401No valid credential.403The credential lacks the scope.415The body is not JSON.
Content type: application/problem+json
schema: Problem
Problem details (RFC 9457).
-
detail(required):stringWhat went wrong this time, for people. Don't parse it.
-
status(required):integer -
title(required):stringFixed for each type. Don't branch on it.
-
type(required):stringNames the problem: a type under https://taskaid.ai/problems/, or about:blank when the status says it all. Handle the types you recognise and fall back to the status for any other; ignore members you don't recognise.
Get a list
- Method:
GET - Path:
/api/v1/lists/{id} - Operation ID:
getList - Tags: Lists
Effective servers
/
Authentication
- oauth2: OAuth 2.0: authorization code, authorize
/oauth/authorize, token/oauth/token, refresh/oauth/token, scopes:read
Or:
-
apiKey: HTTP bearer, scopes:
readAn API key from Settings.
Path parameters
-
id(required):stringThe list's id.
Responses
200 The list.
Content type: application/json
schema: List
emoji(required):stringid(required):stringname(required):stringnote(required):string | null
400, 401, 403, 404
400The request is malformed or breaks a rule.401No valid credential.403The credential lacks the scope.404The list does not exist.
Content type: application/problem+json
schema: Problem
Problem details (RFC 9457).
-
detail(required):stringWhat went wrong this time, for people. Don't parse it.
-
status(required):integer -
title(required):stringFixed for each type. Don't branch on it.
-
type(required):stringNames the problem: a type under https://taskaid.ai/problems/, or about:blank when the status says it all. Handle the types you recognise and fall back to the status for any other; ignore members you don't recognise.
Update a list
- Method:
PATCH - Path:
/api/v1/lists/{id} - Operation ID:
updateList - Tags: Lists
Effective servers
/
Authentication
- oauth2: OAuth 2.0: authorization code, authorize
/oauth/authorize, token/oauth/token, refresh/oauth/token, scopes:write
Or:
-
apiKey: HTTP bearer, scopes:
writeAn API key from Settings.
Path parameters
-
id(required):stringThe list's id.
Request body
Required: true
Content type: application/json
no additional properties
-
emoji:string, minLength:1, maxLength:8A single emoji shown before the list name. Defaults to 📋 when creating; omit on update to keep the current one.
-
name:string, minLength:1, maxLength:120Required when creating. On update, omit to keep the current name.
-
note:string | nullA note about the list, as Markdown, up to 500 characters: what it is for and how its tasks are handled. Replaces the whole note; null clears it.
Responses
200 The updated list.
Content type: application/json
schema: List
emoji(required):stringid(required):stringname(required):stringnote(required):string | null
400, 401, 403, 404, 415
400The request is malformed or breaks a rule.401No valid credential.403The credential lacks the scope.404The list does not exist.415The body is not JSON.
Content type: application/problem+json
schema: Problem
Problem details (RFC 9457).
-
detail(required):stringWhat went wrong this time, for people. Don't parse it.
-
status(required):integer -
title(required):stringFixed for each type. Don't branch on it.
-
type(required):stringNames the problem: a type under https://taskaid.ai/problems/, or about:blank when the status says it all. Handle the types you recognise and fall back to the status for any other; ignore members you don't recognise.
Delete a list
- Method:
DELETE - Path:
/api/v1/lists/{id} - Operation ID:
deleteList - Tags: Lists
Its tasks stay, no longer in any list.
Effective servers
/
Authentication
- oauth2: OAuth 2.0: authorization code, authorize
/oauth/authorize, token/oauth/token, refresh/oauth/token, scopes:write
Or:
-
apiKey: HTTP bearer, scopes:
writeAn API key from Settings.
Path parameters
-
id(required):stringThe list's id.
Responses
204 The list is deleted.
400, 401, 403, 404
400The request is malformed or breaks a rule.401No valid credential.403The credential lacks the scope.404The list does not exist.
Content type: application/problem+json
schema: Problem
Problem details (RFC 9457).
-
detail(required):stringWhat went wrong this time, for people. Don't parse it.
-
status(required):integer -
title(required):stringFixed for each type. Don't branch on it.
-
type(required):stringNames the problem: a type under https://taskaid.ai/problems/, or about:blank when the status says it all. Handle the types you recognise and fall back to the status for any other; ignore members you don't recognise.
Plans
Get a day's plan
- Method:
GET - Path:
/api/v1/plans/{date} - Operation ID:
getPlan - Tags: Plans
Effective servers
/
Authentication
- oauth2: OAuth 2.0: authorization code, authorize
/oauth/authorize, token/oauth/token, refresh/oauth/token, scopes:read
Or:
-
apiKey: HTTP bearer, scopes:
readAn API key from Settings.
Path parameters
-
date(required):string, format:dateThe plan's day, as "YYYY-MM-DD".
Responses
200 The day's plan.
Content type: application/json
schema: Plan
-
content(required):string -
date(required):stringCalendar date, "YYYY-MM-DD".
-
id(required):string -
title(required):string | null -
url(required):string
400, 401, 403, 404
400The request is malformed or breaks a rule.401No valid credential.403The credential lacks the scope.404The day has no plan.
Content type: application/problem+json
schema: Problem
Problem details (RFC 9457).
-
detail(required):stringWhat went wrong this time, for people. Don't parse it.
-
status(required):integer -
title(required):stringFixed for each type. Don't branch on it.
-
type(required):stringNames the problem: a type under https://taskaid.ai/problems/, or about:blank when the status says it all. Handle the types you recognise and fall back to the status for any other; ignore members you don't recognise.
Create or replace a day's plan
- Method:
PUT - Path:
/api/v1/plans/{date} - Operation ID:
savePlan - Tags: Plans
Writes the day's whole plan. A title left out is cleared, as with any field PUT replaces.
Effective servers
/
Authentication
- oauth2: OAuth 2.0: authorization code, authorize
/oauth/authorize, token/oauth/token, refresh/oauth/token, scopes:write
Or:
-
apiKey: HTTP bearer, scopes:
writeAn API key from Settings.
Path parameters
-
date(required):string, format:dateThe plan's day, as "YYYY-MM-DD".
Request body
Required: true
Content type: application/json
no additional properties
-
content(required):string, minLength:1, maxLength:5000The whole day's plan, as Markdown.
-
title:string | null, minLength:1, maxLength:120A few words naming what the day is for. Left out or null, the plan has no title.
Responses
200 The plan, replaced.
Content type: application/json
schema: Plan
-
content(required):string -
date(required):stringCalendar date, "YYYY-MM-DD".
-
id(required):string -
title(required):string | null -
url(required):string
201 The plan, created.
Headers:
-
Location(required)Where the new resource can be read.
stringWhere the new resource can be read.
Content type: application/json
Schema Plan is shown above.
400, 401, 403, 415
400The request is malformed or breaks a rule.401No valid credential.403The credential lacks the scope.415The body is not JSON.
Content type: application/problem+json
schema: Problem
Problem details (RFC 9457).
-
detail(required):stringWhat went wrong this time, for people. Don't parse it.
-
status(required):integer -
title(required):stringFixed for each type. Don't branch on it.
-
type(required):stringNames the problem: a type under https://taskaid.ai/problems/, or about:blank when the status says it all. Handle the types you recognise and fall back to the status for any other; ignore members you don't recognise.