# Plan · Boards

> Boards and their workflow states, the one-call board snapshot, the delta feed, members, labels and saved views. Part of the Dailybot Plan API (Beta).

Language: en
Canonical: https://www.dailybot.com/developers/api/plan-boards
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 · Boards**. 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

Boards and their workflow states, the one-call board snapshot, the delta feed, members, labels and saved views. Part of the Dailybot Plan API (Beta).

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/v1/plan/boards/` | List boards |
| POST | `/v1/plan/boards/` | Create a board and seed its five default states |
| GET | `/v1/plan/boards/{board_id}/` | Retrieve a board |
| PATCH | `/v1/plan/boards/{board_id}/` | Update a board, including renaming its key |
| POST | `/v1/plan/boards/{board_id}/archive/` | Archive a board, cascading to its tasks |
| POST | `/v1/plan/boards/{board_id}/restore/` | Restore an archived board |
| POST | `/v1/plan/boards/{board_id}/visit/` | Record that the caller opened a board (HomePulse recent_boards) |
| GET | `/v1/plan/boards/{board_id}/states/` | List a board's workflow states, in column order |
| POST | `/v1/plan/boards/{board_id}/states/` | Add a workflow state to a board |
| PATCH | `/v1/plan/boards/{board_id}/states/{state_id}/` | Rename, recolour or reorder a workflow state |
| POST | `/v1/plan/boards/{board_id}/states/{state_id}/archive/` | Retire a column |
| POST | `/v1/plan/boards/{board_id}/states/{state_id}/restore/` | Restore a retired column |
| POST | `/v1/plan/boards/{board_id}/states/reorder/` | Reorder every live column on a board in one call |
| GET | `/v1/plan/boards/{board_id}/board/` | The whole board — states and their tasks — in one round trip |
| GET | `/v1/plan/boards/{board_id}/delta/` | What changed on this board since a timestamp. NOT pagination |
| GET | `/v1/plan/boards/{board_id}/views/` | The caller's saved views for this board |
| PUT | `/v1/plan/boards/{board_id}/views/` | Replace the caller's saved views for this board |
| GET | `/v1/plan/boards/{board_id}/mentionables/` | Search people mentionable on a board |
| GET | `/v1/plan/boards/{board_id}/members/` | Members of a board |
| POST | `/v1/plan/boards/{board_id}/members/` | Add a member to a board |
| DELETE | `/v1/plan/boards/{board_id}/members/{user_id}/` | Remove a member from a board |
| PATCH | `/v1/plan/boards/{board_id}/members/{user_id}/` | Inspect a board membership grant (role is read-only) |
| GET | `/v1/plan/boards/{board_id}/labels/` | List organization labels (board access gate) |
| POST | `/v1/plan/boards/{board_id}/labels/` | Create an organization label |
| GET | `/v1/plan/views/{view_id}/` | One saved view by uuid |
| PATCH | `/v1/plan/views/{view_id}/` | Edit one saved view |
| DELETE | `/v1/plan/views/{view_id}/` | Delete one saved view |
| GET | `/v1/plan/boards/{board_id}/attachments/` | List a board's attachments |
| POST | `/v1/plan/boards/{board_id}/attachments/` | Upload an attachment to a board |
| GET | `/v1/plan/boards/{board_id}/attachments/{attachment_id}/` | Retrieve a board attachment |
| GET | `/v1/plan/boards/{board_id}/attachments/{attachment_id}/content/` | Download a board attachment's bytes |
| PATCH | `/v1/plan/boards/{board_id}/attachments/{attachment_id}/` | Rename a board attachment |
| DELETE | `/v1/plan/boards/{board_id}/attachments/{attachment_id}/` | Remove an attachment from a board |

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

**List boards**

The boards you can see, as a page. Filter by `project`, search with `search`, by dates with `start_date` / `end_date`, and bring archived boards with `include_archived`. An agent or organization key sees organization-visible boards only; a personal key sees what its person sees.

- **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. |
| `project` | array | No | Project uuids. Repeatable; values are OR-ed. |

##### Dates

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `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. |

