# Plan · Tasks

> Create, read, update, move, archive and restore tasks, one at a time or in bulk, plus relations, labels, participants and subscriptions. Part of the Dailybot Plan API (Beta).

Language: en
Canonical: https://www.dailybot.com/developers/api/plan-tasks
Markdown: send header `Accept: text/markdown` on any URL to receive Markdown instead of HTML.
Last Updated: 2026-09-25

---

> **Beta** — Plan is in beta. Everything under `/plan` in the web app, the CLI and agent skill commands for projects, goals, boards and tasks, and the `/v1/plan/` public API may change before general availability. Want to try it with your team? Write to **support@dailybot.com**.

This page is the reference for **Plan · Tasks**. Every endpoint lives under `https://api.dailybot.com/v1/plan/` and answers JSON.

Authenticate with a login session or a CLI user token (`Authorization: Bearer …`), or with an API key (`X-API-KEY`). A **personal API key** acts as its person and can do everything that person can do in Dailybot; an **agent or organization key** never acts as a person and is refused on the endpoints that need one. On an endpoint, the *API key* badge means an agent or organization key is accepted too. See [Authentication for Plan](/developers/plan/authentication), [Authentication](/developers/authentication) and [Errors](/developers/errors) for the rules shared by every Dailybot API.

New to Plan? Read the [overview](/developers/plan) for the model: projects, boards, workflow states, keys, ordering, versions and archive.

## Endpoints in this group

Create, read, update, move, archive and restore tasks, one at a time or in bulk, plus relations, labels, participants and subscriptions. Part of the Dailybot Plan API (Beta).

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/v1/plan/boards/{board_id}/tasks/` | List tasks on one board (alias of `GET /v1/plan/tasks/?board=`) |
| POST | `/v1/plan/boards/{board_id}/tasks/` | Create a task on this board (alias of `POST /v1/plan/tasks/`) |
| GET | `/v1/plan/tasks/` | List tasks with the shared filter grammar |
| POST | `/v1/plan/tasks/` | Create a task |
| POST | `/v1/plan/tasks/bulk/` | Create, or apply one operation to, up to 100 tasks |
| GET | `/v1/plan/tasks/{task_id}/` | Retrieve a task by uuid or by KEY-n |
| PATCH | `/v1/plan/tasks/{task_id}/` | Update a task |
| DELETE | `/v1/plan/tasks/{task_id}/` | Archive a task (DELETE alias) |
| GET | `/v1/plan/tasks/{task_id}/children/` | List direct sub-tasks of a task |
| POST | `/v1/plan/tasks/{task_id}/archive/` | Archive a task and its sub-tasks |
| POST | `/v1/plan/tasks/{task_id}/duplicate/` | Duplicate a task on the same board |
| POST | `/v1/plan/tasks/{task_id}/move-board/` | Move a task to another board |
| POST | `/v1/plan/tasks/{task_id}/move/` | Move a task to a state and a position, relatively |
| GET | `/v1/plan/tasks/{task_id}/relations/` | A task's relations, both directions |
| POST | `/v1/plan/tasks/{task_id}/relations/` | Link two tasks |
| DELETE | `/v1/plan/tasks/{task_id}/relations/{relation_id}/` | Unlink two tasks |
| POST | `/v1/plan/tasks/{task_id}/labels/batch/` | Attach, detach or replace a task's labels |
| POST | `/v1/plan/tasks/{task_id}/subscription/` | Subscribe to task notifications (watcher role) |
| DELETE | `/v1/plan/tasks/{task_id}/subscription/` | Remove a watcher subscription |
| POST | `/v1/plan/tasks/{task_id}/restore/` | Restore an archived task |
| GET | `/v1/plan/tasks/{task_id}/participants/` | Who is on this card |
| POST | `/v1/plan/tasks/{task_id}/participants/` | Put someone on this card |
| DELETE | `/v1/plan/tasks/{task_id}/participants/{user_uuid}/` | Take someone off this card |
| PATCH | `/v1/plan/tasks/{task_id}/attachments/{attachment_id}/` | Rename a task attachment |

### GET `/v1/plan/boards/{board_id}/tasks/` · Beta

**List tasks on one board (alias of `GET /v1/plan/tasks/?board=`)**

Convenience alias for clients that nest under the board URL. Same paginated `Task` envelope and shared filter grammar as `GET /v1/plan/tasks/?board={board_id}`. The path `board_id` wins over a conflicting `board=` query parameter.
Prefer this or `?board=` for flat lists; use `GET …/boards/{id}/board/` for the denser snapshot UI.

- **Auth:** API key (`X-API-KEY`), CLI Bearer (read)
- **Rate limit:** `default`
- **Pagination:** Page-number pagination

#### Path parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `board_id` | string | Yes | The board's uuid. |

#### Query parameters

##### Pagination

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `page` | integer | No | 1-based page number. |
| `page_size` | integer | No | Rows per page. Default 50, maximum 100. Out-of-range values are clamped, never rejected: asking for 500 returns 100. |
| `limit` | integer | No | Alias for `page_size`, translated server-side. |
| `offset` | integer | No | Alias translated to `page` server-side. |

##### Filters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `search` | string | No | Matches title and key. Longer than 256 characters is `400 search_query_too_long`, not truncated. `q` is an alias. |
| `key_prefix` | string | No | Select the tasks of every board a key has ever named, retired keys included: `?key_prefix=ENG`. An unknown prefix returns an empty list. |
| `state` | array | No | Repeatable; values are OR-ed. Each value is **either** a workflow-state uuid **or** one of two lifecycle tokens:
- `open`  - the state categories that are not terminal: `backlog`, `todo`, `in_progress`.
- `done`  - the terminal categories: `done`, `canceled`.
The tokens are decided by `state.category` alone and never consult `completed_at`, so a client that classifies rows by the category on the state chip agrees with this filter by construction.
Mixing is allowed: a uuid and a token in the same request are OR-ed like any other repeated value. **Any other value is `400 invalid_filter_value`** with `extra.parameter: "state"` - including `overdue`, which is not a lifecycle state. Overdue is a due-date question: see `due_before`. |
| `category` | array | No | The five fixed state categories. There is no `blocked` category — blocked-ness is a relation; use `blocked=true`. |
| `owner` | array | No | A user uuid, `me`, or `unowned`. Repeatable; values are OR-ed, tokens included: `owner=me&owner=unowned` returns your tasks and the unowned ones. `me` with an agent or organization key is `400 actor_required`. |
| `label` | array | No | Label uuids — v4 only, at most 50, matching the shared label filter's existing cap. A non-v4 value is `400 invalid_label_filter`. |
| `priority` | array | No | 1=urgent, 2=high, 3=medium, 4=low, 5=none. Repeatable. |
| `parent` | string | No | A parent task uuid, or `none` for top-level tasks only. `parent_task` is accepted as an alias of this parameter (same value). Sending both with conflicting values is `400 invalid_filter_value`. |
| `parent_task` | string | No | Alias of `parent` — preferred by some Web clients. Same grammar (uuid or `none`). Do not send both with different values. |
| `blocked` | boolean | No | Derived from relations, not from a status. This is the query the product answers with a link rather than a state.
`blocked=true` means a **live** blocker: a `blocks` relation whose source task is neither archived nor in a terminal category. A blocker that is itself `done` or `canceled` blocks nothing and does not match.
**Orthogonal to lifecycle.** A finished task can still carry a live blocker, so `blocked=true` alone returns terminal rows too. Work a person can act on is `blocked=true&state=open` — that combination is what reproduces the `blocked` tile on `GET /v1/plan/pulse/`. |
| `goal` | string | No | Repeatable. Matches a task's own goal, or the goal it inherits from its project when it has none, the same rule every progress roll-up uses. |
| `team` | string | No | Repeatable. The board's team. It narrows what you see and never widens it. |
| `participant` | string | No | Repeatable. Somebody on the card, owner or not. |
| `created_by` | string | No | Repeatable. Who opened the card. |
| `estimate_min` | integer | No | Minimum `estimate`, inclusive, in the units stored on the task (no conversion from the board's scale). |
| `estimate_max` | integer | No | Maximum `estimate`, inclusive, in the units stored on the task (no conversion from the board's scale). |

##### Dates

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `due_before` | string | No | Inclusive. Alone, this means overdue **or** due by that date - it does not exclude work that is already finished.
**Overdue is spelled `due_before=<today>&state=open`.** That pairing is the supported spelling, it is what reproduces the `overdue` tile on `GET /v1/plan/pulse/`, and there is deliberately no `state=overdue` sugar: `state` is a lifecycle dimension and overdue is a date one, so a single spelling keeps the two from drifting. `state=overdue` answers `400 invalid_filter_value`, which is evidence about that spelling and not about the capability. |
| `due_after` | string | No | Inclusive. |
| `start_after` | string | No | A date (`YYYY-MM-DD`): tasks whose `start_date` is on or after it, inclusive. A bad value is `400 invalid_filter_value`. |
| `start_before` | string | No | A date (`YYYY-MM-DD`): tasks whose `start_date` is on or before it, inclusive. A bad value is `400 invalid_filter_value`. |
| `completed_after` | string | No | A date (`YYYY-MM-DD`): tasks completed on or after it, inclusive (the date of `completed_at`). A bad value is `400 invalid_filter_value`. |
| `completed_before` | string | No | A date (`YYYY-MM-DD`): tasks completed on or before it, inclusive (the date of `completed_at`). A bad value is `400 invalid_filter_value`. |
| `has_due_date` | boolean | No | `false` is the planner's first question: what is not scheduled. |
| `has_start_date` | boolean | No | `true` keeps tasks with a `start_date`; `false` keeps tasks without one. |
| `has_dates` | boolean | No | Both scheduling edges at once. `has_dates=false` means **neither** a start date nor a due date (the unscheduled tray). `has_dates=true` means **at least one**, which is not the same as `has_due_date=true`. |
| `updated_since` | string | No | A timestamp filter on this paginated list: it returns `{count, next, previous, results}`, never a cursor. For a change feed use the board delta endpoint. |
| `start_date` | string | No | Created-at window start. What the CLI's `--since` produces. |
| `end_date` | string | No | Created-at window end. What the CLI's `--until` produces. |

##### Archived rows

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `is_archived` | boolean | No | `true` returns only archived rows; `false` (the default) only live ones. Archive is the delete, so archived rows stay readable. |
| `include_archived` | boolean | No | Include archived rows alongside live ones. Distinct from `is_archived`, which selects one set or the other: `include_archived=true` is the union. Lists return live rows unless you opt in. |

##### Sorting & expansion

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `sort` | string | No | One field, optionally `-`-prefixed. Every ordering appends a stable internal tiebreak so a row cannot appear on two pages. An unsupported value is `400 invalid_sort`, never a silent fallback. |

#### Task object

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `uuid` | uuid | Yes | Stable public identifier. |
| `key` | string | Yes | Human-readable key `KEY-n`, for example `ENG-142`. Retired keys keep resolving. |
| `title` | string | Yes | The task's title. Max 255 characters. |
| `description` | string | null | No | Free-form description. |
| `board` | uuid | No | The board. |
| `state` | WorkflowState | Yes | The task's workflow state (its column). See [WorkflowState](#plan-board-tasks-list-workflowstate). |
| `priority` | integer | No | 1 urgent, 2 high, 3 medium, 4 low, 5 none. From 1 to 5. |
| `estimate` | integer | null | No | Estimate on the board's scale. |
| `owner` | UserRef | null | No | The person accountable for the task. See [UserRef](#plan-board-tasks-list-userref). |
| `executor` | ActorRef | null | No | The actor doing the work, when different from the owner (for example an agent). See [ActorRef](#plan-board-tasks-list-actorref). |
| `executors` | object[] | No | Every agent that has executed a write on this task on someone's behalf, newest first: `{uuid, name, username, avatar, first_at, last_at}`. Separate from `executor`, which stays the current ball-holder. Only on task detail and single-task write responses; omitted on list rows. |
| `participant_count` | integer | No | Number of participants. |
| `start_date` | date | null | No | Planned start date. |
| `due_date` | date | null | No | Due date. |
| `milestone` | null | {uuid, name, date} | No | The milestone this task counts toward. All fields are always present. |
| `parent_task` | null | {uuid, key, title} | No | The parent task, for a sub-task. One level of nesting only. All fields are always present. |
| `subtask_count` | integer | No | Number of sub-tasks. |
| `subtask_done_count` | integer | No | Number of finished sub-tasks. |
| `attachment_count` | integer | No | Number of attachments. |
| `open_blocker_count` | integer | No | Number of live blockers. |
| `labels` | array<Label> | No | Organization labels on the task. See [Label](#plan-board-tasks-list-label). |
| `rank` | string | null | No | Opaque order within the column. Never compute it: move with `after` / `before`. |
| `blocked` | boolean | No | Tasks with a live blocker. |
| `blocked_since` | date-time | null | No | When the task became blocked. |
| `completed_at` | date-time | null | No | When it was completed, or `null`. |
| `is_archived` | boolean | Yes | Whether the row is archived. Archive is the delete: archived rows stay readable and restorable. |
| `subscribed` | boolean | No | Whether you watch this task. |
| `version` | integer | Yes | Increments on every write. Send it back as `If-Match` to refuse a stale update. |
| `created_by` | ActorRef | null | No | Who created the row. See [ActorRef](#plan-board-tasks-list-actorref). |
| `created_at` | date-time | No | When the row was created. |
| `updated_at` | date-time | No | When the row last changed. |

#### WorkflowState object

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `uuid` | uuid | Yes | Stable public identifier. |
| `name` | string | Yes | Display name. Max 48 characters. |
| `category` | enum | Yes | One of the five fixed categories. It never changes after create. One of `backlog`, `todo`, `in_progress`, `done`, `canceled`. |
| `position` | integer | Yes | Column position, left to right. Minimum 0. |
| `color` | string | No | Display color (hex). |
| `is_default` | boolean | No | Whether new tasks land in this state by default. |
| `is_archived` | boolean | No | Whether the row is archived. Archive is the delete: archived rows stay readable and restorable. |
| `task_count` | integer | No | Number of live tasks. |

#### UserRef object

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `uuid` | uuid | Yes | Stable public identifier. |
| `name` | string | No | Display name. |
| `avatar_url` | string | null | No | — |
| `has_photo` | boolean | No | — |

#### ActorRef object

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `kind` | string | Yes | — |
| `uuid` | string | Yes | Stable public identifier. |
| `name` | string | No | Display name. |
| `username` | string | null | No | — |
| `avatar_url` | string | null | No | — |
| `has_photo` | boolean | No | — |

#### Label object

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `uuid` | uuid | Yes | Stable public identifier. |
| `name` | string | Yes | Display name. Max 64 characters. |
| `color` | string | No | Display color (hex). |

#### Response body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `count` | integer | Yes | Total number of rows. |
| `next` | uri | Yes | URL of the next page, or `null`. |
| `previous` | uri | Yes | URL of the previous page, or `null`. |
| `results` | array<Task> | Yes | The rows on this page. See [Task](#plan-board-tasks-list-task). |

#### Error codes

| Status | When |
|--------|------|
| `400` | Validation failed, or a filter, sort or `include` value was not recognised. The response `code` says which. |
| `401` | Missing, expired or malformed credential (`credential_absent`, `credential_expired`, `credential_malformed`). |
| `402` | Plan is not enabled for your organization yet (`plan_upgrade_required`). Expected during the Beta: write to support@dailybot.com. |
| `404` | Not found, or not visible to you. Both cases return the same body. |

#### Example (curl)

```bash
curl -sS "https://api.dailybot.com/v1/plan/boards/00000000-0000-4000-8000-000000000002/tasks/?state=open" \
  -H "X-API-KEY: $DAILYBOT_API_KEY"
