Skip to content
view raw .md

Dailybot Plan API

Plan and track work through the Dailybot Plan API (Beta): projects, boards, tasks and goals over one REST API, with the concepts you need before your first call.

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.

request beta access

Dailybot Plan is where a team plans and tracks its work: projects, boards, tasks and goals. Plan is agent-first: almost everything you can do in the /plan web app is reachable through the same public API, the Dailybot CLI (dailybot plan …) and the agent skill. There is no private API behind the product surface.

All three surfaces call https://api.dailybot.com/v1/plan/. If the web app can do it, your integration, CLI script or agent can do it too — with the Phase 2 limits called out on this page (delegation still answers 501).

This page explains the model once. Every other Plan page links back here.

Who can do what

Every authenticated non-guest member can use the whole Plan API: create and manage goals, projects, boards, states and memberships. A personal API key acts as its person and can do everything that person can do, so a member’s key needs no scope grant and no organization-admin role. Guests are refused before entitlement (403 guest_not_allowed), with a key or a session.

Privacy is invite / membership, not org role. Org-wide projects and boards are a shared workspace. A members container is 404 (not visible) without a grant. Invite a person or a team to share; the last grant on a private container is 409 last_grant_cannot_be_removed. There are no per-project roles (lead/viewer) — membership is a grant, not a role ladder.

Oversight: organization admins and managers of all teams can see every project. A members board still needs an explicit grant.

Agent and organization keys have no person behind them: they see organization-visible boards only and are refused (403 insufficient_scope) on the endpoints that need a person. Full detail: Authentication and scopes for Plan.

Start here

Recipes

Check that Plan is enabled for your organization

Plan is in Beta and is enabled per organization. GET /v1/plan/entitlements/ is the one Plan endpoint that answers even when your organization is not enabled yet, so call it first:

curl -sS "https://api.dailybot.com/v1/plan/entitlements/" \
  -H "Authorization: Bearer $DAILYBOT_TOKEN"
{
  "enabled": false,
  "reason": "rollout",
  "boards": { "used": 0, "limit": 3 },
  "projects": { "used": 0, "limit": 1 },
  "labels": { "enabled": true }
}

enabled: false with reason: "rollout" means your organization is not in the Beta yet. Every other Plan endpoint answers 402 plan_upgrade_required until it is: that is expected. Write to support@dailybot.com to join.

The model: organization, project, board, task

Organization
└── Project            what a body of work is (health, status notes, milestones)
    └── Board          where the work is tracked; its columns are workflow states
        └── Task       one unit of work, addressed as ENG-142

A board belongs to a project and a task belongs to a board. Tasks can have sub-tasks (one level deep), relations to other tasks (blocks, relates_to, duplicates), an owner, participants, labels, comments and attachments.

Workflow states and their five categories

A board’s columns are its workflow states. You can name them anything, but every state has one of five fixed categories, and the category is what answers “is this finished?” on any board:

Category Meaning Counts as
backlog Not planned yet open
todo Planned, not started open
in_progress Being worked on open
done Finished done
canceled Will not be done done

The state filter accepts two shortcuts built on these categories: open (backlog, todo, in_progress) and done (done, canceled). “Blocked” is not a category: it is derived from relations, so filter with blocked=true.

Goals point at work; they do not contain it

A goal says what the work is for, with a period (period_start, period_end) and a declared status. Nothing lives inside a goal. A project can point at several goals and a goal can be served by several projects, so archiving a goal leaves every project where it was.

A task can point at its own goal. When it does not, it inherits its project’s goal, and filters and progress roll-ups apply that same rule.

Goal progress is scoped to you: it counts only the tasks you can see, and is_partial: true tells you when some of the goal’s work is hidden from you. Never present it as an organization-wide number.

There is no dedicated goals-review endpoint. Build a review client-side with GET /v1/plan/goals/?include=progress (and projects when you need the linked work). See the goal progress recipe.

Identifiers: uuid and KEY-n

