# Taskaid API

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`](https://taskaid.ai/openapi.json), in OpenAPI 3.1, also listed in the [API catalog](https://taskaid.ai/.well-known/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:

```http
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](/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](https://taskaid.ai/.well-known/oauth-authorization-server).

### 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 gets `415`.
- **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-45` or `45`. A list is named by its `id`.
- **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: `P0D` is today, `P1D` tomorrow, `-P1D` yesterday.
- **A reminder is a time of day,** `HH:MM` in 24-hour time, on the task's `when` day in your time zone. Setting one needs a `when`, a time zone, and a time that hasn't passed; otherwise it gets `400`.

## Responses

- **Creating a task or a list** answers `201 Created`, with the new one's address in `Location`.
- **Saving a day's plan** with `PUT` answers `201` when the plan is new and `200` when it replaces one. `PUT` writes 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 as `cursor`, with the same filters, for the next page. No `nextCursor` means the last page.

## Errors

Every error is a problem details object ([RFC 9457](https://www.rfc-editor.org/rfc/rfc9457)), sent as `application/problem+json`:

```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).

## 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:

1. Hash the note with SHA-256, as lowercase hex. Treat a null note as an empty string before hashing.
2. Send it in quotes, `If-Match: "<hash>"`, with your `PATCH`.

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](/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: `read`

  An API key from Settings.

#### Query parameters

- **`completed`**: `boolean`

  Narrow to finished (true) or open (false) tasks. Omit for both.
- **`listId`**: `string`

  Narrow to a single list. Omit to span every list.
- **`limit`**: `integer`, default: `50`, minimum: `1`, maximum: `100`

  Max results. Defaults to 50, up to 100.
- **`cursor`**: `string`

  Continue from a previous response's nextCursor, with the same filters. Omit for the first page.
- **`query`**: `string`

  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.
- **`when[gte]`**: `string`

  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.
- **`when[lte]`**: `string`

  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.

#### Responses

##### 200 A page of tasks.

**Content type:** `application/json`

- **`tasks` (required)**: `array of Task`
- **`nextCursor`**: `string`

  Pass as cursor, with the same filters, to read the next page. Absent on the last page.

##### 400, 401, 403

- `400` The request is malformed or breaks a rule.
- `401` No valid credential.
- `403` The credential lacks the scope.

**Content type:** `application/problem+json`

schema: `Problem`

Problem details (RFC 9457).

- **`detail` (required)**: `string`

  What went wrong this time, for people. Don't parse it.
- **`status` (required)**: `integer`
- **`title` (required)**: `string`

  Fixed for each type. Don't branch on it.
- **`type` (required)**: `string`

  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.

### 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: `write`

  An API key from Settings.

#### Request body

**Required:** `true`

**Content type:** `application/json`

no additional properties

- **`name` (required)**: `string`, minLength: `1`, maxLength: `500`

  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`.
- **`completed`**: `boolean`

  Marks the task done. True stamps `completedAt` with the time of the call, never the time the work finished.
- **`duration`**: `integer | null`, minimum: `1`, maximum: `1440`

  Estimated duration in minutes, up to a day. Null clears the estimate.
- **`listId`**: `string | null`

  Pass null to unlink the task from its list.
- **`note`**: `string | null`

  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.
- **`reminder`**: `string | null`, format: `time`

  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.
- **`starred`**: `boolean`
- **`when`**: `string | null`

  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.

#### Responses

##### 201 The created task.

**Headers:**

- **`Location`** (required)

  Where the new resource can be read.

  `string`

  Where 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 | null`
  - **`emoji` (required)**: `string`
  - **`id` (required)**: `string`
  - **`name` (required)**: `string`
- **`name` (required)**: `string`
- **`note` (required)**: `string | null`
- **`reminder` (required)**: `string | null`

  Time of day on the `when` day, "HH:MM", or null for none.
- **`source` (required)**: `string | null`
- **`starred` (required)**: `boolean`
- **`updatedAt` (required)**: `string`
- **`url` (required)**: `string`
- **`when` (required)**: `string | null`

  Calendar date, "YYYY-MM-DD", or null for Inbox.

##### 400, 401, 403, 404, 415

- `400` The request is malformed or breaks a rule.
- `401` No valid credential.
- `403` The credential lacks the scope.
- `404` The list it names does not exist.
- `415` The body is not JSON.

**Content type:** `application/problem+json`

schema: `Problem`

Problem details (RFC 9457).

- **`detail` (required)**: `string`

  What went wrong this time, for people. Don't parse it.
- **`status` (required)**: `integer`
- **`title` (required)**: `string`

  Fixed for each type. Don't branch on it.
- **`type` (required)**: `string`

  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.

### 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: `read`

  An API key from Settings.

#### Path parameters

- **`id` (required)**: `string`

  Task 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 | null`
  - **`emoji` (required)**: `string`
  - **`id` (required)**: `string`
  - **`name` (required)**: `string`
- **`name` (required)**: `string`
- **`note` (required)**: `string | null`
- **`reminder` (required)**: `string | null`

  Time of day on the `when` day, "HH:MM", or null for none.
- **`source` (required)**: `string | null`
- **`starred` (required)**: `boolean`
- **`updatedAt` (required)**: `string`
- **`url` (required)**: `string`
- **`when` (required)**: `string | null`

  Calendar date, "YYYY-MM-DD", or null for Inbox.

##### 400, 401, 403, 404

- `400` The request is malformed or breaks a rule.
- `401` No valid credential.
- `403` The credential lacks the scope.
- `404` The task does not exist.

**Content type:** `application/problem+json`

schema: `Problem`

Problem details (RFC 9457).

- **`detail` (required)**: `string`

  What went wrong this time, for people. Don't parse it.
- **`status` (required)**: `integer`
- **`title` (required)**: `string`

  Fixed for each type. Don't branch on it.
- **`type` (required)**: `string`

  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.

### 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: `write`

  An API key from Settings.

#### Path parameters

- **`id` (required)**: `string`

  Task reference, such as TASKAID-45 or 45.

#### Header parameters

- **`if-match`**: `string`

  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.

#### Request body

**Required:** `true`

**Content type:** `application/json`

no additional properties

- **`completed`**: `boolean`

  Marks the task done. True stamps `completedAt` with the time of the call, never the time the work finished.
- **`duration`**: `integer | null`, minimum: `1`, maximum: `1440`

  Estimated duration in minutes, up to a day. Null clears the estimate.
- **`listId`**: `string | null`

  Pass null to unlink the task from its list.
- **`name`**: `string`, minLength: `1`, maxLength: `500`

  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`**: `string | null`

  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.
- **`reminder`**: `string | null`, format: `time`

  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.
- **`starred`**: `boolean`
- **`when`**: `string | null`

  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.

#### 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 | null`
  - **`emoji` (required)**: `string`
  - **`id` (required)**: `string`
  - **`name` (required)**: `string`
- **`name` (required)**: `string`
- **`note` (required)**: `string | null`
- **`reminder` (required)**: `string | null`

  Time of day on the `when` day, "HH:MM", or null for none.
- **`source` (required)**: `string | null`
- **`starred` (required)**: `boolean`
- **`updatedAt` (required)**: `string`
- **`url` (required)**: `string`
- **`when` (required)**: `string | null`

  Calendar date, "YYYY-MM-DD", or null for Inbox.

##### 400, 401, 403, 404, 415

- `400` The request is malformed or breaks a rule.
- `401` No valid credential.
- `403` The credential lacks the scope.
- `404` The task, or the list it moves to, does not exist.
- `415` The body is not JSON.

**Content type:** `application/problem+json`

schema: `Problem`

Problem details (RFC 9457).

- **`detail` (required)**: `string`

  What went wrong this time, for people. Don't parse it.
- **`status` (required)**: `integer`
- **`title` (required)**: `string`

  Fixed for each type. Don't branch on it.
- **`type` (required)**: `string`

  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.

##### 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)**: `string`

  What went wrong this time, for people. Don't parse it.
- **`note` (required)**: `string | null`

  The note as stored now. Nothing was written.
- **`status` (required)**: `integer`
- **`title` (required)**: `string`

  Fixed 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: `write`

  An API key from Settings.

#### Path parameters

- **`id` (required)**: `string`

  Task reference, such as TASKAID-45 or 45.

#### Responses

##### 204 The task is deleted.

##### 400, 401, 403, 404

- `400` The request is malformed or breaks a rule.
- `401` No valid credential.
- `403` The credential lacks the scope.
- `404` The task does not exist.

**Content type:** `application/problem+json`

schema: `Problem`

Problem details (RFC 9457).

- **`detail` (required)**: `string`

  What went wrong this time, for people. Don't parse it.
- **`status` (required)**: `integer`
- **`title` (required)**: `string`

  Fixed for each type. Don't branch on it.
- **`type` (required)**: `string`

  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.

## 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: `read`

  An API key from Settings.

#### Responses

##### 200 The lists.

**Content type:** `application/json`

- **`lists` (required)**: `array of List`

##### 400, 401, 403

- `400` The request is malformed or breaks a rule.
- `401` No valid credential.
- `403` The credential lacks the scope.

**Content type:** `application/problem+json`

schema: `Problem`

Problem details (RFC 9457).

- **`detail` (required)**: `string`

  What went wrong this time, for people. Don't parse it.
- **`status` (required)**: `integer`
- **`title` (required)**: `string`

  Fixed for each type. Don't branch on it.
- **`type` (required)**: `string`

  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.

### 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: `write`

  An API key from Settings.

#### Request body

**Required:** `true`

**Content type:** `application/json`

no additional properties

- **`name` (required)**: `string`, minLength: `1`, maxLength: `120`

  Required when creating. On update, omit to keep the current name.
- **`emoji`**: `string`, minLength: `1`, maxLength: `8`

  A single emoji shown before the list name. Defaults to 📋 when creating; omit on update to keep the current one.
- **`note`**: `string | null`

  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.

#### Responses

##### 201 The created list.

**Headers:**

- **`Location`** (required)

  Where the new resource can be read.

  `string`

  Where the new resource can be read.

**Content type:** `application/json`

schema: `List`

- **`emoji` (required)**: `string`
- **`id` (required)**: `string`
- **`name` (required)**: `string`
- **`note` (required)**: `string | null`

##### 400, 401, 403, 415

- `400` The request is malformed or breaks a rule.
- `401` No valid credential.
- `403` The credential lacks the scope.
- `415` The body is not JSON.

**Content type:** `application/problem+json`

schema: `Problem`

Problem details (RFC 9457).

- **`detail` (required)**: `string`

  What went wrong this time, for people. Don't parse it.
- **`status` (required)**: `integer`
- **`title` (required)**: `string`

  Fixed for each type. Don't branch on it.
- **`type` (required)**: `string`

  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.

### 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: `read`

  An API key from Settings.

#### Path parameters

- **`id` (required)**: `string`

  The list's id.

#### Responses

##### 200 The list.

**Content type:** `application/json`

schema: `List`

- **`emoji` (required)**: `string`
- **`id` (required)**: `string`
- **`name` (required)**: `string`
- **`note` (required)**: `string | null`

##### 400, 401, 403, 404

- `400` The request is malformed or breaks a rule.
- `401` No valid credential.
- `403` The credential lacks the scope.
- `404` The list does not exist.

**Content type:** `application/problem+json`

schema: `Problem`

Problem details (RFC 9457).

- **`detail` (required)**: `string`

  What went wrong this time, for people. Don't parse it.
- **`status` (required)**: `integer`
- **`title` (required)**: `string`

  Fixed for each type. Don't branch on it.
- **`type` (required)**: `string`

  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.

### 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: `write`

  An API key from Settings.

#### Path parameters

- **`id` (required)**: `string`

  The list's id.

#### Request body

**Required:** `true`

**Content type:** `application/json`

no additional properties

- **`emoji`**: `string`, minLength: `1`, maxLength: `8`

  A single emoji shown before the list name. Defaults to 📋 when creating; omit on update to keep the current one.
- **`name`**: `string`, minLength: `1`, maxLength: `120`

  Required when creating. On update, omit to keep the current name.
- **`note`**: `string | null`

  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.

#### Responses

##### 200 The updated list.

**Content type:** `application/json`

schema: `List`

- **`emoji` (required)**: `string`
- **`id` (required)**: `string`
- **`name` (required)**: `string`
- **`note` (required)**: `string | null`

##### 400, 401, 403, 404, 415

- `400` The request is malformed or breaks a rule.
- `401` No valid credential.
- `403` The credential lacks the scope.
- `404` The list does not exist.
- `415` The body is not JSON.

**Content type:** `application/problem+json`

schema: `Problem`

Problem details (RFC 9457).

- **`detail` (required)**: `string`

  What went wrong this time, for people. Don't parse it.
- **`status` (required)**: `integer`
- **`title` (required)**: `string`

  Fixed for each type. Don't branch on it.
- **`type` (required)**: `string`

  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.

### 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: `write`

  An API key from Settings.

#### Path parameters

- **`id` (required)**: `string`

  The list's id.

#### Responses

##### 204 The list is deleted.

##### 400, 401, 403, 404

- `400` The request is malformed or breaks a rule.
- `401` No valid credential.
- `403` The credential lacks the scope.
- `404` The list does not exist.

**Content type:** `application/problem+json`

schema: `Problem`

Problem details (RFC 9457).

- **`detail` (required)**: `string`

  What went wrong this time, for people. Don't parse it.
- **`status` (required)**: `integer`
- **`title` (required)**: `string`

  Fixed for each type. Don't branch on it.
- **`type` (required)**: `string`

  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.

## 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: `read`

  An API key from Settings.

#### Path parameters

- **`date` (required)**: `string`, format: `date`

  The plan's day, as "YYYY-MM-DD".

#### Responses

##### 200 The day's plan.

**Content type:** `application/json`

schema: `Plan`

- **`content` (required)**: `string`
- **`date` (required)**: `string`

  Calendar date, "YYYY-MM-DD".
- **`id` (required)**: `string`
- **`title` (required)**: `string | null`
- **`url` (required)**: `string`

##### 400, 401, 403, 404

- `400` The request is malformed or breaks a rule.
- `401` No valid credential.
- `403` The credential lacks the scope.
- `404` The day has no plan.

**Content type:** `application/problem+json`

schema: `Problem`

Problem details (RFC 9457).

- **`detail` (required)**: `string`

  What went wrong this time, for people. Don't parse it.
- **`status` (required)**: `integer`
- **`title` (required)**: `string`

  Fixed for each type. Don't branch on it.
- **`type` (required)**: `string`

  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.

### 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: `write`

  An API key from Settings.

#### Path parameters

- **`date` (required)**: `string`, format: `date`

  The plan's day, as "YYYY-MM-DD".

#### Request body

**Required:** `true`

**Content type:** `application/json`

no additional properties

- **`content` (required)**: `string`, minLength: `1`, maxLength: `5000`

  The whole day's plan, as Markdown.
- **`title`**: `string | null`, minLength: `1`, maxLength: `120`

  A 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)**: `string`

  Calendar 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.

  `string`

  Where the new resource can be read.

**Content type:** `application/json`

*Schema `Plan` is shown above.*

##### 400, 401, 403, 415

- `400` The request is malformed or breaks a rule.
- `401` No valid credential.
- `403` The credential lacks the scope.
- `415` The body is not JSON.

**Content type:** `application/problem+json`

schema: `Problem`

Problem details (RFC 9457).

- **`detail` (required)**: `string`

  What went wrong this time, for people. Don't parse it.
- **`status` (required)**: `integer`
- **`title` (required)**: `string`

  Fixed for each type. Don't branch on it.
- **`type` (required)**: `string`

  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.

