Skip to content
view raw .md

Conventions for Plan

Pagination, the shared filter grammar, sorting, include, errors, rate limits, idempotency, concurrency and conditional reads in the Dailybot Plan API (Beta).

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 [email protected].

The Plan API follows the conventions shared by every Dailybot API and adds a few of its own: a filter grammar shared by lists, boards and the timeline; Idempotency-Key on creates; If-Match for concurrent edits; and ETag / 304 on the heaviest reads. This page covers each one once.

Pagination

Lists use pages. Send page (1-based) and page_size, or the aliases limit and offset:

curl -sS "https://api.dailybot.com/v1/plan/tasks/?board=ENG&page=2&page_size=100" \
  -H "Authorization: Bearer $DAILYBOT_TOKEN"

Every list answers the same envelope:

{
  "count": 152,
  "next": "https://api.dailybot.com/v1/plan/tasks/?board=ENG&page=3&page_size=100",
  "previous": "https://api.dailybot.com/v1/plan/tasks/?board=ENG&page=1&page_size=100",
  "results": []
}
  • page_size defaults to 50 and goes up to 100. Larger values are clamped, never rejected: asking for 500 returns 100.
  • The board snapshot returns at most 50 tasks per column, because it carries labels for every card.
  • Follow next until it is null. results is always an array.

The board delta feed is not a page: it returns a cursor, not next. See the delta feed reference.

Filters

The task list, the board snapshot and the timeline share one filter grammar, so a saved view works on all three. Repeating a parameter is OR; different parameters are AND: ?board=ENG&board=OPS&state=open means “open tasks on ENG or OPS”.

Parameter Accepts
board Board uuids or keys (ENG), repeatable
project · goal · team uuids, repeatable. goal matches a task’s own goal or the one it inherits from its project
state State uuids, or the shortcuts open (backlog, todo, in_progress) and done (done, canceled)
category backlog, todo, in_progress, done, canceled
priority 1 urgent to 5 none, repeatable
owner A user uuid, me or unowned, repeatable: owner=me&owner=unowned is your tasks plus the unowned ones
participant · created_by User uuids, repeatable
label Label uuids, repeatable (up to 50)
due_before · due_after · start_before · start_after · completed_before · completed_after ISO dates, inclusive
has_due_date · has_start_date · has_dates · blocked true or false
estimate_min · estimate_max Integers
search Text matched on title and key (up to 256 characters)
updated_since ISO timestamp
is_archived · include_archived true or false: only archived rows, or archived and live together

Two spellings to remember:

  • Overdue is due_before=<today>&state=open. There is no state=overdue: it answers 400 invalid_filter_value.
  • Actionable blocked work is blocked=true&state=open. A finished task can still carry a live blocker, so blocked=true alone also returns done work.

An unknown parameter is ignored on the task list, but the board snapshot and GET /v1/plan/activity/ accept only the parameters they document and refuse anything else with 400 invalid_filter_value. A value the API cannot parse is 400 invalid_filter_value everywhere. owner=me with an organization API key is 400 actor_required (see Authentication for Plan).

Sorting and include

sort takes one field, - prefixed for descending (sort=-updated_at). Every ordering adds a stable tiebreak, so a row never appears on two pages. An unsupported value is 400 invalid_sort, never a silent fallback.

Some reads embed extra data on request with include, a comma-separated list. Each endpoint documents its tokens, for example:

Endpoint include tokens
GET /v1/plan/tasks/{task_id}/ children, relations, participants, attachments, comment_count, activity, comments
GET /v1/plan/projects/ progress
GET /v1/plan/goals/ progress, projects
GET /v1/plan/pulse/ projects, attention, activity, goal_progress

An unknown token is 400 invalid_filter_value; an empty include= is ignored.

Errors

Errors carry a human-readable detail and, for anything you might branch on, a stable machine-readable code:

{
  "detail": "This task changed since you loaded it.",
  "code": "version_conflict",
  "extra": { "current_version": 9 }
}

Branch on code, never on detail. Validation errors on a body list messages per field. A missing object and an object from another organization return the same 404 body. Every code, with what to do next, is in Errors for Plan.

Rate limits

Limits apply per actor (a person, or an organization API key), per minute:

Calls Limit
Reads 120 per minute
Writes 60 per minute
Bulk calls 30 per minute
Board delta feed 240 per minute

Past a limit, the API answers 429 with a Retry-After header: wait that many seconds before retrying. For a live board, poll the delta feed at the poll_after_seconds it suggests rather than on a fixed timer.

Idempotency

Creates and many writes accept an Idempotency-Key header (each endpoint’s Headers table says so): a unique string you generate for one intent.