Every object has a uuid. A task also has a human-readable key, KEY-n, such as ENG-142: ENG is the board’s key and 142 is a per-board counter. Both work wherever a task is addressed, for example GET /v1/plan/tasks/ENG-142/.

A board’s key can be renamed and old keys keep resolving, so a link written last year still opens the card. Keys are never reused, even after a task or board is archived. Numeric ids are never accepted.

An identifier that does not exist and an identifier from another organization return the same 404 body, so the API never reveals whether something exists outside your organization.

Ordering: move relative to neighbours

Cards in a column are ordered by an opaque rank. Never compute a rank. Place a card relative to its neighbours instead: POST /v1/plan/tasks/{task_id}/move/ takes a target state and at most one of after / before (a task in that column). Send neither to append at the end. Two people dragging the same card at the same time both produce a valid order.

Moving is the only way to change a task’s state.

Versions and concurrent edits

Every task carries an integer version that increments on each write. To make sure you do not overwrite somebody else’s edit, send the version you loaded in the If-Match header (or as the body field version) when you update, move, archive or restore a task (and when you move it to another board). If the task changed meanwhile, the API answers 409 version_conflict with the current version in extra.current_version, so you can re-read and decide. Task detail also returns an ETag: send it back as If-None-Match to get 304 Not Modified when nothing changed.

Without If-Match on a write, the last write wins. Core writes also accept an Idempotency-Key so a retry after a timeout does not double-apply; bulk create requires the header. Details: Conventions for Plan.

Archive is the delete

No public endpoint hard-deletes a task, a board or a project. DELETE on a task is a soft archive (the same as the archive endpoint), and it is reversible:

  • Archiving a project cascades to its boards and their tasks; archiving a board cascades to its tasks; archiving a task archives its sub-tasks.
  • Restore walks back up, never down: restoring a project does not restore the boards it archived, because the API cannot tell them apart from boards archived on purpose. Restore each one you want back.
  • Tasks, boards, projects, goals and workflow states each have a …/restore/ endpoint.
  • The DELETE endpoints for tasks and milestones are aliases that soft-archive — they do not permanently remove the row.
  • Archived rows stay readable: lists hide them unless you pass include_archived=true, and a task stays readable by key or uuid.
  • Board keys stay reserved through archive and restore.

The archive endpoints, milestone completion and bulk calls accept ?dry_run=true, which returns the consequence (including a sentence to show a person) without writing anything.

Mentions

To mention someone in a comment or a project update, write <@DB@{uuid}>, using the person’s uuid from GET /v1/plan/boards/{board_id}/mentionables/:

Looks good. <@DB@00000000-0000-4000-8000-00000000000c> can you review the rollout plan?

Responses render mentions as display text in body and list the people in mentions[]: read them from there, never by parsing body. A uuid that names nobody in your organization is removed from the text. Comments support one level of threading through parent_comment.

Ignore values you do not recognise

Event types, relation types, state categories and the list of webhook events are additive: new values arrive in minor releases. A client that treats an unknown value as an error will break on a release that changed nothing for it. Skip what you do not know.

Phase 2 limits (by design)

These doors are not fully available yet. Document them honestly so agents do not assume a full runtime:

Topic Contract today
Task delegation (delegate / handback / revoke) API answers 501 until a separate agent-runtime plan ships
author_kind=agent Never produced from an organization or agent API key. The person credential stays the author; stamp the runner with agent_name / --agent-name and read executed_by_agent on the response
Goals review Client-only: compose GET /v1/plan/goals/?include=progress — no /goals/review/ door
Structure CRUD (projects, boards, states, memberships) Needs a person credential (login session or personal API key). An org/agent key alone is refused on person-only endpoints

Rate limits per actor per minute: reads 120, writes 60, bulk 30, board delta 240. Attachment downloads use relative content_url paths under /v1/plan/…/content/ — never bare http://localhost/media/… URLs. Full tables: Conventions and Agents on Plan.

What Beta means here

Paths, fields and behaviour described on these pages can change before general availability; we announce changes in the API changelog. If something you rely on is missing or unclear, write to support@dailybot.com.