```

#### Scenario examples

##### CLI

```bash
dailybot plan board tasks 00000000-0000-4000-8000-000000000002 --page 2
```

##### Response

```bash
{
  "count": 1,
  "next": null,
  "previous": null,
  "results": [
    {
      "uuid": "00000000-0000-4000-8000-000000000005",
      "key": "ENG-142",
      "title": "Ship the delta feed",
      "description": null,
      "board": "00000000-0000-4000-8000-000000000002",
      "state": {
        "uuid": "00000000-0000-4000-8000-000000000003",
        "name": "In Progress",
        "category": "in_progress",
        "position": 2
      },
      "priority": 2,
      "estimate": 3,
      "owner": {
        "uuid": "00000000-0000-4000-8000-00000000000c",
        "name": "Ada L."
      },
      "executor": null,
      "participant_count": 1,
      "start_date": "2026-09-28",
      "due_date": "2026-10-15",
      "milestone": null,
      "parent_task": null,
      "subtask_count": 1,
      "subtask_done_count": 1,
      "attachment_count": 1,
      "open_blocker_count": 1,
      "labels": [],
      "rank": "aU",
      "blocked": false,
      "blocked_since": null,
      "completed_at": null,
      "is_archived": false,
      "subscribed": true,
      "version": 7,
      "created_by": {
        "kind": "user",
        "uuid": "00000000-0000-4000-8000-00000000000c",
        "name": "Ada L."
      },
      "created_at": "2026-09-25T10:14:02Z",
      "updated_at": "2026-09-25T10:14:02Z"
    }
  ]
}
```

#### Notes

- Scope: `tasks:read`.
- Rate limit: 120 reads per minute per actor.
- Works with a login session, a CLI user token, a personal API key, or an agent or organization key. A personal key sees what its person sees; an agent or organization key acts as a system actor and sees organization-visible boards only.

### POST `/v1/plan/boards/{board_id}/tasks/` · Beta

**Create a task on this board (alias of `POST /v1/plan/tasks/`)**

Same create semantics as `POST /v1/plan/tasks/` with the board taken from the path (`board` in the body is optional and overridden).
`Idempotency-Key` is optional and recommended.

- **Auth:** API key (`X-API-KEY`), CLI Bearer (write)
- **Rate limit:** `default`
- **Pagination:** No pagination

#### Path parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `board_id` | string | Yes | The board's uuid. |

#### Headers

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `Idempotency-Key` | string | No | A key you generate for this intent. A replay with the same key and body returns the first response without a second side effect and carries `Idempotency-Replayed: true`. Keys are kept for 24 hours. The same key with a different body is `409 idempotency_key_payload_mismatch`; a repeat while the first call is still running gets `409 idempotency_in_progress` for up to 120 seconds. |
| `X-Dailybot-Agent-Name` | string | No | The name of the agent that executed this write on the person's behalf. Use it on multipart and body-less writes (DELETE, archive, restore); on JSON writes send the body field `agent_name` instead, which wins if both are present. Percent-encode the value as UTF-8. Control characters are stripped; a blank value means no agent. More than 128 characters, or a value that cannot be decoded, is `400 invalid_agent_attribution` (never truncated). An agent-type key, which is not bound to a person, gets `400 invalid_agent_attribution` if it sends it. The stamp never changes a permission answer. See [Agent attribution](/developers/plan/conventions#agent-attribution). |

#### Request body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `milestone` | uuid | null | No | Not accepted on create yet (`501 not_implemented`): set the milestone with `PATCH` after creating the task. |
| `title` | string | Yes | The task's title. Max 255 characters. |
| `description` | string | null | No | Free-form description. Max 50000 characters. |
| `priority` | integer | No | 1 urgent, 2 high, 3 medium, 4 low, 5 none. From 1 to 5. |
| `estimate` | integer | null | No | Estimate on the board's scale. |
| `owner` | string | null | No | The owner's user uuid. The person must already be able to see the board (`400 participant_cannot_access_board` otherwise). |
| `start_date` | date | null | No | Planned start date. |
| `due_date` | date | null | No | Due date. |
| `parent_task` | uuid | null | No | The parent task, for a sub-task. One level of nesting only. |
| `label_uuids` | array | No | Label uuids to set on the task. Items: `uuid`. |
| `after` | uuid | null | No | Place the pin just below this pin. |
| `before` | uuid | null | No | Place the pin just above this pin. |
| `version` | integer | No | The version you loaded. A stale value is `409 version_conflict`. |
| `agent_name` | string | No | The name of the agent that executed this write on the person's behalf (max 128 characters, blank means no agent). Takes priority over the `X-Dailybot-Agent-Name` header. See [Agent attribution](/developers/plan/conventions#agent-attribution). |

#### Response body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `(body)` | Task | Yes | A [Task](#plan-board-tasks-list-task) object. |

#### Error codes

| Status | When |
|--------|------|
| `400` | Validation failed, or a filter, sort or `include` value was not recognised. The response `code` says which. `invalid_agent_attribution` means the agent name is invalid. |
| `401` | Missing, expired or malformed credential (`credential_absent`, `credential_expired`, `credential_malformed`). |
| `402` | Plan is not enabled for your organization yet (`plan_upgrade_required`). Expected during the Beta: write to support@dailybot.com. |
| `409` | Conflict. The response `code` says which (for example `version_conflict`). |

#### Example (curl)

```bash
curl -sS -X POST "https://api.dailybot.com/v1/plan/boards/00000000-0000-4000-8000-000000000002/tasks/" \
  -H "X-API-KEY: $DAILYBOT_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Ship the delta feed",
    "priority": 2
  }'
```

#### Scenario examples

##### CLI

```bash
dailybot plan task create -t "Ship the delta feed" -b 00000000-0000-4000-8000-000000000002
```

#### Notes

- Scope: `tasks:write`.
- Rate limit: 60 writes per minute per actor.
- Works with a login session, a CLI user token, a personal API key, or an agent or organization key. A personal key sees what its person sees; an agent or organization key acts as a system actor and sees organization-visible boards only.

### GET `/v1/plan/tasks/` · Beta

**List tasks with the shared filter grammar**

Multi-value semantics are OR within a parameter and AND across parameters. An unknown parameter is ignored; an unparseable value of a known parameter is `400 invalid_filter_value`.

- **Auth:** API key (`X-API-KEY`), CLI Bearer (read)
- **Rate limit:** `default`
- **Pagination:** Page-number pagination

#### Query parameters

##### Pagination

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `page` | integer | No | 1-based page number. |
| `page_size` | integer | No | Rows per page. Default 50, maximum 100. Out-of-range values are clamped, never rejected: asking for 500 returns 100. |
| `limit` | integer | No | Alias for `page_size`, translated server-side. |
| `offset` | integer | No | Alias translated to `page` server-side. |

##### Filters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `search` | string | No | Matches title and key. Longer than 256 characters is `400 search_query_too_long`, not truncated. `q` is an alias. |
| `board` | array | No | Board uuids **or** board keys (`ENG`). Repeatable; values are OR-ed. Keys resolve within your organization only; a key that names no board of yours contributes nothing and never returns a 404. Retired keys keep resolving. |
| `key_prefix` | string | No | Select the tasks of every board a key has ever named, retired keys included: `?key_prefix=ENG`. An unknown prefix returns an empty list. |
| `project` | array | No | Project uuids. Repeatable; values are OR-ed. |
| `state` | array | No | Repeatable; values are OR-ed. Each value is **either** a workflow-state uuid **or** one of two lifecycle tokens:
- `open`  - the state categories that are not terminal: `backlog`, `todo`, `in_progress`.
- `done`  - the terminal categories: `done`, `canceled`.
The tokens are decided by `state.category` alone and never consult `completed_at`, so a client that classifies rows by the category on the state chip agrees with this filter by construction.
Mixing is allowed: a uuid and a token in the same request are OR-ed like any other repeated value. **Any other value is `400 invalid_filter_value`** with `extra.parameter: "state"` - including `overdue`, which is not a lifecycle state. Overdue is a due-date question: see `due_before`. |
| `category` | array | No | The five fixed state categories. There is no `blocked` category — blocked-ness is a relation; use `blocked=true`. |
| `owner` | array | No | A user uuid, `me`, or `unowned`. Repeatable; values are OR-ed, tokens included: `owner=me&owner=unowned` returns your tasks and the unowned ones. `me` with an agent or organization key is `400 actor_required`. |
| `label` | array | No | Label uuids — v4 only, at most 50, matching the shared label filter's existing cap. A non-v4 value is `400 invalid_label_filter`. |
| `priority` | array | No | 1=urgent, 2=high, 3=medium, 4=low, 5=none. Repeatable. |
| `parent` | string | No | A parent task uuid, or `none` for top-level tasks only. `parent_task` is accepted as an alias of this parameter (same value). Sending both with conflicting values is `400 invalid_filter_value`. |
| `parent_task` | string | No | Alias of `parent` — preferred by some Web clients. Same grammar (uuid or `none`). Do not send both with different values. |
| `blocked` | boolean | No | Derived from relations, not from a status. This is the query the product answers with a link rather than a state.
`blocked=true` means a **live** blocker: a `blocks` relation whose source task is neither archived nor in a terminal category. A blocker that is itself `done` or `canceled` blocks nothing and does not match.
**Orthogonal to lifecycle.** A finished task can still carry a live blocker, so `blocked=true` alone returns terminal rows too. Work a person can act on is `blocked=true&state=open` — that combination is what reproduces the `blocked` tile on `GET /v1/plan/pulse/`. |
| `goal` | string | No | Repeatable. Matches a task's own goal, or the goal it inherits from its project when it has none, the same rule every progress roll-up uses. |
| `team` | string | No | Repeatable. The board's team. It narrows what you see and never widens it. |
| `participant` | string | No | Repeatable. Somebody on the card, owner or not. |
| `created_by` | string | No | Repeatable. Who opened the card. |
| `estimate_min` | integer | No | Minimum `estimate`, inclusive, in the units stored on the task (no conversion from the board's scale). |
| `estimate_max` | integer | No | Maximum `estimate`, inclusive, in the units stored on the task (no conversion from the board's scale). |

##### Dates

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `due_before` | string | No | Inclusive. Alone, this means overdue **or** due by that date - it does not exclude work that is already finished.
**Overdue is spelled `due_before=<today>&state=open`.** That pairing is the supported spelling, it is what reproduces the `overdue` tile on `GET /v1/plan/pulse/`, and there is deliberately no `state=overdue` sugar: `state` is a lifecycle dimension and overdue is a date one, so a single spelling keeps the two from drifting. `state=overdue` answers `400 invalid_filter_value`, which is evidence about that spelling and not about the capability. |
| `due_after` | string | No | Inclusive. |
| `start_after` | string | No | A date (`YYYY-MM-DD`): tasks whose `start_date` is on or after it, inclusive. A bad value is `400 invalid_filter_value`. |
| `start_before` | string | No | A date (`YYYY-MM-DD`): tasks whose `start_date` is on or before it, inclusive. A bad value is `400 invalid_filter_value`. |
| `completed_after` | string | No | A date (`YYYY-MM-DD`): tasks completed on or after it, inclusive (the date of `completed_at`). A bad value is `400 invalid_filter_value`. |
| `completed_before` | string | No | A date (`YYYY-MM-DD`): tasks completed on or before it, inclusive (the date of `completed_at`). A bad value is `400 invalid_filter_value`. |
| `has_due_date` | boolean | No | `false` is the planner's first question: what is not scheduled. |
| `has_start_date` | boolean | No | `true` keeps tasks with a `start_date`; `false` keeps tasks without one. |
| `has_dates` | boolean | No | Both scheduling edges at once. `has_dates=false` means **neither** a start date nor a due date (the unscheduled tray). `has_dates=true` means **at least one**, which is not the same as `has_due_date=true`. |
| `updated_since` | string | No | A timestamp filter on this paginated list: it returns `{count, next, previous, results}`, never a cursor. For a change feed use the board delta endpoint. |
| `start_date` | string | No | Created-at window start. What the CLI's `--since` produces. |
| `end_date` | string | No | Created-at window end. What the CLI's `--until` produces. |

##### Archived rows

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `is_archived` | boolean | No | `true` returns only archived rows; `false` (the default) only live ones. Archive is the delete, so archived rows stay readable. |
| `include_archived` | boolean | No | Include archived rows alongside live ones. Distinct from `is_archived`, which selects one set or the other: `include_archived=true` is the union. Lists return live rows unless you opt in. |

##### Sorting & expansion

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `sort` | string | No | One field, optionally `-`-prefixed. Every ordering appends a stable internal tiebreak so a row cannot appear on two pages. An unsupported value is `400 invalid_sort`, never a silent fallback. |

#### Response body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `count` | integer | Yes | Total number of rows. |
| `next` | uri | Yes | URL of the next page, or `null`. |
| `previous` | uri | Yes | URL of the previous page, or `null`. |
| `results` | array<Task> | Yes | The rows on this page. See [Task](#plan-board-tasks-list-task). |

#### Error codes

| Status | When |
|--------|------|
| `400` | Validation failed, or a filter, sort or `include` value was not recognised. The response `code` says which. |
| `401` | Missing, expired or malformed credential (`credential_absent`, `credential_expired`, `credential_malformed`). |
| `402` | Plan is not enabled for your organization yet (`plan_upgrade_required`). Expected during the Beta: write to support@dailybot.com. |

#### Example (curl)

```bash
curl -sS "https://api.dailybot.com/v1/plan/tasks/?board=ENG&state=open&owner=me" \
  -H "X-API-KEY: $DAILYBOT_API_KEY"