#### Board object

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `uuid` | uuid | Yes | Stable public identifier. |
| `key` | string | Yes | The board's key: the prefix of its tasks' keys. Renaming it keeps the old key reserved and resolving. |
| `name` | string | Yes | Display name. Max 120 characters. |
| `project` | Project | No | The project. See [Project](#plan-boards-list-project). |
| `team` | uuid | null | No | The team. |
| `visibility` | enum | Yes | `org` (everyone in the organization) or `members` (explicit members only). One of `org`, `members`. |
| `effective_visibility` | string | No | Whether the board is effectively visible to the whole organization (`org`) or only to members (`members`). A board inside a `members` project is `members` here, while `visibility` stays the board's own stored setting. |
| `estimate_scale` | enum | No | How estimates are expressed on this board. One of `none`, `fibonacci`, `linear`. |
| `default_view` | SavedView | null | No | The board's default saved view, or `null`. See [SavedView](#plan-boards-list-savedview). |
| `archive_after_days` | integer | null | No | Archive done tasks automatically after this many days, or `null` to keep them. |
| `task_count` | integer | No | Number of live tasks. |
| `wip_limits` | object | No | Work-in-progress limits per column. |
| `is_archived` | boolean | No | Whether the row is archived. Archive is the delete: archived rows stay readable and restorable. |
| `created_at` | date-time | No | When the row was created. |
| `updated_at` | date-time | No | When the row last changed. |
| `viewer` | object | No | What you can do with this row. Shape: `{is_member, can_see_content, can_manage: boolean} (all required)`. |
| `states` | array<WorkflowState> | No | The board's workflow states, in column order. See [WorkflowState](#plan-boards-list-workflowstate). |

#### Project object

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `uuid` | uuid | Yes | Stable public identifier. |
| `name` | string | Yes | Display name. Max 120 characters. |
| `slug` | string | No | URL-friendly name. Max 48 characters. |
| `description` | string | null | No | Free-form description. |
| `lead` | UserRef | null | No | The project's lead. See [UserRef](#plan-boards-list-userref). |
| `goals` | array | No | Goals this project points at. A project can serve several goals. Always present: `uuid`. Items: `{uuid, name}`. |
| `goal` | object | No | The goal, when there is exactly one. Shape: `{uuid, name}|null`. |
| `board_count` | integer | No | Number of live boards in the project. |
| `health` | enum | No | Declared health. One of `not_set`, `on_track`, `at_risk`, `off_track`. |
| `start_date` | date | null | No | Planned start date. |
| `target_date` | date | null | No | Planned end date. |
| `progress` | ProjectProgress | null | No | Progress roll-up over the tasks you can see. See [ProjectProgress](#plan-boards-list-projectprogress). |
| `is_archived` | boolean | Yes | Whether the row is archived. Archive is the delete: archived rows stay readable and restorable. |
| `archived_at` | date-time | null | No | When the row was archived. |
| `created_at` | date-time | No | When the row was created. |
| `updated_at` | date-time | No | When the row last changed. |
| `viewer` | object | No | What you can do with this row. Shape: `{can_see_content: boolean, can_manage: boolean} (both required)`. |

#### SavedView object

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `name` | string | Yes | Display name. Max 64 characters. |
| `view_mode` | enum | No | How the filtered set is drawn. Reads always return `board` for the kanban layout. One of `list`, `board`, `timeline`, `calendar`. |
| `group_by` | enum | No | The grouping dimension. One of `state`, `owner`, `priority`, `category`. |
| `sort` | string | No | A sort key, `-` prefixed for descending. |
| `filters` | object | Yes | The view's filters, in the shared task filter grammar. |
| `schema_version` | integer | No | Version of the view's stored format. |
| `visibility` | enum | No | `personal` (default) is yours alone. `shared` and `board_default` (the default view for that board or project) are readable by everyone who can see the board or project. Setting them needs a board manager on board views, and project oversight (an organization admin or a manager of all teams) on project views; otherwise `403 view_visibility_forbidden`. One of `personal`, `shared`, `board_default`. |
| `collapsed` | object | array | string | number | boolean | No | Client UI state stored verbatim (which groups are collapsed). Only size and depth are validated. |
| `columns` | object | array | string | number | boolean | No | Client UI state stored verbatim (which columns are shown). Only size and depth are validated. |
| `uuid` | uuid | No | Stable public identifier. |
| `scope` | enum | No | Which container the view belongs to: `board` or `project`. Read-only. One of `board`, `project`. |
| `board` | uuid | null | No | The board's uuid when `scope` is `board`; `null` for a project view. Read-only. |
| `owner` | object | No | Who owns the view. Shape: `{uuid, name}`. |
| `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 | — |

#### ProjectProgress object

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `total` | integer | Yes | All tasks counted. |
| `completed` | integer | Yes | Tasks in a `done` or `canceled` state. |
| `open` | integer | No | Tasks in a `backlog`, `todo` or `in_progress` state. |
| `blocked` | integer | No | Tasks with a live blocker. |
| `overdue` | integer | No | Open tasks past their due date. |
| `percent_complete` | integer | Yes | `completed` as a percentage of `total`. |

#### 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<Board> | Yes | The rows on this page. See [Board](#plan-boards-list-board). |

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

#### Example (curl)

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

#### Scenario examples

##### CLI

```bash
dailybot plan board list --json
```

##### Response

```bash
{
  "count": 1,
  "next": null,
  "previous": null,
  "results": [
    {
      "uuid": "00000000-0000-4000-8000-000000000002",
      "key": "ENG",
      "name": "Engineering",
      "project": {
        "uuid": "00000000-0000-4000-8000-000000000001",
        "name": "Platform",
        "is_archived": false
      },
      "team": null,
      "visibility": "org",
      "estimate_scale": "fibonacci",
      "default_view": null,
      "archive_after_days": null,
      "task_count": 12,
      "wip_limits": {},
      "is_archived": false,
      "created_at": "2026-09-25T10:14:02Z",
      "updated_at": "2026-09-25T10:14:02Z",
      "viewer": {},
      "states": []
    }
  ]
}
```

#### 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/` · Beta

**Create a board and seed its five default states**

Creates a board inside a project and seeds its five default workflow states. The board `key` prefixes every task key (`ENG-142`) and must be unique (`409 duplicate_board_key`). Every non-guest member can call it (with a login session or a personal API key); an agent or organization key cannot. The plan's board limit answers `402 task_boards_limit_reached`.

- **Auth:** 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 |
|------|------|----------|-------------|
| `name` | string | Yes | Display name. Max 120 characters. |
| `key` | string | Yes | The board's key, the prefix of its tasks' keys (for example `ENG`). |
| `project` | uuid | Yes | The project. |
| `team` | uuid | null | No | The team. |
| `visibility` | enum | No | `org` (everyone in the organization) or `members` (explicit members only). One of `org`, `members`. |
| `estimate_scale` | enum | No | How estimates are expressed on this board. One of `none`, `fibonacci`, `linear`. |
| `archive_after_days` | integer | null | No | Archive done tasks automatically after this many days, or `null` to keep them. Minimum 1. |
| `default_view` | SavedView | null | No | The board's default saved view, or `null`. See [SavedView](#plan-boards-list-savedview). |
| `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)` | Board | Yes | A [Board](#plan-boards-list-board) 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`), or the plan's board ceiling is reached (`task_boards_limit_reached`). |
| `409` | Another board already uses this key (`duplicate_board_key`). |

#### Example (curl)

```bash
curl -sS -X POST "https://api.dailybot.com/v1/plan/boards/" \
  -H "Authorization: Bearer $DAILYBOT_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Engineering",
    "key": "ENG",
    "project": "00000000-0000-4000-8000-000000000001",
    "visibility": "org",
    "estimate_scale": "fibonacci"
  }'
```

#### Scenario examples

##### CLI

```bash
dailybot plan board create --name "Engineering"
```

#### Notes

- Scope: `tasks:admin` — container writes. A non-guest member can call it with a login session or a personal API key (a key with explicit Plan scopes needs `tasks:write`, which covers it); an agent or organization key gets `403 insufficient_scope`.
- 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`.

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

**Retrieve a board**

One board by uuid. A board you cannot see answers `404`, the same as one that does not exist.

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

#### Path parameters

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

#### Response body

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

#### 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/boards/00000000-0000-4000-8000-000000000002/" \
  -H "X-API-KEY: $DAILYBOT_API_KEY"
```

#### Scenario examples

##### CLI

```bash
dailybot plan board get 00000000-0000-4000-8000-000000000002
```

#### 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/boards/{board_id}/` · Beta

**Update a board, including renaming its key**

Renaming `key` retires the previous key and keeps it reserved, so `ENG-142` typed years later still resolves. Switching `visibility` to `members` adds you as a member, because a members-only board with no members would be visible to nobody.

- **Auth:** 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 |
|------|------|----------|-------------|
| `name` | string | No | Display name. Max 120 characters. |
| `key` | string | No | The board's key, the prefix of its tasks' keys (for example `ENG`). |
| `project` | uuid | No | The project. |
| `team` | uuid | null | No | The team. |
| `visibility` | enum | No | `org` (everyone in the organization) or `members` (explicit members only). One of `org`, `members`. |
| `estimate_scale` | enum | No | How estimates are expressed on this board. One of `none`, `fibonacci`, `linear`. |
| `archive_after_days` | integer | null | No | Archive done tasks automatically after this many days, or `null` to keep them. Minimum 1. |
| `default_view` | SavedView | null | No | The board's default saved view, or `null`. See [SavedView](#plan-boards-list-savedview). |
| `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)` | Board | Yes | A [Board](#plan-boards-list-board) 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` | Another board already uses this key (`duplicate_board_key`). |

#### Example (curl)

```bash
curl -sS -X PATCH "https://api.dailybot.com/v1/plan/boards/00000000-0000-4000-8000-000000000002/" \
  -H "Authorization: Bearer $DAILYBOT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "key": "PLAT"
  }'
```

#### Scenario examples

##### CLI

```bash
dailybot plan board update 00000000-0000-4000-8000-000000000002 --key PLAT
```

#### Notes

- Scope: `tasks:admin` — container writes. A non-guest member can call it with a login session or a personal API key (a key with explicit Plan scopes needs `tasks:write`, which covers it); an agent or organization key gets `403 insufficient_scope`.
- 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/boards/{board_id}/archive/` · Beta

**Archive a board, cascading to its tasks**

Archives the board and, with it, its tasks. The board key stays reserved, so it is never reused. Send `?dry_run=true` first to see the consequence without archiving; restore the board with the restore endpoint.

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

#### Path parameters

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

#### 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)` | Board | DryRunPreview | Yes | A [Board](#plan-boards-list-board) object. With `?dry_run=true`, a [DryRunPreview](#plan-board-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. |
| `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/boards/00000000-0000-4000-8000-000000000002/archive/" \
  -H "Authorization: Bearer $DAILYBOT_TOKEN"
```

#### Scenario examples

##### CLI

```bash
dailybot plan board archive 00000000-0000-4000-8000-000000000002 --dry-run
```

#### Notes

- Scope: `tasks:admin` — container writes. A non-guest member can call it with a login session or a personal API key (a key with explicit Plan scopes needs `tasks:write`, which covers it); an agent or organization key gets `403 insufficient_scope`.
- 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/boards/{board_id}/restore/` · Beta

**Restore an archived board**

Inverse of archive. The board key was never retired — it stays reserved through archive and restore. Tasks that cascaded on archive stay archived; restore them with `POST …/tasks/{task_id}/restore/`. Restore consumes one board-creation entitlement slot (archive frees one).

- **Auth:** 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). |

#### Response body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `(body)` | Board | Yes | A [Board](#plan-boards-list-board) 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`), or no board slot is free (`task_boards_limit_reached`). |
| `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/boards/00000000-0000-4000-8000-000000000002/restore/" \
  -H "Authorization: Bearer $DAILYBOT_TOKEN"
```

#### Scenario examples

##### CLI

```bash
dailybot plan board restore 00000000-0000-4000-8000-000000000002
```

#### Notes

- Scope: `tasks:admin` — container writes. A non-guest member can call it with a login session or a personal API key (a key with explicit Plan scopes needs `tasks:write`, which covers it); an agent or organization key gets `403 insufficient_scope`.
- 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/boards/{board_id}/visit/` · Beta

**Record that the caller opened a board (HomePulse recent_boards)**

Upserts the caller's last visit timestamp for this board. Repeated POSTs update `visited_at` and never create duplicate rows. Requires a person: a login session or a personal API key (agent and organization keys are refused). Authorization matches board read access — missing and cross-org boards share the same 404.

- **Auth:** 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 |
|------|------|----------|-------------|
| `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). |

#### BoardVisit object

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `board` | uuid | Yes | The board. |
| `visited_at` | date-time | Yes | When you last opened the board. |

#### Response body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `(body)` | BoardVisit | Yes | A [BoardVisit](#plan-board-visit-boardvisit) 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. |
| `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/boards/00000000-0000-4000-8000-000000000002/visit/" \
  -H "Authorization: Bearer $DAILYBOT_TOKEN"
```

#### Scenario examples

##### Response

```bash
{
  "board": "00000000-0000-4000-8000-000000000002",
  "visited_at": "2026-09-25T10:14:02Z"
}
```

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

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

**List a board's workflow states, in column order**

The board's workflow states (its columns), ordered by position. Each has a `category` (such as `in_progress`) that stays stable when a state is renamed. Add `include_archived=true` to see archived states.

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

#### Path parameters

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

#### Query parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `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. |

#### Response body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `(body)` | array<WorkflowState> | Yes | A JSON array of [WorkflowState](#plan-boards-list-workflowstate) objects. |

#### 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/boards/00000000-0000-4000-8000-000000000002/states/" \
  -H "X-API-KEY: $DAILYBOT_API_KEY"
```

#### Scenario examples

##### CLI

```bash
dailybot plan board states 00000000-0000-4000-8000-000000000002 --include-archived
```

##### Response

```bash
[
  {
    "uuid": "00000000-0000-4000-8000-000000000003",
    "name": "In Progress",
    "category": "in_progress",
    "position": 2,
    "color": "#2563eb",
    "is_default": false,
    "is_archived": false,
    "task_count": 12
  }
]
```

#### 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}/states/` · Beta

**Add a workflow state to a board**

`category` is one of five fixed values and never changes after create; `name` is free and can be renamed. The category is what "is this finished?" is answered from.

`position` **inserts** at that place, 1-based among live columns: the column that held it and everything after it shift right. A position past the end lands at the end.

- **Auth:** 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 |
|------|------|----------|-------------|
| `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 | No | Column position among live columns, 1-based, left to right. `0` and `1` both mean the first column, and a value past the end lands last. Omit it to append the new state at the end. Minimum 0. |
| `color` | string | No | Display color (hex). |
| `is_default` | boolean | No | Whether new tasks land in this state by default. |
| `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)` | WorkflowState | Yes | A [WorkflowState](#plan-boards-list-workflowstate) 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` | 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/states/" \
  -H "Authorization: Bearer $DAILYBOT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "In review",
    "category": "in_progress",
    "position": 3
  }'
```

#### Scenario examples

##### CLI

```bash
dailybot plan board state create 00000000-0000-4000-8000-000000000002 -n "In review" --category in_progress --position 3
```

##### Response

```bash
{
  "uuid": "00000000-0000-4000-8000-000000000003",
  "name": "In Progress",
  "category": "in_progress",
  "position": 2,
  "color": "#2563eb",
  "is_default": false,
  "is_archived": false,
  "task_count": 12
}
```

#### Notes

- Scope: `tasks:admin` — container writes. A non-guest member can call it with a login session or a personal API key (a key with explicit Plan scopes needs `tasks:write`, which covers it); an agent or organization key gets `403 insufficient_scope`.
- 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/boards/{board_id}/states/{state_id}/` · Beta

**Rename, recolour or reorder a workflow state**

Allowed fields are `name`, `color` and `position` only. Unknown fields are refused with `400` (never silently ignored). `category` cannot change after create.

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

#### Path parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `board_id` | string | Yes | The board's uuid. |
| `state_id` | string | Yes | The workflow state'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 |
|------|------|----------|-------------|
| `name` | string | No | Display name. Max 48 characters. |
| `position` | integer | No | Column position among live columns, 1-based, left to right. `0` and `1` both mean the first column, and a value past the end lands last. Minimum 0. |
| `color` | string | No | Display color (hex). |
| `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)` | WorkflowState | Yes | A [WorkflowState](#plan-boards-list-workflowstate) 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. |

#### Example (curl)

```bash
curl -sS -X PATCH "https://api.dailybot.com/v1/plan/boards/00000000-0000-4000-8000-000000000002/states/00000000-0000-4000-8000-000000000003/" \
  -H "Authorization: Bearer $DAILYBOT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Code review"
  }'
```

#### Scenario examples

##### CLI

```bash
dailybot plan board state update 00000000-0000-4000-8000-000000000002 00000000-0000-4000-8000-000000000003 --name "Code review"
```

#### Notes

- Scope: `tasks:admin` — container writes. A non-guest member can call it with a login session or a personal API key (a key with explicit Plan scopes needs `tasks:write`, which covers it); an agent or organization key gets `403 insufficient_scope`.
- 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/boards/{board_id}/states/{state_id}/archive/` · Beta

**Retire a column**

Refused with `409 state_in_use` while live tasks still sit in the column, unless the body names `migrate_to` — another live state on the same board that receives every card in one bulk update before the column is archived.

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

#### Path parameters

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

#### 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 |
|------|------|----------|-------------|
| `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 |
|------|------|----------|-------------|
| `migrate_to` | uuid | No | Another live state on the same board that receives every task in the column before it is archived. |
| `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)` | WorkflowState | DryRunPreview | Yes | A [WorkflowState](#plan-boards-list-workflowstate) object. With `?dry_run=true`, a [DryRunPreview](#plan-board-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` | Live tasks still sit in the column (`state_in_use`). Send `migrate_to` to move them first. |

#### Example (curl)

```bash
curl -sS -X POST "https://api.dailybot.com/v1/plan/boards/00000000-0000-4000-8000-000000000002/states/00000000-0000-4000-8000-000000000003/archive/" \
  -H "Authorization: Bearer $DAILYBOT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "migrate_to": "00000000-0000-4000-8000-000000000004"
  }'
```

#### Scenario examples

##### CLI

```bash
dailybot plan board state archive 00000000-0000-4000-8000-000000000002 00000000-0000-4000-8000-000000000003 --migrate-to 00000000-0000-4000-8000-000000000004 --yes
```

#### Notes

- Scope: `tasks:admin` — container writes. A non-guest member can call it with a login session or a personal API key (a key with explicit Plan scopes needs `tasks:write`, which covers it); an agent or organization key gets `403 insufficient_scope`.
- 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/boards/{board_id}/states/{state_id}/restore/` · Beta

**Restore a retired column**

Inverse of archive. The column returns after the live columns, and a second POST on a live column is a 200 no-op. Read it back with `GET …/states/?include_archived=true` while it is still retired.

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

#### Path parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `board_id` | string | Yes | The board's uuid. |
| `state_id` | string | Yes | The workflow state'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). |

#### Response body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `(body)` | WorkflowState | Yes | A [WorkflowState](#plan-boards-list-workflowstate) 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/boards/00000000-0000-4000-8000-000000000002/states/00000000-0000-4000-8000-000000000003/restore/" \
  -H "Authorization: Bearer $DAILYBOT_TOKEN"
```

#### Scenario examples

##### CLI

```bash
dailybot plan board state restore 00000000-0000-4000-8000-000000000002 00000000-0000-4000-8000-000000000003
```

#### Notes

- Scope: `tasks:admin` — container writes. A non-guest member can call it with a login session or a personal API key (a key with explicit Plan scopes needs `tasks:write`, which covers it); an agent or organization key gets `403 insufficient_scope`.
- 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/boards/{board_id}/states/reorder/` · Beta

**Reorder every live column on a board in one call**

Body `{ "order": [state_uuid, …] }` must list **every** live column on the board exactly once, in the desired left-to-right order. Partial lists, unknown uuids and duplicates return `400 states_reorder_invalid`. Emits `state.reordered` for each column. To set a board-level default view (or clear it), use `PATCH /boards/{board_id}/` with `default_view` — there is no separate make-default endpoint.

- **Auth:** 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 |
|------|------|----------|-------------|
| `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 |
|------|------|----------|-------------|
| `order` | array | Yes | Every live column's uuid exactly once, left to right. 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 |
|------|------|----------|-------------|
| `(body)` | array<WorkflowState> | Yes | A JSON array of [WorkflowState](#plan-boards-list-workflowstate) objects. |

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

#### Example (curl)

```bash
curl -sS -X POST "https://api.dailybot.com/v1/plan/boards/00000000-0000-4000-8000-000000000002/states/reorder/" \
  -H "Authorization: Bearer $DAILYBOT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "order": [
      "00000000-0000-4000-8000-000000000003",
      "00000000-0000-4000-8000-000000000004"
    ]
  }'
```

#### Scenario examples

##### CLI

```bash
dailybot plan board state reorder 00000000-0000-4000-8000-000000000002 00000000-0000-4000-8000-000000000003 00000000-0000-4000-8000-000000000004
```

#### Notes

- Scope: `tasks:admin` — container writes. A non-guest member can call it with a login session or a personal API key (a key with explicit Plan scopes needs `tasks:write`, which covers it); an agent or organization key gets `403 insufficient_scope`.
- 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`.

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

**The whole board — states and their tasks — in one round trip**

One call renders a board: one entry in `groups` per column, in column order, each with its first tasks in rank order, the column's true `task_count` and `has_more`. Page the rest of a column with `GET /v1/plan/tasks/?board=…&state=…`.

Store `delta_cursor` and switch to the delta feed for every later read. Answers `If-None-Match` with `304`. `…/snapshot/` is an alias with the same response. Unknown query parameters and invalid filter values are `400 invalid_filter_value`.

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

#### Path parameters

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

#### Query parameters

##### Filters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `group_by` | string | No | Group the snapshot by another dimension instead of workflow state. Grouping exists on the snapshot only: grouping a paginated list would fork its envelope. |
| `tasks_per_state` | integer | No | How many tasks to include per column. Maximum 50, tighter than the usual 100 because this read carries labels for every card. |
| `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. |
| `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/`. |
| `search` | string | No | Matches title and key. Longer than 256 characters is `400 search_query_too_long`, not truncated. `q` is an alias. |

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

#### Headers

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

#### BoardSnapshot object

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `board` | Board | Yes | The board. See [Board](#plan-boards-list-board). |
| `generated_at` | date-time | Yes | When the response was computed. |
| `delta_cursor` | date-time | Yes | Pass it as `updated_since` to the delta feed. |
| `group_by` | string | No | The grouping dimension. |
| `groups` | array | Yes | One entry per column (or group), in order. Always present: `key`, `task_count`, `has_more`, `tasks`. Items: `{key: string, name: string, category: string|null, position: integer|null, color: string|null, task_count: integer, has_more: boolean, tasks: array}`. |
| `viewer` | object | No | What you can do with this row. Shape: `{is_member, can_see_content, can_manage} (all required)`. |

#### Response body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `(body)` | BoardSnapshot | Yes | A [BoardSnapshot](#plan-board-snapshot-boardsnapshot) 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/boards/00000000-0000-4000-8000-000000000002/board/?tasks_per_state=25" \
  -H "X-API-KEY: $DAILYBOT_API_KEY"
```

#### Scenario examples

##### CLI

```bash
dailybot plan board snapshot 00000000-0000-4000-8000-000000000002 --json
```

##### Response

```bash
{
  "board": {
    "uuid": "00000000-0000-4000-8000-000000000100",
    "key": "ENG",
    "name": "Engineering",
    "visibility": "org",
    "estimate_scale": "fibonacci",
    "project": {
      "uuid": "00000000-0000-4000-8000-000000000101",
      "name": "Platform"
    }
  },
  "generated_at": "2026-08-29T10:14:02.113954Z",
  "delta_cursor": "2026-08-29T10:14:02.113954Z",
  "group_by": "state",
  "groups": [
    {
      "key": "00000000-0000-4000-8000-000000000102",
      "name": "In Progress",
      "category": "in_progress",
      "position": 2,
      "color": "#f59e0b",
      "task_count": 137,
      "has_more": true,
      "tasks": [
        {
          "uuid": "00000000-0000-4000-8000-000000000103",
          "key": "ENG-142",
          "title": "Ship the delta feed",
          "state": {
            "uuid": "00000000-0000-4000-8000-000000000102",
            "name": "In Progress",
            "category": "in_progress"
          },
          "priority": 2,
          "estimate": 3,
          "rank": "aU",
          "version": 7,
          "open_blocker_count": 1,
          "participant_count": 3,
          "owner": {
            "uuid": "00000000-0000-4000-8000-000000000104",
            "name": "Ada L."
          },
          "executor": null,
          "due_date": "2026-09-04",
          "labels": [
            {
              "uuid": "00000000-0000-4000-8000-000000000105",
              "name": "backend",
              "color": "#2563eb"
            }
          ],
          "is_archived": false,
          "updated_at": "2026-08-29T10:12:44.201113Z"
        }
      ]
    }
  ]
}
```

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

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

**What changed on this board since a timestamp. NOT pagination**

A change feed, not a page: no `count`, `next` or `previous`. Send the `cursor` from your previous response (or the snapshot's `delta_cursor`) verbatim as `updated_since`; never compute it from your own clock.

Delivery is at-least-once, so a row written in the same instant as your cursor is sent again rather than lost. Entries are compacted to one per task (the current row wins). `states` is `null` unless a column was created, renamed, reordered or archived; when it is set, replace your whole column list.

Poll again after `poll_after_seconds` (15 s, doubling to 120 s while the board is quiet, reset on any change). Pause while the page is hidden and refresh when it becomes visible. If `truncated` is true, poll again immediately. Cursors older than 7 days return `400 delta_window_expired`: re-read the snapshot. Polling is the v1 transport.

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

#### Path parameters

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

#### Query parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `updated_since` | string | Yes | The `cursor` from your previous delta, or the `delta_cursor` from a board snapshot. Older than 7 days is refused with `delta_window_expired`. `since` is accepted as a deprecated alias for older clients; send `updated_since`. |
| `limit` | integer | No | Maximum entries in `changed`. |

#### BoardDelta object

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `since` | date-time | Yes | The `updated_since` you sent. |
| `cursor` | date-time | Yes | Send it as `updated_since` on your next poll. |
| `changed` | array<Task> | Yes | Tasks that changed, one entry per task. See [Task](#plan-board-delta-task). |
| `removed` | array | Yes | Tasks that left the board, with a `reason` such as `archived`. Items: `{uuid, key, reason}`. |
| `states` | array | null | No | The board's workflow states, in column order. |
| `truncated` | boolean | Yes | `true` when more changes are waiting: poll again immediately. |
| `poll_after_seconds` | integer | Yes | When to poll next, suggested by the server (15 to 120 seconds). A hint, not enforced. From 15 to 120. |

#### 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-boards-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-boards-list-userref). |
| `executor` | ActorRef | null | No | The actor doing the work, when different from the owner (for example an agent). See [ActorRef](#plan-board-delta-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-delta-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-delta-actorref). |
| `created_at` | date-time | No | When the row was created. |
| `updated_at` | date-time | No | When the row last changed. |

#### 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 |
|------|------|----------|-------------|
| `(body)` | BoardDelta | Yes | A [BoardDelta](#plan-board-delta-boarddelta) object. |

#### Error codes

| Status | When |
|--------|------|
| `400` | The cursor is older than 7 days (`delta_window_expired`): re-read the board snapshot. Also returned for a malformed `updated_since`. |
| `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/delta/?updated_since=2026-09-25T10:14:02.113954Z" \
  -H "X-API-KEY: $DAILYBOT_API_KEY"
```

#### Scenario examples

##### CLI

```bash
dailybot plan tasks changes 00000000-0000-4000-8000-000000000002 --updated-since 2026-09-25T10:14:02.113954Z --json
```

##### Response

```bash
{
  "since": "2026-08-29T10:14:02.113954Z",
  "cursor": "2026-08-29T10:19:44.902311Z",
  "changed": [
    {
      "uuid": "00000000-0000-4000-8000-000000000100",
      "key": "ENG-142",
      "title": "Ship the delta feed",
      "state": {
        "uuid": "00000000-0000-4000-8000-000000000101",
        "name": "Done",
        "category": "done"
      },
      "priority": 2,
      "rank": "b0",
      "version": 9,
      "is_archived": false,
      "updated_at": "2026-08-29T10:19:44.902311Z"
    }
  ],
  "removed": [
    {
      "uuid": "00000000-0000-4000-8000-000000000102",
      "key": "ENG-77",
      "reason": "archived"
    }
  ],
  "states": null,
  "truncated": false,
  "poll_after_seconds": 15
}
```

#### Notes

- Scope: `tasks:read`.
- Rate limit: 240 delta polls 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/boards/{board_id}/views/` · Beta

**The caller's saved views for this board**

Your saved views for this board. Views are personal. Needs a person: agent and organization keys are refused; a personal API key works.

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

#### Path parameters

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

#### 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<SavedView> | Yes | The rows on this page. See [SavedView](#plan-boards-list-savedview). |

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

#### Example (curl)

```bash
curl -sS "https://api.dailybot.com/v1/plan/boards/00000000-0000-4000-8000-000000000002/views/" \
  -H "Authorization: Bearer $DAILYBOT_TOKEN"
```

#### Scenario examples

##### CLI

```bash
dailybot plan board views 00000000-0000-4000-8000-000000000002 --etag
```

##### Response

```bash
{
  "count": 1,
  "next": null,
  "previous": null,
  "results": [
    {
      "name": "My open work",
      "view_mode": "list",
      "group_by": "state",
      "sort": "-updated_at",
      "filters": {},
      "schema_version": 1,
      "visibility": "personal",
      "collapsed": {},
      "columns": {}
    }
  ]
}
```

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

### PUT `/v1/plan/boards/{board_id}/views/` · Beta

**Replace the caller's saved views for this board**

Replaces your whole saved-view array, which is why `If-Match` is required: without it, two concurrent saves would silently drop one another's view.

- **Auth:** 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 |
|------|------|----------|-------------|
| `If-Match` | string | Yes | The `ETag` you received from `GET .../views/`, quoted. **Required**, because this `PUT` replaces the whole array: without a precondition two concurrent saves silently drop one another's view. A stale validator is `412 precondition_failed`; a missing one is `428 precondition_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). |

#### Response body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `(body)` | array<SavedView> | Yes | A JSON array of [SavedView](#plan-boards-list-savedview) objects. |

#### 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. |
| `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. |
| `412` | The `If-Match` validator is stale (`precondition_failed`). Read again and retry. |
| `428` | `If-Match` is required (`precondition_required`). |

#### Example (curl)

```bash
curl -sS -X PUT "https://api.dailybot.com/v1/plan/boards/00000000-0000-4000-8000-000000000002/views/" \
  -H "Authorization: Bearer $DAILYBOT_TOKEN" \
  -H "If-Match: $VIEWS_ETAG" \
  -H "Content-Type: application/json" \
  -d '[
    {
      "name": "My open work",
      "view_mode": "board",
      "group_by": "state",
      "sort": "-updated_at",
      "filters": {
        "owner": [
          "me"
        ],
        "state": [
          "open"
        ]
      }
    }
  ]'
```

#### Scenario examples

##### CLI

```bash
dailybot plan board view save 00000000-0000-4000-8000-000000000002 -f views.json --fetch-etag
```

##### Response

```bash
[
  {
    "name": "My open work",
    "view_mode": "list",
    "group_by": "state",
    "sort": "-updated_at",
    "filters": {},
    "schema_version": 1,
    "visibility": "personal",
    "collapsed": {},
    "columns": {}
  }
]
```

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

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

**Search people mentionable on a board**

The roster for owner and participant pickers, searchable with `q` (name, handle or external id, never a whole email address). Do not use the members list for pickers: it only lists explicit grants and is often empty on organization-visible boards.

People who cannot see the board never appear, even when they match `q`, matching the rule that refuses them as owner or participant (`participant_cannot_access_board`). `limit` (default 25) and `offset` page the whole roster in a stable order.

Rows are `{uuid, name, handle, avatar_url, has_photo, kind}`, with no email. `avatar_url` and `has_photo` mean the same as on a task owner: when `has_photo` is `false`, show initials; on an `agent` row they are `null` and `false`. Key mention chips on `uuid`, never `handle`: `handle` is not unique within an organization, so show `name` to disambiguate. `kinds=agent` lists the workspace's agents, but an agent cannot be mentioned yet; do not build a mention from an `agent` row.

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

#### Path parameters

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

#### Query parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `q` | string | No | Matches name, handle or external id, never a whole email address. |
| `limit` | integer | No | Page size. Clamped to the maximum the response echoes; garbage is ignored rather than refused, because this is a type-ahead control and a 400 here would break the picker on a stray keystroke. |
| `offset` | integer | No | Rows to skip, over the deterministic `full_name, id` ordering — so a page boundary can neither drop nor repeat somebody. |
| `kinds` | string | No | Comma-separated `user`, `agent`. **Absent means users only**, so a caller that does not ask for agents sees exactly what it saw before. An unrecognised token is dropped, not refused. |

#### MentionableList object

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `limit` | integer | Yes | Page size applied. |
| `results` | array<Mentionable> | Yes | The rows on this page. See [Mentionable](#plan-board-mentionables-mentionable). |

#### Mentionable object

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `uuid` | uuid | Yes | The person's uuid. Key mention chips on it. |
| `name` | string | Yes | Display name. Show it to tell people with the same handle apart. |
| `handle` | string | null | Yes | Handle, if the person has one. Not unique within an organization. |
| `avatar_url` | string | null | Yes | Avatar image URL, the same as on a task owner. `null` on an agent row. |
| `has_photo` | boolean | Yes | `false` means there is no photo: show initials. Always `false` on an agent row. |
| `kind` | enum | Yes | Whether the row is a person or an agent. One of `user`, `agent`. |

#### Response body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `(body)` | MentionableList | Yes | A [MentionableList](#plan-board-mentionables-mentionablelist) object. |

#### 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. |
| `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 "https://api.dailybot.com/v1/plan/boards/00000000-0000-4000-8000-000000000002/mentionables/" \
  -H "Authorization: Bearer $DAILYBOT_TOKEN"
```

#### Scenario examples

##### CLI

```bash
dailybot plan board mentionables 00000000-0000-4000-8000-000000000002 -q ada
```

##### Response

```bash
{
  "limit": 25,
  "results": [
    {
      "uuid": "00000000-0000-4000-8000-00000000000c",
      "name": "Ada L.",
      "handle": "ada",
      "avatar_url": "https://example.com/avatars/ada.png",
      "has_photo": true,
      "kind": "user"
    },
    {
      "uuid": "00000000-0000-4000-8000-000000000012",
      "name": "Grace H.",
      "handle": null,
      "avatar_url": null,
      "has_photo": false,
      "kind": "user"
    }
  ]
}
```

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

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

**Members of a board**

Explicit membership grants only, never the full organization roster, so organization-visible boards often return an empty list. Use it to manage who may see a members-only board; for pickers use `…/mentionables/`. An organization admin can read this list without gaining sight of the board's tasks.

- **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

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

#### BoardMember object

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `subject_type` | enum | Yes | One of `user`, `team`. |
| `user_uuid` | uuid | null | No | The person's user uuid. |
| `uuid` | uuid | null | No | Stable public identifier. |
| `full_name` | string | No | — |
| `name` | string | No | Display name. |
| `role` | enum | null | No | Participant role. One of `admin`, `member`, `guest`. |
| `team_uuid` | uuid | null | No | A team's uuid, instead of `user_uuid`. It creates one live team grant: whoever joins the team later is in, and whoever leaves is out. |
| `team_name` | string | No | — |
| `added_at` | date-time | Yes | — |
| `added_by_uuid` | uuid | null | No | — |

#### 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<BoardMember> | Yes | The rows on this page. See [BoardMember](#plan-board-members-list-boardmember). |

#### 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/boards/00000000-0000-4000-8000-000000000002/members/" \
  -H "X-API-KEY: $DAILYBOT_API_KEY"
```

#### Scenario examples

##### CLI

```bash
dailybot plan board members 00000000-0000-4000-8000-000000000002
```

##### Response

```bash
{
  "count": 1,
  "next": null,
  "previous": null,
  "results": [
    {
      "subject_type": "user",
      "user_uuid": "00000000-0000-4000-8000-00000000000c",
      "uuid": "00000000-0000-4000-8000-00000000000c",
      "full_name": "Ada L.",
      "name": "Ada L.",
      "role": "admin",
      "team_uuid": null,
      "team_name": "example",
      "added_at": "2026-09-25T10:14:02Z",
      "added_by_uuid": null
    }
  ]
}
```

#### 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}/members/` · Beta

**Add a member to a board**

The deliberate, visible way to give a person (`user_uuid`) or a team (`team_uuid`) sight of a members-only board: send exactly one of them; both or neither is `400 invalid_filter_value`. A team grant is live: whoever joins the team later is in, and whoever leaves is out. It writes a `board.member_added` event the board's members can see. Adding an existing member returns `200` with the existing row. There are no board-level roles. Every non-guest member can call it (with a login session or a personal API key); an agent or organization key gets `403 insufficient_scope`.

- **Auth:** 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 |
|------|------|----------|-------------|
| `user_uuid` | uuid | No | The person's user uuid. |
| `team_uuid` | uuid | No | A team's uuid, instead of `user_uuid`. It creates one live team grant: whoever joins the team later is in, and whoever leaves is out. |
| `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)` | BoardMember | Yes | A [BoardMember](#plan-board-members-list-boardmember) object. |

#### Error codes

| Status | When |
|--------|------|
| `400` | Send exactly one of `user_uuid` and `team_uuid`; both or neither is `invalid_filter_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. |
| `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/boards/00000000-0000-4000-8000-000000000002/members/" \
  -H "Authorization: Bearer $DAILYBOT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "user_uuid": "00000000-0000-4000-8000-00000000000c"
  }'
```

#### Scenario examples

##### CLI

```bash
dailybot plan board member add 00000000-0000-4000-8000-000000000002 00000000-0000-4000-8000-00000000000c
```

#### Notes

- Scope: `tasks:admin` — container writes. A non-guest member can call it with a login session or a personal API key (a key with explicit Plan scopes needs `tasks:write`, which covers it); an agent or organization key gets `403 insufficient_scope`.
- 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/boards/{board_id}/members/{user_id}/` · Beta

**Remove a member from a board**

Emits `board.member_removed`. Removing the last member of a members-only board is refused with `409 last_grant_cannot_be_removed`, because a private board with no members would be readable by nobody. Needs a person: agent and organization keys are refused; a personal API key works.

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

#### Path parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `board_id` | string | Yes | The board's uuid. |
| `user_id` | string | Yes | The member'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. |
| `409` | This is the last member of a members-only board (`last_grant_cannot_be_removed`). |

#### Example (curl)

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

#### Scenario examples

##### CLI

```bash
dailybot plan board member remove 00000000-0000-4000-8000-000000000002 00000000-0000-4000-8000-00000000000c --yes
```

#### Notes

- Scope: `tasks:admin` — container writes. A non-guest member can call it with a login session or a personal API key (a key with explicit Plan scopes needs `tasks:write`, which covers it); an agent or organization key gets `403 insufficient_scope`.
- 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/boards/{board_id}/members/{user_id}/` · Beta

**Inspect a board membership grant (role is read-only)**

Board membership has no role column — org roles plus board visibility are the access model. Sending `role` returns `400`. An empty PATCH returns the current grant row.

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

#### Path parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `board_id` | string | Yes | The board's uuid. |
| `user_id` | string | Yes | The member'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). |

#### Response body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `(body)` | BoardMember | Yes | A [BoardMember](#plan-board-members-list-boardmember) 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. |

#### Example (curl)

```bash
curl -sS -X PATCH "https://api.dailybot.com/v1/plan/boards/00000000-0000-4000-8000-000000000002/members/00000000-0000-4000-8000-00000000000c/" \
  -H "Authorization: Bearer $DAILYBOT_TOKEN"
```

#### Notes

- Scope: `tasks:admin` — container writes. A non-guest member can call it with a login session or a personal API key (a key with explicit Plan scopes needs `tasks:write`, which covers it); an agent or organization key gets `403 insufficient_scope`.
- 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`.

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

**List organization labels (board access gate)**

The organization's labels, behind this board's access check, so board settings can manage them without leaving the Plan API. Apply labels to cards with task `PATCH`, the labels batch endpoint or bulk `set_labels`.

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

#### Path parameters

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

#### 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. |
| `search` | string | No | Case-insensitive substring match on the label name only. Empty means no filter; a value that matches nothing returns an empty list. |
| `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. |

#### Response body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `results` | array<Label> | Yes | The rows on this page. |

#### 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/boards/00000000-0000-4000-8000-000000000002/labels/" \
  -H "Authorization: Bearer $DAILYBOT_TOKEN"
```

#### Scenario examples

##### CLI

```bash
dailybot plan board labels 00000000-0000-4000-8000-000000000002
```

##### Response

```bash
{
  "count": 1,
  "next": null,
  "previous": null,
  "results": [
    {
      "uuid": "00000000-0000-4000-8000-00000000000b",
      "name": "backend",
      "color": "#2563eb"
    }
  ]
}
```

#### 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/boards/{board_id}/labels/` · Beta

**Create an organization label**

Creates an organization label from a board's settings, behind that board's access check. The label belongs to the organization, so every board can use it.

- **Auth:** 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 |
|------|------|----------|-------------|
| `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 |
|------|------|----------|-------------|
| `name` | string | Yes | Display name. |
| `color` | string | No | Display color (hex). |
| `description` | string | No | Free-form description. |
| `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)` | Label | Yes | A [Label](#plan-board-delta-label) 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. |

#### Example (curl)

```bash
curl -sS -X POST "https://api.dailybot.com/v1/plan/boards/00000000-0000-4000-8000-000000000002/labels/" \
  -H "Authorization: Bearer $DAILYBOT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "backend",
    "color": "#2563eb"
  }'
```

#### Scenario examples

##### CLI

```bash
dailybot plan board label create 00000000-0000-4000-8000-000000000002 -n backend --color "#2563eb"
```

##### Response

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

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

### GET `/v1/plan/views/{view_id}/` · Beta

**One saved view by uuid**

Readable when it is your own view, or a `shared` or `board_default` view on a board you can see. Anything else, including another person's personal view, is `404`, never `403`.

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

#### Path parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `view_id` | uuid | Yes | The saved view's uuid. |

#### Response body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `(body)` | SavedView | Yes | A [SavedView](#plan-boards-list-savedview) object. |

#### 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/views/{view_id}/" \
  -H "Authorization: Bearer $DAILYBOT_TOKEN"
```

#### Scenario examples

##### CLI

```bash
dailybot plan tasks view get 00000000-0000-4000-8000-000000000010 --json
```

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

### PATCH `/v1/plan/views/{view_id}/` · Beta

**Edit one saved view**

Partial: only the fields you send change; unknown fields are refused. Making a view `shared` or `board_default`, or editing one that already is, needs a board manager; otherwise `403 view_visibility_forbidden`.

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

#### Path parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `view_id` | uuid | Yes | The saved view'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 |
|------|------|----------|-------------|
| `name` | string | No | Display name. Max 64 characters. |
| `view_mode` | enum | No | How the filtered set is drawn. `kanban` is accepted as an alias of `board`. One of `list`, `board`, `kanban`, `timeline`, `calendar`. |
| `group_by` | enum | No | The grouping dimension. One of `state`, `owner`, `priority`, `category`. |
| `sort` | string | No | A sort key, `-` prefixed for descending. |
| `filters` | object | No | The view's filters, in the shared task filter grammar. |
| `schema_version` | integer | No | Version of the view's stored format. |
| `visibility` | enum | No | `personal`, `shared` or `board_default`. `shared` and `board_default` need a board manager on board views and project oversight (an organization admin or a manager of all teams) on project views; otherwise `403 view_visibility_forbidden`. One of `personal`, `shared`, `board_default`. |
| `collapsed` | object | array | string | number | boolean | No | Client UI state stored verbatim (which groups are collapsed). Only size and depth are validated. |
| `columns` | object | array | string | number | boolean | No | Client UI state stored verbatim (which columns are shown). Only size and depth are validated. |
| `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)` | SavedView | Yes | A [SavedView](#plan-boards-list-savedview) 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. |
| `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 PATCH "https://api.dailybot.com/v1/plan/views/{view_id}/" \
  -H "Authorization: Bearer $DAILYBOT_TOKEN"
```

#### Scenario examples

##### CLI

```bash
dailybot plan tasks view update 00000000-0000-4000-8000-000000000010 --view-mode kanban --group-by owner
```

#### 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/views/{view_id}/` · Beta

**Delete one saved view**

Permanent. Deleting a `shared` or `board_default` view needs a board manager; otherwise `403 view_visibility_forbidden`.

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

#### Path parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `view_id` | uuid | Yes | The saved view'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. |
| `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/views/{view_id}/" \
  -H "Authorization: Bearer $DAILYBOT_TOKEN"
```

#### Scenario examples

##### CLI

```bash
dailybot plan tasks view delete 00000000-0000-4000-8000-000000000010 --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`.

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

**List a board's attachments**

The board's ready attachments, ordered by position, as a page. Anyone who can see the board can list them; a board you cannot see is `404`. Each `url` is a download link: do not store it, keep the attachment `uuid` and read it again when you need the file.

- **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

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

#### TaskAttachment object

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `uuid` | uuid | Yes | Stable public identifier. |
| `filename` | string | Yes | File name. |
| `content_type` | string | Yes | MIME type. |
| `size` | integer | Yes | Size in bytes. |
| `url` | string | Yes | Where to download the file. |
| `thumbnail_url` | uri | null | No | Thumbnail for images. |
| `width` | integer | null | No | — |
| `height` | integer | null | No | — |
| `status` | enum | Yes | Current status. One of `pending`, `ready`, `scanning`, `rejected`. |
| `uploaded_by` | ActorRef | null | No | Who uploaded the file. See [ActorRef](#plan-board-attachments-list-actorref). |
| `executed_by_agent` | object | null | No | The agent that executed this on behalf of the person, or `null` when no agent was named: an object with `uuid`, `name`, `username` and `avatar`. The person in the author field is still the author; the agent is shown as the one who executed it. |
| `created_at` | date-time | Yes | When the row was created. |

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

#### 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<TaskAttachment> | Yes | The page of [TaskAttachment](#plan-board-attachments-list-taskattachment) objects. |

#### 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` | The board does not exist or you cannot see it (`not_found`), never a 403. |

#### Example (curl)

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

#### Scenario examples

##### Response

```bash
{
  "count": 1,
  "next": null,
  "previous": null,
  "results": [
    {
      "uuid": "00000000-0000-4000-8000-000000000009",
      "filename": "roadmap.pdf",
      "content_type": "application/pdf",
      "size": 48213,
      "url": "https://media.dailybot.com/\u2026",
      "url_expires_at": null,
      "content_url": "/v1/plan/boards/00000000-0000-4000-8000-000000000002/attachments/00000000-0000-4000-8000-000000000009/content/",
      "thumbnail_url": null,
      "width": null,
      "height": null,
      "status": "ready",
      "uploaded_by": {
        "kind": "user",
        "uuid": "00000000-0000-4000-8000-000000000001",
        "name": "Ana"
      },
      "created_at": "2026-09-30T14:00:00Z"
    }
  ]
}
```

#### 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}/attachments/` · Beta

**Upload an attachment to a board**

Attach a file to a board in one request. Send `multipart/form-data` with the `file` field and an optional `caption`; there is no presign flow here. The limit is **5 MiB**: a larger file is `400 attachment_too_large`, with `extra.max_size_bytes`. The file type is checked from its content, with the same policy as project attachments (`400 attachment_invalid_type`).

- **Auth:** 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 |
|------|------|----------|-------------|
| `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 |
|------|------|----------|-------------|
| `file` | binary | Yes | The file, as a multipart part. |
| `caption` | string | No | Optional caption, max 255 characters. |

#### Response body

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

#### Error codes

| Status | When |
|--------|------|
| `400` | The file is missing, too large (`attachment_too_large`) or of a refused type (`attachment_invalid_type`), or the board holds the maximum number of attachments (`attachment_limit_reached`). `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 are not an organization administrator (`insufficient_scope`), or you are a guest (`guest_not_allowed`). An agent or organization key is refused here too. |
| `404` | The board does not exist or you cannot see it (`not_found`), never a 403. |

#### Example (curl)

```bash
curl -sS -X POST "https://api.dailybot.com/v1/plan/boards/00000000-0000-4000-8000-000000000002/attachments/" \
  -H "Authorization: Bearer $DAILYBOT_TOKEN" \
  -F "file=@./roadmap.pdf" \
  -F "caption=Q4 roadmap"
```

#### Notes

- Scope: `tasks:admin` — container writes. A non-guest member can call it with a login session or a personal API key (a key with explicit Plan scopes needs `tasks:write`, which covers it); an agent or organization key gets `403 insufficient_scope`.
- 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`.

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

**Retrieve a board attachment**

One attachment of the board. Anyone who can see the board can read it.

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

#### Path parameters

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

#### Response body

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

#### 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` | The board or the attachment does not exist or you cannot see it (`not_found`), never a 403. |

#### Example (curl)

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

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

### GET `/v1/plan/boards/{board_id}/attachments/{attachment_id}/content/` · Beta

**Download a board attachment's bytes**

The file bytes, with the recorded content type, through the API rather than the media link. An attachment whose upload is not complete yet is `409 attachment_not_ready`.

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

#### Path parameters

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

#### Response body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `(body)` | binary | Yes | The file bytes; `Content-Type` is the attachment's. |

#### 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` | The board or the attachment does not exist or you cannot see it (`not_found`), never a 403. |
| `409` | The attachment is not ready yet (`attachment_not_ready`). |

#### Example (curl)

```bash
curl -sS "https://api.dailybot.com/v1/plan/boards/00000000-0000-4000-8000-000000000002/attachments/00000000-0000-4000-8000-000000000009/content/" \
  -H "X-API-KEY: $DAILYBOT_API_KEY" \
  -o roadmap.pdf
```

#### 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/boards/{board_id}/attachments/{attachment_id}/` · Beta

**Rename a board attachment**

Changes the display file name; the stored bytes do not change.

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

#### Path parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `board_id` | string | Yes | The board's uuid. |
| `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-board-attachments-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 are not an organization administrator (`insufficient_scope`), or you are a guest (`guest_not_allowed`). An agent or organization key is refused here too. |
| `404` | The board 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/boards/00000000-0000-4000-8000-000000000002/attachments/00000000-0000-4000-8000-000000000009/" \
  -H "Authorization: Bearer $DAILYBOT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "filename": "roadmap-q4.pdf"
}'
```

#### Notes

- Scope: `tasks:admin` — container writes. A non-guest member can call it with a login session or a personal API key (a key with explicit Plan scopes needs `tasks:write`, which covers it); an agent or organization key gets `403 insufficient_scope`.
- 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/boards/{board_id}/attachments/{attachment_id}/` · Beta

**Remove an attachment from a board**

Removes the attachment from the board. Answers `204`.

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

#### Path parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `board_id` | string | Yes | The board's uuid. |
| `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). |

#### 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 are not an organization administrator (`insufficient_scope`), or you are a guest (`guest_not_allowed`). An agent or organization key is refused here too. |
| `404` | The board or the attachment does not exist or you cannot see it (`not_found`), never a 403. |

#### Example (curl)

```bash
curl -sS -X DELETE "https://api.dailybot.com/v1/plan/boards/00000000-0000-4000-8000-000000000002/attachments/00000000-0000-4000-8000-000000000009/" \
  -H "Authorization: Bearer $DAILYBOT_TOKEN"
```

#### Notes

- Scope: `tasks:admin` — container writes. A non-guest member can call it with a login session or a personal API key (a key with explicit Plan scopes needs `tasks:write`, which covers it); an agent or organization key gets `403 insufficient_scope`.
- 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`.

---

## 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) (this page)
- [Plan · Tasks](/developers/api/plan-tasks)
- [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)