curl -sS -X POST "https://api.dailybot.com/v1/plan/tasks/" \
  -H "Authorization: Bearer $DAILYBOT_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: import-row-42" \
  -d '{"board": "ENG", "title": "Write the migration guide"}'
  • Sending the same key with the same body again returns the first response, performs nothing new, and adds the header Idempotency-Replayed: true. Retry with confidence after a timeout.
  • Sending the same key with a different body is 409 idempotency_key_payload_mismatch: a key names one intent.
  • A repeat sent while the first call is still running gets 409 idempotency_in_progress for up to 120 seconds.
  • Keys are remembered for 24 hours.
  • Bulk requires it: POST /v1/plan/tasks/bulk/ without a key is 400 idempotency_key_required.

Concurrent edits

Every task has an integer version. To avoid overwriting someone else’s change, send the version you loaded in If-Match (quoted) or as the body field version:

curl -sS -X PATCH "https://api.dailybot.com/v1/plan/tasks/ENG-142/" \
  -H "Authorization: Bearer $DAILYBOT_TOKEN" \
  -H "Content-Type: application/json" \
  -H 'If-Match: "7"' \
  -d '{"due_date": "2026-10-22"}'
  • If the task moved on, the answer is 409 version_conflict with extra.current_version: re-read, reconcile, retry.
  • Sending both If-Match and version with different values is 400 version_precondition_ambiguous.
  • Without either, the last write wins.
  • Today task update, move and move-to-board check versions.

Saved views are different: PUT …/views/ replaces your whole list, so it requires If-Match with the ETag from your last read. A stale value is 412 precondition_failed; a missing one is 428 precondition_required.

Conditional reads

The board snapshot, task detail and the home pulse return an ETag. Send it back in If-None-Match; if nothing changed, the API answers 304 Not Modified with an empty body, so you skip parsing a response you already have:

curl -sS -i "https://api.dailybot.com/v1/plan/boards/$BOARD/board/" \
  -H "Authorization: Bearer $DAILYBOT_TOKEN" \
  -H "If-None-Match: $ETAG"

Previews with dry_run

Archive endpoints, milestone completion and bulk calls accept ?dry_run=true. The API computes the consequence and returns it without writing anything, including consequence, a sentence meant to be shown to a person before they confirm:

curl -sS -X POST "https://api.dailybot.com/v1/plan/boards/$BOARD/archive/?dry_run=true" \
  -H "Authorization: Bearer $DAILYBOT_TOKEN"

Agent attribution

When an agent works through a person’s credential, the person is the author of every write and the agent is shown as the one who executed it on their behalf. Name the agent on each write:

Write How to send the name
JSON body The body field agent_name (canonical)
Multipart, or no body (DELETE, archive, restore) The header X-Dailybot-Agent-Name, with the value percent-encoded as UTF-8
curl -sS -X POST "https://api.dailybot.com/v1/plan/tasks/ENG-142/comments/" \
  -H "Authorization: Bearer $DAILYBOT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"body": "Migration guide drafted.", "agent_name": "Release agent"}'
  • If both are present, the body wins. Control characters are stripped and a blank name means no agent.
  • The maximum is 128 characters. A longer or undecodable name is 400 invalid_agent_attribution; it is never truncated.
  • Every mutating /v1/plan/ endpoint (POST, PUT, PATCH, DELETE) accepts it. GET requests ignore it.
  • An agent-type key, which is not bound to a person, gets 400 invalid_agent_attribution if it sends a name.
  • The stamp never changes a permission answer.
  • The name resolves against the same agent registry as /v1/agent-reports/ (name, aliases, avatar).
  • A name may use only letters, numbers, spaces and . - _ ( ) ' # + / & , :. Anything else, and the name of a deactivated agent, is 400 invalid_agent_attribution.
  • A comment written through an API key or stamped with an agent has provenance: agent_authored.

In responses, comments, attachments and activity items carry executed_by_agent ({uuid, name, username, avatar}, or null) next to the author or actor. A task gains executors, a list of {uuid, name, username, avatar, first_at, last_at}, newest first. It is separate from the singular executor, which stays the current ball-holder.

From the Dailybot CLI (4.0.0 and later): pass --agent-name (or set DAILYBOT_AGENT_NAME; the flag wins). With neither, a person is acting directly and nothing is stamped. It is an attribution label, never a credential, so it changes no permission. The CLI sends agent_name on JSON writes and the header on uploads and body-less writes, never on reads, and refuses names over 128 characters locally.

dailybot --agent-name "Claude Code" plan task comment ENG-12 "Reproduced and fixed"

task comments shows Jane Doe via "Claude Code", and task get adds an Agents line listing the executors, most recent first. task brief [--download DIR] [--force] [--json] reads a whole card for an agent, attachments included.

Additive changes

New fields, parameters, event types, relation types and state categories can appear at any time during the Beta. Ignore values you do not recognise, and follow the API changelog.