```

#### Scenario examples

##### CLI

```bash
dailybot plan task list --board ENG --state open --owner me --json
```

#### Notes

- Scope: `tasks:read`.
- Rate limit: 120 reads per minute per actor.
- Works with a login session, a CLI user token, a personal API key, or an agent or organization key. A personal key sees what its person sees; an agent or organization key acts as a system actor and sees organization-visible boards only.

### POST `/v1/plan/tasks/` · Beta

**Create a task**

The key (`ENG-143`) is allocated from the board's counter and never reused, even after archive.

When `owner` is set, that person must already be able to see the board; otherwise the call is refused with `400 participant_cannot_access_board` and nothing is written.

Placement is relative: `after` or `before` names a visible task in the target column (at most one of them); omit both to append at the end. Raw `rank` is never accepted.

- **Auth:** API key (`X-API-KEY`), CLI Bearer (write)
- **Rate limit:** `default`
- **Pagination:** No pagination

#### Headers

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `Idempotency-Key` | string | No | A key you generate for this intent. A replay with the same key and body returns the first response without a second side effect and carries `Idempotency-Replayed: true`. Keys are kept for 24 hours. The same key with a different body is `409 idempotency_key_payload_mismatch`; a repeat while the first call is still running gets `409 idempotency_in_progress` for up to 120 seconds. |
| `X-Dailybot-Agent-Name` | string | No | The name of the agent that executed this write on the person's behalf. Use it on multipart and body-less writes (DELETE, archive, restore); on JSON writes send the body field `agent_name` instead, which wins if both are present. Percent-encode the value as UTF-8. Control characters are stripped; a blank value means no agent. More than 128 characters, or a value that cannot be decoded, is `400 invalid_agent_attribution` (never truncated). An agent-type key, which is not bound to a person, gets `400 invalid_agent_attribution` if it sends it. The stamp never changes a permission answer. See [Agent attribution](/developers/plan/conventions#agent-attribution). |

#### Request body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `board` | uuid | Yes | The board: its uuid or its key (`ENG`). |
| `milestone` | uuid | null | No | Not accepted on create yet (`501 not_implemented`): set the milestone with `PATCH` after creating the task. |
| `title` | string | Yes | The task's title. Max 255 characters. |
| `description` | string | null | No | Free-form description. Max 50000 characters. |
| `priority` | integer | No | 1 urgent, 2 high, 3 medium, 4 low, 5 none. From 1 to 5. |
| `estimate` | integer | null | No | Estimate on the board's scale. |
| `owner` | string | null | No | The owner's user uuid. The person must already be able to see the board (`400 participant_cannot_access_board` otherwise). |
| `start_date` | date | null | No | Planned start date. |
| `due_date` | date | null | No | Due date. |
| `parent_task` | uuid | null | No | The parent task, for a sub-task. One level of nesting only. |
| `label_uuids` | array | No | Label uuids to set on the task. Items: `uuid`. |
| `after` | uuid | null | No | Place the pin just below this pin. |
| `before` | uuid | null | No | Place the pin just above this pin. |
| `version` | integer | No | The version you loaded. A stale value is `409 version_conflict`. |
| `agent_name` | string | No | The name of the agent that executed this write on the person's behalf (max 128 characters, blank means no agent). Takes priority over the `X-Dailybot-Agent-Name` header. See [Agent attribution](/developers/plan/conventions#agent-attribution). |

#### Response body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `(body)` | Task | Yes | A [Task](#plan-board-tasks-list-task) object. |

#### Error codes

| Status | When |
|--------|------|
| `400` | Validation failed, or a filter, sort or `include` value was not recognised. The response `code` says which. `invalid_agent_attribution` means the agent name is invalid. |
| `401` | Missing, expired or malformed credential (`credential_absent`, `credential_expired`, `credential_malformed`). |
| `402` | Plan is not enabled for your organization yet (`plan_upgrade_required`). Expected during the Beta: write to support@dailybot.com. |
| `409` | Conflict. The response `code` says which (for example `version_conflict`). |
| `422` | The request could not be applied (`column_too_large`). |

#### Example (curl)

```bash
curl -sS -X POST "https://api.dailybot.com/v1/plan/tasks/" \
  -H "X-API-KEY: $DAILYBOT_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "board": "00000000-0000-4000-8000-000000000002",
    "title": "Ship the delta feed",
    "priority": 2,
    "due_date": "2026-10-15"
  }'
```

#### Scenario examples

##### CLI

```bash
dailybot plan task create -t "Ship the delta feed" -b 00000000-0000-4000-8000-000000000002 --owner me --priority 2
```

#### Notes

- Scope: `tasks:write`.
- Rate limit: 60 writes per minute per actor.
- Works with a login session, a CLI user token, a personal API key, or an agent or organization key. A personal key sees what its person sees; an agent or organization key acts as a system actor and sees organization-visible boards only.

### POST `/v1/plan/tasks/bulk/` · Beta

**Create, or apply one operation to, up to 100 tasks**

Registered BEFORE the `{task_id}` detail route, or `bulk` parses as an identifier. Atomicity is per item, not per batch: the response reports each item separately and the HTTP status describes whether the batch was accepted, not whether every item succeeded. `Idempotency-Key` is required — a bulk move that half-applies twice is a corrupted board.
The `restore` operation is the batch form of `POST .../tasks/{task_id}/restore/` and obeys the same rules.

- **Auth:** API key (`X-API-KEY`), CLI Bearer (write)
- **Rate limit:** `default`
- **Pagination:** No pagination

#### Query parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `dry_run` | boolean | No | Run the call and roll it back. Answers `{operation, dry_run, reversible, consequence, affects{tasks}, items[{index, task, key, changes{field:{from,to}}}], refused[{index, code, detail}]}`. No `Idempotency-Key` needed. |

#### Headers

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `Idempotency-Key` | string | Yes | Required on bulk: a batch that half-applies twice is a corrupted board. Missing is `400 idempotency_key_required`. |
| `X-Dailybot-Agent-Name` | string | No | The name of the agent that executed this write on the person's behalf. Use it on multipart and body-less writes (DELETE, archive, restore); on JSON writes send the body field `agent_name` instead, which wins if both are present. Percent-encode the value as UTF-8. Control characters are stripped; a blank value means no agent. More than 128 characters, or a value that cannot be decoded, is `400 invalid_agent_attribution` (never truncated). An agent-type key, which is not bound to a person, gets `400 invalid_agent_attribution` if it sends it. The stamp never changes a permission answer. See [Agent attribution](/developers/plan/conventions#agent-attribution). |

#### Request body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `operation` | enum | Yes | The operation to apply. One of `move`, `update`, `archive`, `restore`, `create`, `set_labels`. Aliases: `set_owner`, `set_priority`, `set_due_date`, `set_parent` (→ `update`); `delete` (→ `archive`). |
| `board` | uuid | No | The board. Required when `operation` is `create`. |
| `items` | array (max 100): mutate items {task (uuid or KEY-n, required), state, after, before, owner, priority 1-5, due_date, version, label_uuids (or labels), parent_task}; create items {title (≤512, required), description, state, owner, priority, estimate, start_date, due_date, external_id} | Yes | Up to 100 items. |
| `position` | enum | No | `create` only: where the new tasks land in each column. `start` puts them at the top, in item order; `end` at the bottom. One of `start`, `end`. Default `end`. |
| `agent_name` | string | No | The name of the agent that executed this write on the person's behalf (max 128 characters, blank means no agent). Takes priority over the `X-Dailybot-Agent-Name` header. See [Agent attribution](/developers/plan/conventions#agent-attribution). |

#### BulkResponse object

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `succeeded` | integer | Yes | Items that succeeded. |
| `failed` | integer | Yes | Items that failed. |
| `results` | array | Yes | The rows on this page. Always present: `task`, `status`. Items: `{task: string, status: string, version: integer|null, code: string|null, detail: string|null, extra: object, external_id?: string, key?: string}`. |

#### Response body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `(body)` | BulkResponse | Yes | A [BulkResponse](#plan-tasks-bulk-bulkresponse) object. |

#### Error codes

| Status | When |
|--------|------|
| `400` | Missing `Idempotency-Key` (`idempotency_key_required`), more than 100 items (`too_many_items`), or an invalid payload. `invalid_agent_attribution` means the agent name is invalid. |
| `401` | Missing, expired or malformed credential (`credential_absent`, `credential_expired`, `credential_malformed`). |
| `402` | Plan is not enabled for your organization yet (`plan_upgrade_required`). Expected during the Beta: write to support@dailybot.com. |
| `409` | The same `Idempotency-Key` is still running (`idempotency_in_progress`) or was used with a different body (`idempotency_key_payload_mismatch`). |
| `429` | Rate limit reached. Wait the number of seconds in `Retry-After`. |

#### Example (curl)

```bash
curl -sS -X POST "https://api.dailybot.com/v1/plan/tasks/bulk/" \
  -H "X-API-KEY: $DAILYBOT_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "operation": "create",
    "board": "00000000-0000-4000-8000-000000000002",
    "items": [
      {
        "title": "Write the migration guide",
        "external_id": "row-1"
      },
      {
        "title": "Record the demo",
        "external_id": "row-2"
      }
    ]
  }'
