{"openapi":"3.1.0","info":{"title":"Taskaid API","version":"1"},"servers":[{"url":"/"}],"components":{"securitySchemes":{"oauth2":{"type":"oauth2","flows":{"authorizationCode":{"authorizationUrl":"/oauth/authorize","tokenUrl":"/oauth/token","refreshUrl":"/oauth/token","scopes":{"read":"Read tasks","write":"Write tasks"}}}},"apiKey":{"type":"http","scheme":"bearer","description":"An API key from Settings."}},"schemas":{"Task":{"type":"object","properties":{"id":{"type":"string"},"url":{"type":"string"},"name":{"type":"string"},"completed":{"type":"boolean"},"starred":{"type":"boolean"},"when":{"type":["string","null"],"description":"Calendar date, \"YYYY-MM-DD\", or null for Inbox."},"reminder":{"type":["string","null"],"description":"Time of day on the `when` day, \"HH:MM\", or null for none."},"note":{"type":["string","null"]},"duration":{"type":["number","null"]},"completedAt":{"type":["string","null"]},"updatedAt":{"type":"string"},"source":{"type":["string","null"]},"list":{"type":["object","null"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"emoji":{"type":"string"}},"required":["id","name","emoji"]}},"required":["id","url","name","completed","starred","when","reminder","note","duration","completedAt","updatedAt","source","list"]},"Problem":{"type":"object","properties":{"type":{"type":"string","description":"Names 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."},"title":{"type":"string","description":"Fixed for each type. Don't branch on it."},"status":{"type":"integer"},"detail":{"type":"string","description":"What went wrong this time, for people. Don't parse it."}},"required":["type","title","status","detail"],"description":"Problem details (RFC 9457)."},"NoteChangedProblem":{"type":"object","properties":{"type":{"type":"string","enum":["https://taskaid.ai/problems/note-changed"]},"title":{"type":"string","description":"Fixed for each type. Don't branch on it."},"status":{"type":"integer"},"detail":{"type":"string","description":"What went wrong this time, for people. Don't parse it."},"note":{"type":["string","null"],"description":"The note as stored now. Nothing was written."}},"required":["type","title","status","detail","note"],"description":"The note changed since it was read."},"List":{"type":"object","properties":{"id":{"type":"string"},"emoji":{"type":"string"},"name":{"type":"string"},"note":{"type":["string","null"]}},"required":["id","emoji","name","note"]},"Plan":{"type":"object","properties":{"id":{"type":"string"},"url":{"type":"string"},"date":{"type":"string","description":"Calendar date, \"YYYY-MM-DD\"."},"title":{"type":["string","null"]},"content":{"type":"string"}},"required":["id","url","date","title","content"]}},"parameters":{}},"paths":{"/api/v1/tasks":{"get":{"operationId":"listTasks","summary":"List tasks","tags":["Tasks"],"security":[{"oauth2":["read"]},{"apiKey":["read"]}],"parameters":[{"schema":{"type":"boolean","description":"Narrow to finished (true) or open (false) tasks. Omit for both."},"required":false,"description":"Narrow to finished (true) or open (false) tasks. Omit for both.","name":"completed","in":"query"},{"schema":{"type":"string","description":"Narrow to a single list. Omit to span every list."},"required":false,"description":"Narrow to a single list. Omit to span every list.","name":"listId","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":50,"description":"Max results. Defaults to 50, up to 100."},"required":false,"description":"Max results. Defaults to 50, up to 100.","name":"limit","in":"query"},{"schema":{"type":"string","description":"Continue from a previous response's nextCursor, with the same filters. Omit for the first page."},"required":false,"description":"Continue from a previous response's nextCursor, with the same filters. Omit for the first page.","name":"cursor","in":"query"},{"schema":{"type":"string","description":"Narrow 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."},"required":false,"description":"Narrow 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.","name":"query","in":"query"},{"schema":{"type":"string","description":"Narrow 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."},"required":false,"description":"Narrow 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.","name":"when[gte]","in":"query"},{"schema":{"type":"string","description":"Narrow 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."},"required":false,"description":"Narrow 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.","name":"when[lte]","in":"query"}],"responses":{"200":{"description":"A page of tasks.","content":{"application/json":{"schema":{"type":"object","properties":{"tasks":{"type":"array","items":{"$ref":"#/components/schemas/Task"}},"nextCursor":{"type":"string","description":"Pass as cursor, with the same filters, to read the next page. Absent on the last page."}},"required":["tasks"]}}}},"400":{"description":"The request is malformed or breaks a rule.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No valid credential.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The credential lacks the scope.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"post":{"operationId":"createTask","summary":"Create a task","tags":["Tasks"],"security":[{"oauth2":["write"]},{"apiKey":["write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":500,"description":"Required when creating. On update, omit to keep the current name. A few words naming the action; the day goes in `when`, the detail in `note`."},"note":{"type":["string","null"],"description":"Task 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."},"when":{"type":["string","null"],"description":"The 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."},"reminder":{"type":["string","null"],"format":"time","description":"A time of day for a reminder, \"HH:MM\" in 24-hour time (e.g. \"15:00\"), on the task's `when` day in the user's time zone. Moving the task moves it; null removes it. At that time Taskaid sends the `task.reminded` event to subscribed assistants."},"listId":{"type":["string","null"],"description":"Pass null to unlink the task from its list."},"completed":{"type":"boolean","description":"Marks the task done. True stamps `completedAt` with the time of the call, never the time the work finished."},"starred":{"type":"boolean"},"duration":{"type":["integer","null"],"minimum":1,"maximum":1440,"description":"Estimated duration in minutes, up to a day. Null clears the estimate."}},"required":["name"],"additionalProperties":false}}}},"responses":{"201":{"description":"The created task.","headers":{"Location":{"schema":{"type":"string","description":"Where the new resource can be read."},"required":true,"description":"Where the new resource can be read."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Task"}}}},"400":{"description":"The request is malformed or breaks a rule.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No valid credential.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The credential lacks the scope.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"The list it names does not exist.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"415":{"description":"The body is not JSON.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/tasks/{id}":{"get":{"operationId":"getTask","summary":"Get a task","tags":["Tasks"],"security":[{"oauth2":["read"]},{"apiKey":["read"]}],"parameters":[{"schema":{"type":"string","description":"Task reference, such as TASKAID-45 or 45."},"required":true,"description":"Task reference, such as TASKAID-45 or 45.","name":"id","in":"path"}],"responses":{"200":{"description":"The task.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Task"}}}},"400":{"description":"The request is malformed or breaks a rule.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No valid credential.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The credential lacks the scope.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"The task does not exist.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"patch":{"operationId":"updateTask","summary":"Update a task","tags":["Tasks"],"security":[{"oauth2":["write"]},{"apiKey":["write"]}],"parameters":[{"schema":{"type":"string","description":"Task reference, such as TASKAID-45 or 45."},"required":true,"description":"Task reference, such as TASKAID-45 or 45.","name":"id","in":"path"},{"schema":{"type":"string","description":"The 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."},"required":false,"description":"The 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.","name":"if-match","in":"header"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":500,"description":"Required when creating. On update, omit to keep the current name. A few words naming the action; the day goes in `when`, the detail in `note`."},"note":{"type":["string","null"],"description":"Task 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."},"when":{"type":["string","null"],"description":"The 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."},"reminder":{"type":["string","null"],"format":"time","description":"A time of day for a reminder, \"HH:MM\" in 24-hour time (e.g. \"15:00\"), on the task's `when` day in the user's time zone. Moving the task moves it; null removes it. At that time Taskaid sends the `task.reminded` event to subscribed assistants."},"listId":{"type":["string","null"],"description":"Pass null to unlink the task from its list."},"completed":{"type":"boolean","description":"Marks the task done. True stamps `completedAt` with the time of the call, never the time the work finished."},"starred":{"type":"boolean"},"duration":{"type":["integer","null"],"minimum":1,"maximum":1440,"description":"Estimated duration in minutes, up to a day. Null clears the estimate."}},"additionalProperties":false}}}},"responses":{"200":{"description":"The updated task.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Task"}}}},"400":{"description":"The request is malformed or breaks a rule.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No valid credential.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The credential lacks the scope.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"The task, or the list it moves to, does not exist.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"412":{"description":"The note changed since it was read; nothing was written.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/NoteChangedProblem"}}}},"415":{"description":"The body is not JSON.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"delete":{"operationId":"deleteTask","summary":"Delete a task","tags":["Tasks"],"security":[{"oauth2":["write"]},{"apiKey":["write"]}],"parameters":[{"schema":{"type":"string","description":"Task reference, such as TASKAID-45 or 45."},"required":true,"description":"Task reference, such as TASKAID-45 or 45.","name":"id","in":"path"}],"responses":{"204":{"description":"The task is deleted."},"400":{"description":"The request is malformed or breaks a rule.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No valid credential.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The credential lacks the scope.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"The task does not exist.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/lists":{"get":{"operationId":"listLists","summary":"List lists","description":"Every list, oldest first.","tags":["Lists"],"security":[{"oauth2":["read"]},{"apiKey":["read"]}],"responses":{"200":{"description":"The lists.","content":{"application/json":{"schema":{"type":"object","properties":{"lists":{"type":"array","items":{"$ref":"#/components/schemas/List"}}},"required":["lists"]}}}},"400":{"description":"The request is malformed or breaks a rule.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No valid credential.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The credential lacks the scope.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"post":{"operationId":"createList","summary":"Create a list","tags":["Lists"],"security":[{"oauth2":["write"]},{"apiKey":["write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120,"description":"Required when creating. On update, omit to keep the current name."},"emoji":{"type":"string","minLength":1,"maxLength":8,"description":"A single emoji shown before the list name. Defaults to 📋 when creating; omit on update to keep the current one."},"note":{"type":["string","null"],"description":"A 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."}},"required":["name"],"additionalProperties":false}}}},"responses":{"201":{"description":"The created list.","headers":{"Location":{"schema":{"type":"string","description":"Where the new resource can be read."},"required":true,"description":"Where the new resource can be read."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/List"}}}},"400":{"description":"The request is malformed or breaks a rule.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No valid credential.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The credential lacks the scope.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"415":{"description":"The body is not JSON.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/lists/{id}":{"get":{"operationId":"getList","summary":"Get a list","tags":["Lists"],"security":[{"oauth2":["read"]},{"apiKey":["read"]}],"parameters":[{"schema":{"type":"string","description":"The list's id."},"required":true,"description":"The list's id.","name":"id","in":"path"}],"responses":{"200":{"description":"The list.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/List"}}}},"400":{"description":"The request is malformed or breaks a rule.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No valid credential.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The credential lacks the scope.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"The list does not exist.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"patch":{"operationId":"updateList","summary":"Update a list","tags":["Lists"],"security":[{"oauth2":["write"]},{"apiKey":["write"]}],"parameters":[{"schema":{"type":"string","description":"The list's id."},"required":true,"description":"The list's id.","name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120,"description":"Required when creating. On update, omit to keep the current name."},"emoji":{"type":"string","minLength":1,"maxLength":8,"description":"A single emoji shown before the list name. Defaults to 📋 when creating; omit on update to keep the current one."},"note":{"type":["string","null"],"description":"A 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."}},"additionalProperties":false}}}},"responses":{"200":{"description":"The updated list.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/List"}}}},"400":{"description":"The request is malformed or breaks a rule.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No valid credential.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The credential lacks the scope.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"The list does not exist.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"415":{"description":"The body is not JSON.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"delete":{"operationId":"deleteList","summary":"Delete a list","description":"Its tasks stay, no longer in any list.","tags":["Lists"],"security":[{"oauth2":["write"]},{"apiKey":["write"]}],"parameters":[{"schema":{"type":"string","description":"The list's id."},"required":true,"description":"The list's id.","name":"id","in":"path"}],"responses":{"204":{"description":"The list is deleted."},"400":{"description":"The request is malformed or breaks a rule.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No valid credential.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The credential lacks the scope.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"The list does not exist.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/plans/{date}":{"get":{"operationId":"getPlan","summary":"Get a day's plan","tags":["Plans"],"security":[{"oauth2":["read"]},{"apiKey":["read"]}],"parameters":[{"schema":{"type":"string","format":"date","description":"The plan's day, as \"YYYY-MM-DD\"."},"required":true,"description":"The plan's day, as \"YYYY-MM-DD\".","name":"date","in":"path"}],"responses":{"200":{"description":"The day's plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Plan"}}}},"400":{"description":"The request is malformed or breaks a rule.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No valid credential.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The credential lacks the scope.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"The day has no plan.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"put":{"operationId":"savePlan","summary":"Create or replace a day's plan","description":"Writes the day's whole plan. A title left out is cleared, as with any field PUT replaces.","tags":["Plans"],"security":[{"oauth2":["write"]},{"apiKey":["write"]}],"parameters":[{"schema":{"type":"string","format":"date","description":"The plan's day, as \"YYYY-MM-DD\"."},"required":true,"description":"The plan's day, as \"YYYY-MM-DD\".","name":"date","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":["string","null"],"minLength":1,"maxLength":120,"description":"A few words naming what the day is for. Left out or null, the plan has no title."},"content":{"type":"string","minLength":1,"maxLength":5000,"description":"The whole day's plan, as Markdown."}},"required":["content"],"additionalProperties":false}}}},"responses":{"200":{"description":"The plan, replaced.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Plan"}}}},"201":{"description":"The plan, created.","headers":{"Location":{"schema":{"type":"string","description":"Where the new resource can be read."},"required":true,"description":"Where the new resource can be read."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Plan"}}}},"400":{"description":"The request is malformed or breaks a rule.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No valid credential.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The credential lacks the scope.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"415":{"description":"The body is not JSON.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}}},"webhooks":{}}