```

#### Scenario examples

##### CLI

```bash
dailybot plan task bulk --operation create --board 00000000-0000-4000-8000-000000000002 -f tasks.json --dry-run
dailybot plan task bulk --operation create --board 00000000-0000-4000-8000-000000000002 -f tasks.json
```

##### Response

```bash
{
  "succeeded": 1,
  "failed": 1,
  "results": [
    {
      "task": "ENG-142",
      "status": "ok",
      "version": 8
    },
    {
      "task": "ENG-9",
      "status": "error",
      "code": "version_conflict",
      "detail": "This task changed since you loaded it.",
      "extra": {
        "current_version": 4
      }
    }
  ]
}
```

#### Notes

- Scope: `tasks:write`.
- Rate limit: 30 bulk calls per minute per actor.
- Works with a login session, a CLI user token, a personal API key, or an agent or organization key. A personal key sees what its person sees; an agent or organization key acts as a system actor and sees organization-visible boards only.

### GET `/v1/plan/tasks/{task_id}/` · Beta

**Retrieve a task by uuid or by KEY-n**

Address the task by uuid or by key. An **archived** task stays readable by anyone who can see its board; no `include_archived` is needed on a direct read. The `ETag` carries the task's version.

- **Auth:** API key (`X-API-KEY`), CLI Bearer (read)
- **Rate limit:** `default`
- **Pagination:** No pagination

#### Path parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `task_id` | string | Yes | A task uuid **or** its key, such as `ENG-142`, including a key retired by a board rename. Resolution is scoped to your organization first, so another organization's key is a 404 identical to a missing one. Numeric ids are never accepted. |

#### Query parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `include` | string | No | Comma-separated embed tokens on task detail. Allowed: `children`, `relations`, `participants`, `attachments`, `comment_count`, `activity`, `comments`.
Each collection embed is the first page of the matching list endpoint (`activity` matches `/tasks/{id}/activity/`; `comments` matches `/tasks/{id}/comments/`).
Unknown tokens return `400 invalid_filter_value`. An empty value (`?include=`) is treated as no embeds (200). Embeds do not change the task `ETag` (version-only). |

#### Headers

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `If-None-Match` | string | No | The ETag from your previous read. A match answers `304`. |

#### Response body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `(body)` | Task | Yes | A [Task](#plan-board-tasks-list-task) object. |

#### Error codes

| Status | When |
|--------|------|
| `304` | Not modified: the `If-None-Match` ETag you sent still matches. |
| `401` | Missing, expired or malformed credential (`credential_absent`, `credential_expired`, `credential_malformed`). |
| `402` | Plan is not enabled for your organization yet (`plan_upgrade_required`). Expected during the Beta: write to support@dailybot.com. |
| `404` | Not found, or not visible to you. Both cases return the same body. |

#### Example (curl)

```bash
curl -sS "https://api.dailybot.com/v1/plan/tasks/ENG-142/?include=relations,participants" \
  -H "X-API-KEY: $DAILYBOT_API_KEY"
```

#### Scenario examples

##### CLI

```bash
dailybot plan task get ENG-142 --json
```

#### Notes

- Scope: `tasks:read`.
- Rate limit: 120 reads per minute per actor.
- Works with a login session, a CLI user token, a personal API key, or an agent or organization key. A personal key sees what its person sees; an agent or organization key acts as a system actor and sees organization-visible boards only.

### PATCH `/v1/plan/tasks/{task_id}/` · Beta

**Update a task**

Send `If-Match` with the version you loaded to detect a lost update. Without it the write is last-write-wins and still returns the new version.
Unknown body fields return `400` (never a silent `200`). `is_archived` is not accepted on PATCH — use `POST …/archive/` or `POST …/restore/`.

- **Auth:** API key (`X-API-KEY`), CLI Bearer (write)
- **Rate limit:** `default`
- **Pagination:** No pagination

#### Path parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `task_id` | string | Yes | A task uuid **or** its key, such as `ENG-142`, including a key retired by a board rename. Resolution is scoped to your organization first, so another organization's key is a 404 identical to a missing one. Numeric ids are never accepted. |

#### Headers

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `If-Match` | string | No | The `version` you loaded, as a quoted validator (or send it as the body field `version`). A stale value is `409 version_conflict` with `extra.current_version`; sending both with different values is `400 version_precondition_ambiguous`. Omitting it is last-write-wins. |
| `Idempotency-Key` | string | No | A key you generate for this intent. A replay with the same key and body returns the first response without a second side effect and carries `Idempotency-Replayed: true`. Keys are kept for 24 hours. The same key with a different body is `409 idempotency_key_payload_mismatch`; a repeat while the first call is still running gets `409 idempotency_in_progress` for up to 120 seconds. |
| `X-Dailybot-Agent-Name` | string | No | The name of the agent that executed this write on the person's behalf. Use it on multipart and body-less writes (DELETE, archive, restore); on JSON writes send the body field `agent_name` instead, which wins if both are present. Percent-encode the value as UTF-8. Control characters are stripped; a blank value means no agent. More than 128 characters, or a value that cannot be decoded, is `400 invalid_agent_attribution` (never truncated). An agent-type key, which is not bound to a person, gets `400 invalid_agent_attribution` if it sends it. The stamp never changes a permission answer. See [Agent attribution](/developers/plan/conventions#agent-attribution). |

#### Request body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `board` | uuid | No | The board. |
| `milestone` | uuid | null | No | The milestone this task counts toward. |
| `title` | string | No | The task's title. Max 255 characters. |
| `description` | string | null | No | Free-form description. Max 50000 characters. |
| `priority` | integer | No | 1 urgent, 2 high, 3 medium, 4 low, 5 none. From 1 to 5. |
| `estimate` | integer | null | No | Estimate on the board's scale. |
| `owner` | string | null | No | The owner's user uuid. The person must already be able to see the board (`400 participant_cannot_access_board` otherwise). |
| `start_date` | date | null | No | Planned start date. |
| `due_date` | date | null | No | Due date. |
| `parent_task` | uuid | null | No | The parent task, for a sub-task. One level of nesting only. |
| `label_uuids` | array | No | Label uuids to set on the task. Items: `uuid`. |
| `after` | uuid | null | No | Place the pin just below this pin. |
| `before` | uuid | null | No | Place the pin just above this pin. |
| `version` | integer | No | The version you loaded. A stale value is `409 version_conflict`. |
| `agent_name` | string | No | The name of the agent that executed this write on the person's behalf (max 128 characters, blank means no agent). Takes priority over the `X-Dailybot-Agent-Name` header. See [Agent attribution](/developers/plan/conventions#agent-attribution). |

#### Response body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `(body)` | Task | Yes | A [Task](#plan-board-tasks-list-task) object. |

#### Error codes

| Status | When |
|--------|------|
| `400` | Validation failed, or a filter, sort or `include` value was not recognised. The response `code` says which. `invalid_agent_attribution` means the agent name is invalid. |
| `401` | Missing, expired or malformed credential (`credential_absent`, `credential_expired`, `credential_malformed`). |
| `402` | Plan is not enabled for your organization yet (`plan_upgrade_required`). Expected during the Beta: write to support@dailybot.com. |
| `404` | Not found, or not visible to you. Both cases return the same body. |
| `409` | The task changed since you loaded it (`version_conflict`); `extra.current_version` carries the new version. |

#### Example (curl)

```bash
curl -sS -X PATCH "https://api.dailybot.com/v1/plan/tasks/ENG-142/" \
  -H "X-API-KEY: $DAILYBOT_API_KEY" \
  -H 'If-Match: "7"' \
  -H "Content-Type: application/json" \
  -d '{
    "owner": "00000000-0000-4000-8000-00000000000c",
    "due_date": "2026-10-22"
  }'
```

#### Scenario examples

##### CLI

```bash
dailybot plan task update ENG-142 --priority 1 --due 2026-10-01
dailybot plan task set-owner ENG-142 me
```

#### Notes

- Scope: `tasks:write`.
- Rate limit: 60 writes per minute per actor.
- Works with a login session, a CLI user token, a personal API key, or an agent or organization key. A personal key sees what its person sees; an agent or organization key acts as a system actor and sees organization-visible boards only.

### DELETE `/v1/plan/tasks/{task_id}/` · Beta

**Archive a task (DELETE alias)**

`DELETE` is an alias for archive — the task and its sub-tasks are archived (`204`). Already-archived tasks return `204` idempotently. Prefer `POST …/archive/` when you need the archived body echoed. Concurrency (`If-Match`) is not applied on this alias; use PATCH for versioned updates before archiving if needed.

- **Auth:** API key (`X-API-KEY`), CLI Bearer (write)
- **Rate limit:** `default`
- **Pagination:** No pagination

#### Path parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `task_id` | string | Yes | A task uuid **or** its key, such as `ENG-142`, including a key retired by a board rename. Resolution is scoped to your organization first, so another organization's key is a 404 identical to a missing one. Numeric ids are never accepted. |

#### Headers

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `X-Dailybot-Agent-Name` | string | No | The name of the agent that executed this write on the person's behalf. Use it on multipart and body-less writes (DELETE, archive, restore); on JSON writes send the body field `agent_name` instead, which wins if both are present. Percent-encode the value as UTF-8. Control characters are stripped; a blank value means no agent. More than 128 characters, or a value that cannot be decoded, is `400 invalid_agent_attribution` (never truncated). An agent-type key, which is not bound to a person, gets `400 invalid_agent_attribution` if it sends it. The stamp never changes a permission answer. See [Agent attribution](/developers/plan/conventions#agent-attribution). |

#### Error codes

| Status | When |
|--------|------|
| `400` | The agent name is invalid (`invalid_agent_attribution`). |
| `401` | Missing, expired or malformed credential (`credential_absent`, `credential_expired`, `credential_malformed`). |
| `402` | Plan is not enabled for your organization yet (`plan_upgrade_required`). Expected during the Beta: write to support@dailybot.com. |
| `404` | Not found, or not visible to you. Both cases return the same body. |

#### Example (curl)

```bash
curl -sS -X DELETE "https://api.dailybot.com/v1/plan/tasks/ENG-142/" \
  -H "X-API-KEY: $DAILYBOT_API_KEY"
```

#### Scenario examples

##### CLI

```bash
dailybot plan task delete ENG-142 --yes   # archives the task
```

#### Notes

- Scope: `tasks:write`.
- Rate limit: 60 writes per minute per actor.
- Works with a login session, a CLI user token, a personal API key, or an agent or organization key. A personal key sees what its person sees; an agent or organization key acts as a system actor and sees organization-visible boards only.

### GET `/v1/plan/tasks/{task_id}/children/` · Beta

**List direct sub-tasks of a task**

Paginated task cards (same shape as the task list / board snapshot). Default order is `created_at` (rank is column-scoped, so children in different states are not sibling-ranked). Pass `?sort=rank` or `?ordering=rank` when all children share a column. Unsupported sort values are `400 invalid_sort` (never silently ignored). Sibling drag uses `POST …/move/` with `after` / `before`. One nesting level only — grandchildren refused on write with `subtask_depth_exceeded`.

- **Auth:** API key (`X-API-KEY`), CLI Bearer (read)
- **Rate limit:** `default`
- **Pagination:** Page-number pagination

#### Path parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `task_id` | string | Yes | A task uuid **or** its key, such as `ENG-142`, including a key retired by a board rename. Resolution is scoped to your organization first, so another organization's key is a 404 identical to a missing one. Numeric ids are never accepted. |

#### Query parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `page` | integer | No | 1-based page number. |
| `page_size` | integer | No | Rows per page. Default 50, maximum 100. Out-of-range values are clamped, never rejected: asking for 500 returns 100. |
| `sort` | string | No | One field, optionally `-`-prefixed. Every ordering appends a stable internal tiebreak so a row cannot appear on two pages. An unsupported value is `400 invalid_sort`, never a silent fallback. |
| `ordering` | string | No | Web alias for `sort` on the children list. Same allow-list and refusal semantics — unsupported values are `400 invalid_sort`, never silently ignored. Do not send both parameters with conflicting values. |

#### Response body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `count` | integer | Yes | Total number of rows. |
| `next` | uri | Yes | URL of the next page, or `null`. |
| `previous` | uri | Yes | URL of the previous page, or `null`. |
| `results` | array<Task> | Yes | The rows on this page. See [Task](#plan-board-tasks-list-task). |

#### Error codes

| Status | When |
|--------|------|
| `401` | Missing, expired or malformed credential (`credential_absent`, `credential_expired`, `credential_malformed`). |
| `402` | Plan is not enabled for your organization yet (`plan_upgrade_required`). Expected during the Beta: write to support@dailybot.com. |
| `404` | Not found, or not visible to you. Both cases return the same body. |

#### Example (curl)

```bash
curl -sS "https://api.dailybot.com/v1/plan/tasks/ENG-142/children/" \
  -H "X-API-KEY: $DAILYBOT_API_KEY"
```

#### Scenario examples

##### CLI

```bash
dailybot plan task children ENG-142
```

#### Notes

- Scope: `tasks:read`.
- Rate limit: 120 reads per minute per actor.
- Works with a login session, a CLI user token, a personal API key, or an agent or organization key. A personal key sees what its person sees; an agent or organization key acts as a system actor and sees organization-visible boards only.

### POST `/v1/plan/tasks/{task_id}/archive/` · Beta

**Archive a task and its sub-tasks**

Archiving nulls the task's rank, so it leaves every board ordering without leaving the table. Relations and participants are kept.

- **Auth:** API key (`X-API-KEY`), CLI Bearer (write)
- **Rate limit:** `default`
- **Pagination:** No pagination

#### Path parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `task_id` | string | Yes | A task uuid **or** its key, such as `ENG-142`, including a key retired by a board rename. Resolution is scoped to your organization first, so another organization's key is a 404 identical to a missing one. Numeric ids are never accepted. |

#### Query parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `dry_run` | boolean | No | Preview the consequence without performing it. The response has the same shape, `{operation, dry_run, reversible, restore_path, consequence, affects}`, but nothing is written and no event is emitted. Show `consequence` to a person before acting. |

#### Headers

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `Idempotency-Key` | string | No | A key you generate for this intent. A replay with the same key and body returns the first response without a second side effect and carries `Idempotency-Replayed: true`. Keys are kept for 24 hours. The same key with a different body is `409 idempotency_key_payload_mismatch`; a repeat while the first call is still running gets `409 idempotency_in_progress` for up to 120 seconds. |
| `X-Dailybot-Agent-Name` | string | No | The name of the agent that executed this write on the person's behalf. Use it on multipart and body-less writes (DELETE, archive, restore); on JSON writes send the body field `agent_name` instead, which wins if both are present. Percent-encode the value as UTF-8. Control characters are stripped; a blank value means no agent. More than 128 characters, or a value that cannot be decoded, is `400 invalid_agent_attribution` (never truncated). An agent-type key, which is not bound to a person, gets `400 invalid_agent_attribution` if it sends it. The stamp never changes a permission answer. See [Agent attribution](/developers/plan/conventions#agent-attribution). |

#### DryRunPreview object

What the call answers with `?dry_run=true`: the consequence, without performing it. Nothing is written and no event is emitted.


| Name | Type | Required | Description |
|------|------|----------|-------------|
| `operation` | string | Yes | The operation that would run. |
| `dry_run` | boolean | Yes | Always `true`. |
| `reversible` | boolean | Yes | Whether the operation can be undone. |
| `restore_path` | string | null | Yes | The path that would undo it, or `null` when there is none. |
| `consequence` | string | Yes | A sentence to show a person before acting. It states the cascade rather than summarising it. |
| `affects` | object | Yes | What the operation would touch, as counts (integers) by kind. |
| `would_refuse` | boolean | No | Workflow state archive only: `true` when the real call would be refused. |
| `refusal_code` | string | No | Workflow state archive only: the error code the real call would answer with. |

#### Response body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `(body)` | Task | DryRunPreview | Yes | A [Task](#plan-board-tasks-list-task) object. With `?dry_run=true`, a [DryRunPreview](#plan-task-archive-dryrunpreview) object instead. |

#### Error codes

| Status | When |
|--------|------|
| `400` | The agent name is invalid (`invalid_agent_attribution`). |
| `401` | Missing, expired or malformed credential (`credential_absent`, `credential_expired`, `credential_malformed`). |
| `402` | Plan is not enabled for your organization yet (`plan_upgrade_required`). Expected during the Beta: write to support@dailybot.com. |
| `404` | Not found, or not visible to you. Both cases return the same body. |
| `409` | Conflict. The response `code` says which (for example `version_conflict`). |

#### Example (curl)

```bash
curl -sS -X POST "https://api.dailybot.com/v1/plan/tasks/ENG-142/archive/" \
  -H "X-API-KEY: $DAILYBOT_API_KEY"
```

#### Scenario examples

##### CLI

```bash
dailybot plan task archive ENG-142 --dry-run
dailybot plan task archive ENG-142 --yes
```

#### Notes

- Scope: `tasks:write`.
- Rate limit: 60 writes per minute per actor.
- Works with a login session, a CLI user token, a personal API key, or an agent or organization key. A personal key sees what its person sees; an agent or organization key acts as a system actor and sees organization-visible boards only.

### POST `/v1/plan/tasks/{task_id}/duplicate/` · Beta

**Duplicate a task on the same board**

Creates a new task in the same column. Default `include` copies `title`, `description`, and `labels`. Emits `task.created`.

- **Auth:** API key (`X-API-KEY`), CLI Bearer (write)
- **Rate limit:** `default`
- **Pagination:** No pagination

#### Path parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `task_id` | string | Yes | A task uuid **or** its key, such as `ENG-142`, including a key retired by a board rename. Resolution is scoped to your organization first, so another organization's key is a 404 identical to a missing one. Numeric ids are never accepted. |

#### Headers

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `X-Dailybot-Agent-Name` | string | No | The name of the agent that executed this write on the person's behalf. Use it on multipart and body-less writes (DELETE, archive, restore); on JSON writes send the body field `agent_name` instead, which wins if both are present. Percent-encode the value as UTF-8. Control characters are stripped; a blank value means no agent. More than 128 characters, or a value that cannot be decoded, is `400 invalid_agent_attribution` (never truncated). An agent-type key, which is not bound to a person, gets `400 invalid_agent_attribution` if it sends it. The stamp never changes a permission answer. See [Agent attribution](/developers/plan/conventions#agent-attribution). |

#### Request body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `include` | array | No | What to copy. Default: `title`, `description`, `labels`. Items: `enum title|description|labels|priority|estimate|owner|start_date|due_date`. |
| `agent_name` | string | No | The name of the agent that executed this write on the person's behalf (max 128 characters, blank means no agent). Takes priority over the `X-Dailybot-Agent-Name` header. See [Agent attribution](/developers/plan/conventions#agent-attribution). |

#### Response body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `(body)` | Task | Yes | A [Task](#plan-board-tasks-list-task) object. |

#### Error codes

| Status | When |
|--------|------|
| `400` | The agent name is invalid (`invalid_agent_attribution`). |
| `401` | Missing, expired or malformed credential (`credential_absent`, `credential_expired`, `credential_malformed`). |
| `402` | Plan is not enabled for your organization yet (`plan_upgrade_required`). Expected during the Beta: write to support@dailybot.com. |
| `403` | The task is archived (`task_delete_forbidden`): restore it before duplicating it. |
| `404` | Not found, or not visible to you. Both cases return the same body. |

#### Example (curl)

```bash
curl -sS -X POST "https://api.dailybot.com/v1/plan/tasks/ENG-142/duplicate/" \
  -H "X-API-KEY: $DAILYBOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "include": [
      "title",
      "description",
      "labels"
    ]
  }'
```

#### Scenario examples

##### CLI

```bash
dailybot plan task duplicate ENG-142 --include title --include description
```

#### Notes

- Scope: `tasks:write`.
- Rate limit: 60 writes per minute per actor.
- Works with a login session, a CLI user token, a personal API key, or an agent or organization key. A personal key sees what its person sees; an agent or organization key acts as a system actor and sees organization-visible boards only.

### POST `/v1/plan/tasks/{task_id}/move-board/` · Beta

**Move a task to another board**

Body requires `board` (target board uuid). Target column resolution: explicit `state`, or `state_map` from source column uuid → target column uuid, or same `category` on the target board. Emits `task.moved` (with `from_board_uuid` when crossing boards). Invalid mappings return `400 move_board_state_invalid`.

- **Auth:** API key (`X-API-KEY`), CLI Bearer (write)
- **Rate limit:** `default`
- **Pagination:** No pagination

#### Path parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `task_id` | string | Yes | A task uuid **or** its key, such as `ENG-142`, including a key retired by a board rename. Resolution is scoped to your organization first, so another organization's key is a 404 identical to a missing one. Numeric ids are never accepted. |

#### Headers

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `If-Match` | string | No | The `version` you loaded, as a quoted validator (or send it as the body field `version`). A stale value is `409 version_conflict` with `extra.current_version`; sending both with different values is `400 version_precondition_ambiguous`. Omitting it is last-write-wins. |
| `X-Dailybot-Agent-Name` | string | No | The name of the agent that executed this write on the person's behalf. Use it on multipart and body-less writes (DELETE, archive, restore); on JSON writes send the body field `agent_name` instead, which wins if both are present. Percent-encode the value as UTF-8. Control characters are stripped; a blank value means no agent. More than 128 characters, or a value that cannot be decoded, is `400 invalid_agent_attribution` (never truncated). An agent-type key, which is not bound to a person, gets `400 invalid_agent_attribution` if it sends it. The stamp never changes a permission answer. See [Agent attribution](/developers/plan/conventions#agent-attribution). |

#### Request body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `board` | uuid | Yes | The board. |
| `state` | uuid | No | The task's workflow state (its column). |
| `state_map` | object | No | Source column uuid → target column uuid. |
| `version` | integer | No | The version you loaded. A stale value is `409 version_conflict`. |
| `agent_name` | string | No | The name of the agent that executed this write on the person's behalf (max 128 characters, blank means no agent). Takes priority over the `X-Dailybot-Agent-Name` header. See [Agent attribution](/developers/plan/conventions#agent-attribution). |

#### Response body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `(body)` | Task | Yes | A [Task](#plan-board-tasks-list-task) object. |

#### Error codes

| Status | When |
|--------|------|
| `400` | Validation failed, or a filter, sort or `include` value was not recognised. The response `code` says which. `invalid_agent_attribution` means the agent name is invalid. |
| `401` | Missing, expired or malformed credential (`credential_absent`, `credential_expired`, `credential_malformed`). |
| `402` | Plan is not enabled for your organization yet (`plan_upgrade_required`). Expected during the Beta: write to support@dailybot.com. |
| `404` | Not found, or not visible to you. Both cases return the same body. |
| `409` | The task changed since you loaded it (`version_conflict`). |

#### Example (curl)

```bash
curl -sS -X POST "https://api.dailybot.com/v1/plan/tasks/ENG-142/move-board/" \
  -H "X-API-KEY: $DAILYBOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "board": "00000000-0000-4000-8000-000000000012"
  }'
```

#### Scenario examples

##### CLI

```bash
dailybot plan task move ENG-142 --board 00000000-0000-4000-8000-000000000012
```

#### Notes

- Scope: `tasks:write`.
- Rate limit: 60 writes per minute per actor.
- Works with a login session, a CLI user token, a personal API key, or an agent or organization key. A personal key sees what its person sees; an agent or organization key acts as a system actor and sees organization-visible boards only.

### POST `/v1/plan/tasks/{task_id}/move/` · Beta

**Move a task to a state and a position, relatively**

The only way to change a task's state. The position is a neighbour, not a number, so two people dragging the same card at once both produce a valid order. At most one of `after` / `before` may be set; both null appends to the end of the column. One write, one `task.moved` event. Send `If-Match` (or `version`) to refuse a stale move.

- **Auth:** API key (`X-API-KEY`), CLI Bearer (write)
- **Rate limit:** `default`
- **Pagination:** No pagination

#### Path parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `task_id` | string | Yes | A task uuid **or** its key, such as `ENG-142`, including a key retired by a board rename. Resolution is scoped to your organization first, so another organization's key is a 404 identical to a missing one. Numeric ids are never accepted. |

#### Headers

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `If-Match` | string | No | The `version` you loaded, as a quoted validator (or send it as the body field `version`). A stale value is `409 version_conflict` with `extra.current_version`; sending both with different values is `400 version_precondition_ambiguous`. Omitting it is last-write-wins. |
| `Idempotency-Key` | string | No | A key you generate for this intent. A replay with the same key and body returns the first response without a second side effect and carries `Idempotency-Replayed: true`. Keys are kept for 24 hours. The same key with a different body is `409 idempotency_key_payload_mismatch`; a repeat while the first call is still running gets `409 idempotency_in_progress` for up to 120 seconds. |
| `X-Dailybot-Agent-Name` | string | No | The name of the agent that executed this write on the person's behalf. Use it on multipart and body-less writes (DELETE, archive, restore); on JSON writes send the body field `agent_name` instead, which wins if both are present. Percent-encode the value as UTF-8. Control characters are stripped; a blank value means no agent. More than 128 characters, or a value that cannot be decoded, is `400 invalid_agent_attribution` (never truncated). An agent-type key, which is not bound to a person, gets `400 invalid_agent_attribution` if it sends it. The stamp never changes a permission answer. See [Agent attribution](/developers/plan/conventions#agent-attribution). |

#### Request body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `state` | uuid | Yes | The task's workflow state (its column). |
| `board` | uuid | null | No | The board. |
| `after` | uuid | null | No | Place the pin just below this pin. |
| `before` | uuid | null | No | Place the pin just above this pin. |
| `agent_name` | string | No | The name of the agent that executed this write on the person's behalf (max 128 characters, blank means no agent). Takes priority over the `X-Dailybot-Agent-Name` header. See [Agent attribution](/developers/plan/conventions#agent-attribution). |

#### Response body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `(body)` | Task | Yes | A [Task](#plan-board-tasks-list-task) object. |

#### Error codes

| Status | When |
|--------|------|
| `400` | Validation failed, or a filter, sort or `include` value was not recognised. The response `code` says which. `invalid_agent_attribution` means the agent name is invalid. |
| `401` | Missing, expired or malformed credential (`credential_absent`, `credential_expired`, `credential_malformed`). |
| `402` | Plan is not enabled for your organization yet (`plan_upgrade_required`). Expected during the Beta: write to support@dailybot.com. |
| `404` | Not found, or not visible to you. Both cases return the same body. |
| `409` | The task changed since you loaded it (`version_conflict`), or a named neighbour moved away (`rank_neighbor_missing`). The response names the column's current head and tail so you can retry. |
| `422` | The request could not be applied (`column_too_large`). |

#### Example (curl)

```bash
curl -sS -X POST "https://api.dailybot.com/v1/plan/tasks/ENG-142/move/" \
  -H "X-API-KEY: $DAILYBOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "state": "00000000-0000-4000-8000-000000000004",
    "after": null,
    "before": null
  }'
```

#### Scenario examples

##### CLI

```bash
dailybot plan task move ENG-142 --state done
```

#### Notes

- Scope: `tasks:write`.
- Rate limit: 60 writes per minute per actor.
- Works with a login session, a CLI user token, a personal API key, or an agent or organization key. A personal key sees what its person sees; an agent or organization key acts as a system actor and sees organization-visible boards only.

### GET `/v1/plan/tasks/{task_id}/relations/` · Beta

**A task's relations, both directions**

`blocked_by` is not stored — it is the inverse read of `blocks`, so there is exactly one row per fact and the two directions cannot disagree. The `direction` field tells you which side you are on.

- **Auth:** API key (`X-API-KEY`), CLI Bearer (read)
- **Rate limit:** `default`
- **Pagination:** Page-number pagination

#### Path parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `task_id` | string | Yes | A task uuid **or** its key, such as `ENG-142`, including a key retired by a board rename. Resolution is scoped to your organization first, so another organization's key is a 404 identical to a missing one. Numeric ids are never accepted. |

#### TaskRelation object

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `uuid` | uuid | Yes | Stable public identifier. |
| `relation_type` | enum | Yes | `blocks`, `relates_to` or `duplicates`. New types may be added: ignore ones you do not recognise. One of `blocks`, `relates_to`, `duplicates`. |
| `direction` | enum | null | Yes | `outgoing` when this task is the source, `incoming` when it is the target. One of `outgoing`, `incoming`. |
| `other_task` | object | Yes | The task on the other side. Shape: `{uuid, key, title, state_category}`. |
| `created_at` | date-time | No | When the row was created. |

#### Response body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `count` | integer | Yes | Total number of rows. |
| `next` | uri | Yes | URL of the next page, or `null`. |
| `previous` | uri | Yes | URL of the previous page, or `null`. |
| `results` | array<TaskRelation> | Yes | The rows on this page. See [TaskRelation](#plan-task-relations-list-taskrelation). |

#### Error codes

| Status | When |
|--------|------|
| `401` | Missing, expired or malformed credential (`credential_absent`, `credential_expired`, `credential_malformed`). |
| `402` | Plan is not enabled for your organization yet (`plan_upgrade_required`). Expected during the Beta: write to support@dailybot.com. |
| `404` | Not found, or not visible to you. Both cases return the same body. |

#### Example (curl)

```bash
curl -sS "https://api.dailybot.com/v1/plan/tasks/ENG-142/relations/" \
  -H "X-API-KEY: $DAILYBOT_API_KEY"
```

#### Scenario examples

##### CLI

```bash
dailybot plan task relations ENG-142 --json
```

##### Response

```bash
{
  "count": 1,
  "next": null,
  "previous": null,
  "results": [
    {
      "uuid": "00000000-0000-4000-8000-00000000000a",
      "relation_type": "blocks",
      "direction": "outgoing",
      "other_task": {},
      "created_at": "2026-09-25T10:14:02Z"
    }
  ]
}
```

#### Notes

- Scope: `tasks:read`.
- Rate limit: 120 reads per minute per actor.
- Works with a login session, a CLI user token, a personal API key, or an agent or organization key. A personal key sees what its person sees; an agent or organization key acts as a system actor and sees organization-visible boards only.

### POST `/v1/plan/tasks/{task_id}/relations/` · Beta

**Link two tasks**

Links this task to another one. Send `relation_type` (`blocks`, `relates_to` or `duplicates`) and `target_task`, a task uuid or a key like `ENG-142`; a task you cannot see is `404`. `kind` and `target` are deprecated aliases of those two fields: sending an alias and its field with different values is `400`. A link that already exists or would create a cycle is `409`.

- **Auth:** API key (`X-API-KEY`), CLI Bearer (write)
- **Rate limit:** `default`
- **Pagination:** No pagination

#### Path parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `task_id` | string | Yes | A task uuid **or** its key, such as `ENG-142`, including a key retired by a board rename. Resolution is scoped to your organization first, so another organization's key is a 404 identical to a missing one. Numeric ids are never accepted. |

#### Headers

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `Idempotency-Key` | string | No | A key you generate for this intent. A replay with the same key and body returns the first response without a second side effect and carries `Idempotency-Replayed: true`. Keys are kept for 24 hours. The same key with a different body is `409 idempotency_key_payload_mismatch`; a repeat while the first call is still running gets `409 idempotency_in_progress` for up to 120 seconds. |
| `X-Dailybot-Agent-Name` | string | No | The name of the agent that executed this write on the person's behalf. Use it on multipart and body-less writes (DELETE, archive, restore); on JSON writes send the body field `agent_name` instead, which wins if both are present. Percent-encode the value as UTF-8. Control characters are stripped; a blank value means no agent. More than 128 characters, or a value that cannot be decoded, is `400 invalid_agent_attribution` (never truncated). An agent-type key, which is not bound to a person, gets `400 invalid_agent_attribution` if it sends it. The stamp never changes a permission answer. See [Agent attribution](/developers/plan/conventions#agent-attribution). |

#### Request body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `relation_type` | enum | Yes | `blocks`, `relates_to` or `duplicates`. New types may be added: ignore ones you do not recognise. Required, or its deprecated alias `kind`. One of `blocks`, `relates_to`, `duplicates`. |
| `target_task` | string | Yes | The other task: its uuid or a key like `ENG-142`. A task you cannot see is `404`. Required, or its deprecated alias `target`. |
| `kind` | enum | No | Deprecated alias of `relation_type`, kept for older clients. Send `relation_type` instead; both with different values is `400`. One of `blocks`, `relates_to`, `duplicates`. |
| `target` | string | No | Deprecated alias of `target_task`, kept for older clients. Send `target_task` instead; both with different values is `400`. |
| `agent_name` | string | No | The name of the agent that executed this write on the person's behalf (max 128 characters, blank means no agent). Takes priority over the `X-Dailybot-Agent-Name` header. See [Agent attribution](/developers/plan/conventions#agent-attribution). |

#### Response body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `(body)` | TaskRelation | Yes | A [TaskRelation](#plan-task-relations-list-taskrelation) object. |

#### Error codes

| Status | When |
|--------|------|
| `400` | Missing type or target, an alias that disagrees with its field, or an invalid value. `invalid_agent_attribution` means the agent name is invalid. |
| `401` | Missing, expired or malformed credential (`credential_absent`, `credential_expired`, `credential_malformed`). |
| `402` | Plan is not enabled for your organization yet (`plan_upgrade_required`). Expected during the Beta: write to support@dailybot.com. |
| `404` | Not found, or not visible to you. Both cases return the same body. |
| `409` | The link already exists (`relation_exists`) or would create a cycle (`relation_cycle`). |

#### Example (curl)

```bash
curl -sS -X POST "https://api.dailybot.com/v1/plan/tasks/ENG-142/relations/" \
  -H "X-API-KEY: $DAILYBOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "relation_type": "blocks",
    "target_task": "ENG-150"
  }'
```

#### Scenario examples

##### CLI

```bash
dailybot plan task link ENG-142 ENG-150 --type blocks
```

#### Notes

- Scope: `tasks:write`.
- Rate limit: 60 writes per minute per actor.
- Works with a login session, a CLI user token, a personal API key, or an agent or organization key. A personal key sees what its person sees; an agent or organization key acts as a system actor and sees organization-visible boards only.

### DELETE `/v1/plan/tasks/{task_id}/relations/{relation_id}/` · Beta

**Unlink two tasks**

Emits `task.unrelated` on the task event stream (not `relation_removed`). Activity enrichment maps it to `changes[{field: related, from: …, to: null}]`.

- **Auth:** API key (`X-API-KEY`), CLI Bearer (write)
- **Rate limit:** `default`
- **Pagination:** No pagination

#### Path parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `task_id` | string | Yes | A task uuid **or** its key, such as `ENG-142`, including a key retired by a board rename. Resolution is scoped to your organization first, so another organization's key is a 404 identical to a missing one. Numeric ids are never accepted. |
| `relation_id` | string | Yes | The relation's uuid. |

#### Headers

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `X-Dailybot-Agent-Name` | string | No | The name of the agent that executed this write on the person's behalf. Use it on multipart and body-less writes (DELETE, archive, restore); on JSON writes send the body field `agent_name` instead, which wins if both are present. Percent-encode the value as UTF-8. Control characters are stripped; a blank value means no agent. More than 128 characters, or a value that cannot be decoded, is `400 invalid_agent_attribution` (never truncated). An agent-type key, which is not bound to a person, gets `400 invalid_agent_attribution` if it sends it. The stamp never changes a permission answer. See [Agent attribution](/developers/plan/conventions#agent-attribution). |

#### Error codes

| Status | When |
|--------|------|
| `400` | The agent name is invalid (`invalid_agent_attribution`). |
| `401` | Missing, expired or malformed credential (`credential_absent`, `credential_expired`, `credential_malformed`). |
| `402` | Plan is not enabled for your organization yet (`plan_upgrade_required`). Expected during the Beta: write to support@dailybot.com. |
| `404` | Not found, or not visible to you. Both cases return the same body. |

#### Example (curl)

```bash
curl -sS -X DELETE "https://api.dailybot.com/v1/plan/tasks/ENG-142/relations/00000000-0000-4000-8000-00000000000a/" \
  -H "X-API-KEY: $DAILYBOT_API_KEY"
```

#### Scenario examples

##### CLI

```bash
dailybot plan task unlink ENG-142 00000000-0000-4000-8000-00000000000a --yes
```

#### Notes

- Scope: `tasks:write`.
- Rate limit: 60 writes per minute per actor.
- Works with a login session, a CLI user token, a personal API key, or an agent or organization key. A personal key sees what its person sees; an agent or organization key acts as a system actor and sees organization-visible boards only.

### POST `/v1/plan/tasks/{task_id}/labels/batch/` · Beta

**Attach, detach or replace a task's labels**

Labels are the organization-wide taxonomy shared with forms and check-ins; there is no plan-only label vocabulary. At most 50 labels per task.

- **Auth:** API key (`X-API-KEY`), CLI Bearer (write)
- **Rate limit:** `default`
- **Pagination:** No pagination

#### Path parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `task_id` | string | Yes | A task uuid **or** its key, such as `ENG-142`, including a key retired by a board rename. Resolution is scoped to your organization first, so another organization's key is a 404 identical to a missing one. Numeric ids are never accepted. |

#### Headers

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `Idempotency-Key` | string | No | A key you generate for this intent. A replay with the same key and body returns the first response without a second side effect and carries `Idempotency-Replayed: true`. Keys are kept for 24 hours. The same key with a different body is `409 idempotency_key_payload_mismatch`; a repeat while the first call is still running gets `409 idempotency_in_progress` for up to 120 seconds. |
| `X-Dailybot-Agent-Name` | string | No | The name of the agent that executed this write on the person's behalf. Use it on multipart and body-less writes (DELETE, archive, restore); on JSON writes send the body field `agent_name` instead, which wins if both are present. Percent-encode the value as UTF-8. Control characters are stripped; a blank value means no agent. More than 128 characters, or a value that cannot be decoded, is `400 invalid_agent_attribution` (never truncated). An agent-type key, which is not bound to a person, gets `400 invalid_agent_attribution` if it sends it. The stamp never changes a permission answer. See [Agent attribution](/developers/plan/conventions#agent-attribution). |

#### Request body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `mode` | enum | Yes | `add`, `remove` or `replace`. One of `add`, `remove`, `replace`. |
| `label_uuids` | array | Yes | Label uuids to set on the task. Items: `uuid`. |
| `agent_name` | string | No | The name of the agent that executed this write on the person's behalf (max 128 characters, blank means no agent). Takes priority over the `X-Dailybot-Agent-Name` header. See [Agent attribution](/developers/plan/conventions#agent-attribution). |

#### Response body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `labels` | array<Label> | Yes | Organization labels on the task. |

#### Error codes

| Status | When |
|--------|------|
| `400` | Validation failed, or a filter, sort or `include` value was not recognised. The response `code` says which. `invalid_agent_attribution` means the agent name is invalid. |
| `401` | Missing, expired or malformed credential (`credential_absent`, `credential_expired`, `credential_malformed`). |
| `402` | Plan is not enabled for your organization yet (`plan_upgrade_required`). Expected during the Beta: write to support@dailybot.com. |
| `404` | Not found, or not visible to you. Both cases return the same body. |
| `429` | Rate limit reached. Wait the number of seconds in `Retry-After`. |

#### Example (curl)

```bash
curl -sS -X POST "https://api.dailybot.com/v1/plan/tasks/ENG-142/labels/batch/" \
  -H "X-API-KEY: $DAILYBOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "mode": "add",
    "label_uuids": [
      "00000000-0000-4000-8000-00000000000b"
    ]
  }'
```

#### Scenario examples

##### CLI

```bash
dailybot plan task labels ENG-142 --mode add --label 00000000-0000-4000-8000-00000000000b
```

##### Response

```bash
{
  "labels": [
    {
      "uuid": "00000000-0000-4000-8000-00000000000b",
      "name": "backend",
      "color": "#2563eb"
    }
  ]
}
```

#### Notes

- Scope: `tasks:write`.
- Rate limit: 60 writes per minute per actor.
- Works with a login session, a CLI user token, a personal API key, or an agent or organization key. A personal key sees what its person sees; an agent or organization key acts as a system actor and sees organization-visible boards only.

### POST `/v1/plan/tasks/{task_id}/subscription/` · Beta

**Subscribe to task notifications (watcher role)**

The only way to set the task's `subscribed` field (sending `subscribed` in a task PATCH is `400`). Returns `{"subscribed": true}`, so no re-read is needed.

- **Auth:** CLI Bearer (write)
- **Rate limit:** `default`
- **Pagination:** No pagination

#### Path parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `task_id` | string | Yes | A task uuid **or** its key, such as `ENG-142`, including a key retired by a board rename. Resolution is scoped to your organization first, so another organization's key is a 404 identical to a missing one. Numeric ids are never accepted. |

#### Headers

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `X-Dailybot-Agent-Name` | string | No | The name of the agent that executed this write on the person's behalf. Use it on multipart and body-less writes (DELETE, archive, restore); on JSON writes send the body field `agent_name` instead, which wins if both are present. Percent-encode the value as UTF-8. Control characters are stripped; a blank value means no agent. More than 128 characters, or a value that cannot be decoded, is `400 invalid_agent_attribution` (never truncated). An agent-type key, which is not bound to a person, gets `400 invalid_agent_attribution` if it sends it. The stamp never changes a permission answer. See [Agent attribution](/developers/plan/conventions#agent-attribution). |

#### Response body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `subscribed` | boolean | Yes | Whether you watch this task. |

#### Error codes

| Status | When |
|--------|------|
| `400` | The agent name is invalid (`invalid_agent_attribution`). |
| `401` | Missing, expired or malformed credential (`credential_absent`, `credential_expired`, `credential_malformed`). |
| `402` | Plan is not enabled for your organization yet (`plan_upgrade_required`). Expected during the Beta: write to support@dailybot.com. |
| `404` | Not found, or not visible to you. Both cases return the same body. |

#### Example (curl)

```bash
curl -sS -X POST "https://api.dailybot.com/v1/plan/tasks/ENG-142/subscription/" \
  -H "Authorization: Bearer $DAILYBOT_TOKEN"
```

#### Scenario examples

##### CLI

```bash
dailybot plan task watch ENG-142
```

##### Response

```bash
{
  "subscribed": true
}
```

#### Notes

- Scope: `tasks:write`.
- Rate limit: 60 writes per minute per actor.
- Needs a person: call it with a login session, a CLI user token or a personal API key. An agent or organization key gets `403 insufficient_scope`.

### DELETE `/v1/plan/tasks/{task_id}/subscription/` · Beta

**Remove a watcher subscription**

Clears the caller's watcher subscription. Returns 204 (empty body). Re-GET the task for `subscribed: false`, or update client state locally.

- **Auth:** CLI Bearer (write)
- **Rate limit:** `default`
- **Pagination:** No pagination

#### Path parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `task_id` | string | Yes | A task uuid **or** its key, such as `ENG-142`, including a key retired by a board rename. Resolution is scoped to your organization first, so another organization's key is a 404 identical to a missing one. Numeric ids are never accepted. |

#### Headers

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `X-Dailybot-Agent-Name` | string | No | The name of the agent that executed this write on the person's behalf. Use it on multipart and body-less writes (DELETE, archive, restore); on JSON writes send the body field `agent_name` instead, which wins if both are present. Percent-encode the value as UTF-8. Control characters are stripped; a blank value means no agent. More than 128 characters, or a value that cannot be decoded, is `400 invalid_agent_attribution` (never truncated). An agent-type key, which is not bound to a person, gets `400 invalid_agent_attribution` if it sends it. The stamp never changes a permission answer. See [Agent attribution](/developers/plan/conventions#agent-attribution). |

#### Error codes

| Status | When |
|--------|------|
| `400` | The agent name is invalid (`invalid_agent_attribution`). |
| `401` | Missing, expired or malformed credential (`credential_absent`, `credential_expired`, `credential_malformed`). |
| `402` | Plan is not enabled for your organization yet (`plan_upgrade_required`). Expected during the Beta: write to support@dailybot.com. |
| `404` | Not found, or not visible to you. Both cases return the same body. |

#### Example (curl)

```bash
curl -sS -X DELETE "https://api.dailybot.com/v1/plan/tasks/ENG-142/subscription/" \
  -H "Authorization: Bearer $DAILYBOT_TOKEN"
```

#### Scenario examples

##### CLI

```bash
dailybot plan task unwatch ENG-142
```

#### Notes

- Scope: `tasks:write`.
- Rate limit: 60 writes per minute per actor.
- Needs a person: call it with a login session, a CLI user token or a personal API key. An agent or organization key gets `403 insufficient_scope`.

### POST `/v1/plan/tasks/{task_id}/restore/` · Beta

**Restore an archived task**

The mirror of archive: same scope, same credentials, same idempotency. The task returns at the end of its column, because its old neighbours are gone. Restoring a live task is a no-op `200`.

- **Auth:** API key (`X-API-KEY`), CLI Bearer (write)
- **Rate limit:** `default`
- **Pagination:** No pagination

#### Path parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `task_id` | string | Yes | A task uuid **or** its key, such as `ENG-142`, including a key retired by a board rename. Resolution is scoped to your organization first, so another organization's key is a 404 identical to a missing one. Numeric ids are never accepted. |

#### Headers

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `Idempotency-Key` | string | No | A key you generate for this intent. A replay with the same key and body returns the first response without a second side effect and carries `Idempotency-Replayed: true`. Keys are kept for 24 hours. The same key with a different body is `409 idempotency_key_payload_mismatch`; a repeat while the first call is still running gets `409 idempotency_in_progress` for up to 120 seconds. |
| `X-Dailybot-Agent-Name` | string | No | The name of the agent that executed this write on the person's behalf. Use it on multipart and body-less writes (DELETE, archive, restore); on JSON writes send the body field `agent_name` instead, which wins if both are present. Percent-encode the value as UTF-8. Control characters are stripped; a blank value means no agent. More than 128 characters, or a value that cannot be decoded, is `400 invalid_agent_attribution` (never truncated). An agent-type key, which is not bound to a person, gets `400 invalid_agent_attribution` if it sends it. The stamp never changes a permission answer. See [Agent attribution](/developers/plan/conventions#agent-attribution). |

#### Response body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `(body)` | Task | Yes | A [Task](#plan-board-tasks-list-task) object. |

#### Error codes

| Status | When |
|--------|------|
| `400` | The agent name is invalid (`invalid_agent_attribution`). |
| `401` | Missing, expired or malformed credential (`credential_absent`, `credential_expired`, `credential_malformed`). |
| `402` | Plan is not enabled for your organization yet (`plan_upgrade_required`). Expected during the Beta: write to support@dailybot.com. |
| `404` | Not found, or not visible to you. Both cases return the same body. |
| `409` | The task's board or state was archived meanwhile (`state_in_use`). The response names the state so you can pick a target. |

#### Example (curl)

```bash
curl -sS -X POST "https://api.dailybot.com/v1/plan/tasks/ENG-142/restore/" \
  -H "X-API-KEY: $DAILYBOT_API_KEY"
```

#### Scenario examples

##### CLI

```bash
dailybot plan task restore ENG-142
```

#### Notes

- Scope: `tasks:write`.
- Rate limit: 60 writes per minute per actor.
- Works with a login session, a CLI user token, a personal API key, or an agent or organization key. A personal key sees what its person sees; an agent or organization key acts as a system actor and sees organization-visible boards only.

### GET `/v1/plan/tasks/{task_id}/participants/` · Beta

**Who is on this card**

Participants and watchers, oldest first — the order the card's people strip renders. Visible to anyone who can see the task. Participation is not an access lever: this list never widens what its members can see.

- **Auth:** CLI Bearer (read)
- **Rate limit:** `default`
- **Pagination:** Page-number pagination

#### Path parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `task_id` | string | Yes | A task uuid **or** its key, such as `ENG-142`, including a key retired by a board rename. Resolution is scoped to your organization first, so another organization's key is a 404 identical to a missing one. Numeric ids are never accepted. |

#### Query parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `page` | integer | No | 1-based page number. |
| `page_size` | integer | No | Rows per page. Default 50, maximum 100. Out-of-range values are clamped, never rejected: asking for 500 returns 100. |

#### TaskParticipant object

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `member` | ActorRef | Yes | The person. See [ActorRef](#plan-board-tasks-list-actorref). |
| `role` | enum | Yes | Participant role. One of `participant`, `watcher`. |
| `source` | enum | Yes | How the person came to be on the card. One of `manual`, `creator`, `owner`, `commented`, `mentioned`, `sync`. |
| `is_muted` | boolean | Yes | Stay on the card without notifications. |
| `added_by` | ActorRef | null | No | Who added the person. See [ActorRef](#plan-board-tasks-list-actorref). |
| `created_at` | date-time | Yes | When the row was created. |

#### Response body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `count` | integer | Yes | Total number of rows. |
| `next` | uri | Yes | URL of the next page, or `null`. |
| `previous` | uri | Yes | URL of the previous page, or `null`. |
| `results` | array<TaskParticipant> | Yes | The rows on this page. See [TaskParticipant](#plan-task-participants-list-taskparticipant). |

#### Error codes

| Status | When |
|--------|------|
| `401` | Missing, expired or malformed credential (`credential_absent`, `credential_expired`, `credential_malformed`). |
| `402` | Plan is not enabled for your organization yet (`plan_upgrade_required`). Expected during the Beta: write to support@dailybot.com. |
| `404` | Not found, or not visible to you. Both cases return the same body. |

#### Example (curl)

```bash
curl -sS "https://api.dailybot.com/v1/plan/tasks/ENG-142/participants/" \
  -H "Authorization: Bearer $DAILYBOT_TOKEN"
```

#### Scenario examples

##### CLI

```bash
dailybot plan task participants list ENG-142
```

##### Response

```bash
{
  "count": 1,
  "next": null,
  "previous": null,
  "results": [
    {
      "member": {
        "kind": "user",
        "uuid": "00000000-0000-4000-8000-00000000000c",
        "name": "Ada L."
      },
      "role": "participant",
      "source": "manual",
      "is_muted": false,
      "added_by": {
        "kind": "user",
        "uuid": "00000000-0000-4000-8000-00000000000c",
        "name": "Ada L."
      },
      "created_at": "2026-09-25T10:14:02Z"
    }
  ]
}
```

#### Notes

- Scope: `tasks:read`.
- Rate limit: 120 reads per minute per actor.
- Needs a person: call it with a login session, a CLI user token or a personal API key. An agent or organization key gets `403 insufficient_scope`.

### POST `/v1/plan/tasks/{task_id}/participants/` · Beta

**Put someone on this card**

Adds a participant or watcher. Adding someone already on the card returns `200` with the existing row. Adding or removing a participant emits `task.participant_added` with `actor_is_self`, so "someone added me" and "I joined" can be told apart. **A watcher change emits nothing**: following a task is a private preference. Muting (`is_muted`) keeps the person on the card.

- **Auth:** CLI Bearer (write)
- **Rate limit:** `default`
- **Pagination:** No pagination

#### Path parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `task_id` | string | Yes | A task uuid **or** its key, such as `ENG-142`, including a key retired by a board rename. Resolution is scoped to your organization first, so another organization's key is a 404 identical to a missing one. Numeric ids are never accepted. |

#### Headers

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `Idempotency-Key` | string | No | A key you generate for this intent. A replay with the same key and body returns the first response without a second side effect and carries `Idempotency-Replayed: true`. Keys are kept for 24 hours. The same key with a different body is `409 idempotency_key_payload_mismatch`; a repeat while the first call is still running gets `409 idempotency_in_progress` for up to 120 seconds. |
| `X-Dailybot-Agent-Name` | string | No | The name of the agent that executed this write on the person's behalf. Use it on multipart and body-less writes (DELETE, archive, restore); on JSON writes send the body field `agent_name` instead, which wins if both are present. Percent-encode the value as UTF-8. Control characters are stripped; a blank value means no agent. More than 128 characters, or a value that cannot be decoded, is `400 invalid_agent_attribution` (never truncated). An agent-type key, which is not bound to a person, gets `400 invalid_agent_attribution` if it sends it. The stamp never changes a permission answer. See [Agent attribution](/developers/plan/conventions#agent-attribution). |

#### Request body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `user_uuid` | uuid | Yes | The person's user uuid. |
| `role` | enum | No | Participant role. One of `participant`, `watcher`. Default `participant`. |
| `is_muted` | boolean | No | Stay on the card without notifications. Default `false`. |
| `agent_name` | string | No | The name of the agent that executed this write on the person's behalf (max 128 characters, blank means no agent). Takes priority over the `X-Dailybot-Agent-Name` header. See [Agent attribution](/developers/plan/conventions#agent-attribution). |

#### Response body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `(body)` | TaskParticipant | Yes | A [TaskParticipant](#plan-task-participants-list-taskparticipant) object. |

#### Error codes

| Status | When |
|--------|------|
| `400` | The agent name is invalid (`invalid_agent_attribution`). |
| `401` | Missing, expired or malformed credential (`credential_absent`, `credential_expired`, `credential_malformed`). |
| `402` | Plan is not enabled for your organization yet (`plan_upgrade_required`). Expected during the Beta: write to support@dailybot.com. |
| `403` | Authenticated but not allowed: missing scope (`insufficient_scope`, which is also what an agent or organization key gets on an operation that needs a person, and what a personal key gets when its explicit Plan scopes do not cover the endpoint) or a guest account (`guest_not_allowed`). |
| `404` | Not found, or not visible to you. Both cases return the same body. |

#### Example (curl)

```bash
curl -sS -X POST "https://api.dailybot.com/v1/plan/tasks/ENG-142/participants/" \
  -H "Authorization: Bearer $DAILYBOT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "user_uuid": "00000000-0000-4000-8000-00000000000c",
    "role": "participant"
  }'
```

#### Scenario examples

##### CLI

```bash
dailybot plan task participants add ENG-142 --user 00000000-0000-4000-8000-00000000000c --role participant
dailybot plan task mute ENG-142
dailybot plan task unmute ENG-142
```

#### Notes

- Scope: `tasks:write`.
- Rate limit: 60 writes per minute per actor.
- Needs a person: call it with a login session, a CLI user token or a personal API key. An agent or organization key gets `403 insufficient_scope`.

### DELETE `/v1/plan/tasks/{task_id}/participants/{user_uuid}/` · Beta

**Take someone off this card**

Removes the person from the card and emits `task.participant_removed`. **Leaving is not muting**: to stop notifications but stay on the card, set `is_muted` through the add endpoint.

- **Auth:** CLI Bearer (write)
- **Rate limit:** `default`
- **Pagination:** No pagination

#### Path parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `task_id` | string | Yes | A task uuid **or** its key, such as `ENG-142`, including a key retired by a board rename. Resolution is scoped to your organization first, so another organization's key is a 404 identical to a missing one. Numeric ids are never accepted. |
| `user_uuid` | string | Yes | The participant's user uuid. |

#### Headers

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `X-Dailybot-Agent-Name` | string | No | The name of the agent that executed this write on the person's behalf. Use it on multipart and body-less writes (DELETE, archive, restore); on JSON writes send the body field `agent_name` instead, which wins if both are present. Percent-encode the value as UTF-8. Control characters are stripped; a blank value means no agent. More than 128 characters, or a value that cannot be decoded, is `400 invalid_agent_attribution` (never truncated). An agent-type key, which is not bound to a person, gets `400 invalid_agent_attribution` if it sends it. The stamp never changes a permission answer. See [Agent attribution](/developers/plan/conventions#agent-attribution). |

#### Error codes

| Status | When |
|--------|------|
| `400` | The agent name is invalid (`invalid_agent_attribution`). |
| `401` | Missing, expired or malformed credential (`credential_absent`, `credential_expired`, `credential_malformed`). |
| `402` | Plan is not enabled for your organization yet (`plan_upgrade_required`). Expected during the Beta: write to support@dailybot.com. |
| `403` | Authenticated but not allowed: missing scope (`insufficient_scope`, which is also what an agent or organization key gets on an operation that needs a person, and what a personal key gets when its explicit Plan scopes do not cover the endpoint) or a guest account (`guest_not_allowed`). |
| `404` | Not found, or not visible to you. Both cases return the same body. |

#### Example (curl)

```bash
curl -sS -X DELETE "https://api.dailybot.com/v1/plan/tasks/ENG-142/participants/00000000-0000-4000-8000-00000000000c/" \
  -H "Authorization: Bearer $DAILYBOT_TOKEN"
```

#### Scenario examples

##### CLI

```bash
dailybot plan task participants remove ENG-142 00000000-0000-4000-8000-00000000000c --yes
```

#### Notes

- Scope: `tasks:write`.
- Rate limit: 60 writes per minute per actor.
- Needs a person: call it with a login session, a CLI user token or a personal API key. An agent or organization key gets `403 insufficient_scope`.

### PATCH `/v1/plan/tasks/{task_id}/attachments/{attachment_id}/` · Beta

**Rename a task attachment**

Changes the display file name; the stored bytes do not change. Anyone who may write to the parent can rename its attachments.

- **Auth:** API key (`X-API-KEY`), CLI Bearer (write)
- **Rate limit:** `default`
- **Pagination:** No pagination

#### Path parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `task_id` | string | Yes | A task uuid **or** its key, such as `ENG-142`, including a key retired by a board rename. Resolution is scoped to your organization first, so another organization's key is a 404 identical to a missing one. Numeric ids are never accepted. |
| `attachment_id` | uuid | Yes | The attachment's uuid. |

#### Headers

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `X-Dailybot-Agent-Name` | string | No | The name of the agent that executed this write on the person's behalf. Use it on multipart and body-less writes (DELETE, archive, restore); on JSON writes send the body field `agent_name` instead, which wins if both are present. Percent-encode the value as UTF-8. Control characters are stripped; a blank value means no agent. More than 128 characters, or a value that cannot be decoded, is `400 invalid_agent_attribution` (never truncated). An agent-type key, which is not bound to a person, gets `400 invalid_agent_attribution` if it sends it. The stamp never changes a permission answer. See [Agent attribution](/developers/plan/conventions#agent-attribution). |

#### Request body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `filename` | string | Yes | The new file name (1–255 characters). The stored bytes do not change. |
| `agent_name` | string | No | The name of the agent that executed this write on the person's behalf (max 128 characters, blank means no agent). Takes priority over the `X-Dailybot-Agent-Name` header. See [Agent attribution](/developers/plan/conventions#agent-attribution). |

#### Response body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `(body)` | TaskAttachment | Yes | A [TaskAttachment](#plan-task-comments-list-taskattachment) object. |

#### Error codes

| Status | When |
|--------|------|
| `400` | Validation failed; the response `code` says which field. `invalid_agent_attribution` means the agent name is invalid. |
| `401` | Missing, expired or malformed credential (`credential_absent`, `credential_expired`, `credential_malformed`). |
| `402` | Plan is not enabled for your organization yet (`plan_upgrade_required`). Expected during the Beta: write to support@dailybot.com. |
| `403` | You may not write here (`insufficient_scope`), or you are a guest (`guest_not_allowed`). |
| `404` | The parent or the attachment does not exist or you cannot see it (`not_found`), never a 403. |

#### Example (curl)

```bash
curl -sS -X PATCH "https://api.dailybot.com/v1/plan/tasks/ENG-142/attachments/00000000-0000-4000-8000-000000000009/" \
  -H "X-API-KEY: $DAILYBOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "filename": "spec-v2.pdf"
}'
```

#### Scenario examples

##### CLI

```bash
dailybot plan task attachments rename ENG-142 00000000-0000-4000-8000-000000000009 spec-v2.pdf
```

#### Notes

- Scope: `tasks:write`.
- Rate limit: 60 writes per minute per actor.
- Works with a login session, a CLI user token, a personal API key, or an agent or organization key. A personal key sees what its person sees; an agent or organization key acts as a system actor and sees organization-visible boards only.

---

## Developer portal navigation

**Getting Started**

- [Overview](/developers)
- [Quick start](/developers/getting-started)
- [Authentication](/developers/authentication)

**API Reference**

- [API Overview](/developers/api)
- [Users](/developers/api/users)
- [Organization](/developers/api/organization)
- [Teams](/developers/api/teams)
- [Invitations](/developers/api/invitations)
- [Check-ins](/developers/api/check-ins)
- [Forms](/developers/api/forms)
- [Labels](/developers/api/labels)
- [Report channels](/developers/api/report-channels)
- [Templates](/developers/api/templates)
- [Kudos](/developers/api/kudos)
- [Mood tracking](/developers/api/mood)
- [Important dates](/developers/api/important-dates)
- [Messaging](/developers/api/messaging)
- [Automations](/developers/api/workflows)
- [Webhooks](/developers/api/webhooks)
- [Commands platform](/developers/api/commands-platform)
- [Agents](/developers/api/agents)
- [OAuth2](/developers/api/oauth2)
- [Integrations](/developers/api/integrations)
- [CLI](/developers/api/cli)
- [Plan · Projects](/developers/api/plan-projects)
- [Plan · Goals](/developers/api/plan-goals)
- [Plan · Boards](/developers/api/plan-boards)
- [Plan · Tasks](/developers/api/plan-tasks) (this page)
- [Plan · Comments & files](/developers/api/plan-collaboration)
- [Plan · Home & search](/developers/api/plan-home)
- [Plan · Notifications & reports](/developers/api/plan-notifications)

**Dailybot Plan**

- [Overview](/developers/plan)
- [Concepts](/developers/plan/concepts)
- [Quickstart](/developers/plan/quickstart)
- [Authentication & scopes](/developers/plan/authentication)
- [Agents on Plan](/developers/plan/agents)
- [Conventions](/developers/plan/conventions)
- [Errors](/developers/plan/errors)
- [CLI for Plan](/developers/plan/cli)
- [Agent skill](/developers/plan/agent-skill)
- [Recipe: live board](/developers/plan/recipes/board-live-updates)
- [Recipe: home in one request](/developers/plan/recipes/home-in-one-request)
- [Recipe: bulk create](/developers/plan/recipes/bulk-create)
- [Recipe: move on PR merge](/developers/plan/recipes/move-on-pr-merge)
- [Recipe: goal progress](/developers/plan/recipes/goal-progress)
- [Recipe: webhooks](/developers/plan/recipes/webhooks)

**API guides**

- [Errors & Status Codes](/developers/errors)
- [Rate Limits](/developers/rate-limits)
- [Conventions](/developers/conventions)
- [API Changelog](/developers/api-changelog)
- [Recipes](/developers/recipes)

**Developer Features**

- [Custom commands](/developers/custom-commands)
- [Serverless commands](/developers/serverless)
- [Webhooks & events](/developers/webhooks)
- [Automation API trigger](/developers/workflow-trigger)
- [Activity API](/developers/activity-api)

**CLI**

- [Overview](/developers/cli)
- [Authentication](/developers/cli-authentication)
- [Command reference](/developers/cli-reference)
- [CI/CD recipes](/developers/cli-ci-cd)
- [Configuration](/developers/cli-configuration)
- [Troubleshooting](/developers/cli-troubleshooting)

**Agent Skill**

- [Overview](/developers/agent-skill)
- [Skills catalog](/skills)

---

## Site navigation

**Product:**
- [Home](/)
- [Product](/product)
- [Pricing](/pricing)
- [Enterprise](/enterprise)
- [Integrations](/integrations)
- [Templates](/templates)

**Resources:**
- [Blog](/blog)
- [Academy](/academy)
- [Changelog](/changelog)
- [Help Center](/help)
- [Developers](/developers)
- [Agents](/agents)

**Company:**
- [About](/about)
- [Careers](/careers)
- [Security](/security)
- [Contact Sales](/demo)

**Connect:**
- [LinkedIn](https://www.linkedin.com/company/dailybot/)
- [X/Twitter](https://twitter.com/dailybot)
- [GitHub](https://github.com/Dailybot-Inc)
- [YouTube](https://www.youtube.com/channel/UC3uM9V52vwX7e3vQpCc4qvA)

