Convenciones de Plan
Paginación, la gramática de filtros compartida, ordenamiento, include, errores, límites de solicitudes, idempotencia, concurrencia y lecturas condicionales en la API de Dailybot Plan (Beta).
Beta
Plan está en beta. Todo lo que está bajo /plan en la aplicación web, los comandos del CLI y de la agent skill para proyectos, objetivos, tableros y tareas, y la API pública /v1/plan/ puede cambiar antes de la disponibilidad general. ¿Quieres probarlo con tu equipo? Escribe a [email protected].
La API de Plan sigue las convenciones compartidas por todas las API de Dailybot y suma algunas propias: una gramática de filtros que comparten las listas, los tableros y el timeline; Idempotency-Key al crear; If-Match para ediciones concurrentes; y ETag / 304 en las lecturas más pesadas. Esta página explica cada una una sola vez.
Paginación
Las listas usan páginas. Envía page (empieza en 1) y page_size, o los alias limit y offset:
curl -sS "https://api.dailybot.com/v1/plan/tasks/?board=ENG&page=2&page_size=100" \
-H "Authorization: Bearer $DAILYBOT_TOKEN"
Todas las listas responden con el mismo envoltorio:
{
"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_sizevale 50 por defecto y llega hasta 100. Los valores mayores se ajustan al máximo, nunca se rechazan: si pides 500, recibes 100.- La instantánea del tablero devuelve como máximo 50 tareas por columna, porque incluye las etiquetas de cada tarjeta.
- Sigue
nexthasta que seanull.resultssiempre es un arreglo.
El feed de cambios del tablero no es una página: devuelve un cursor, no next. Consulta la referencia del feed de cambios.
Filtros
La lista de tareas, la instantánea del tablero y el timeline comparten una misma gramática de filtros, así que una vista guardada funciona en los tres. Repetir un parámetro es OR; parámetros distintos son AND: ?board=ENG&board=OPS&state=open significa “tareas abiertas en ENG u OPS”.
| Parámetro | Acepta |
|---|---|
board |
uuids o claves de tablero (ENG), repetible |
project · goal · team |
uuids, repetible. goal coincide con el objetivo propio de la tarea o con el que hereda de su proyecto |
state |
uuids de estado, o los atajos open (backlog, todo, in_progress) y done (done, canceled) |
category |
backlog, todo, in_progress, done, canceled |
priority |
De 1 urgente a 5 ninguna, repetible |
owner |
Un uuid de usuario, me o unowned, repetible: owner=me&owner=unowned son tus tareas más las que no tienen responsable |
participant · created_by |
uuids de usuario, repetible |
label |
uuids de etiqueta, repetible (hasta 50) |
due_before · due_after · start_before · start_after · completed_before · completed_after |
Fechas ISO, inclusivas |
has_due_date · has_start_date · has_dates · blocked |
true o false |
estimate_min · estimate_max |
Enteros |
search |
Texto que se busca en el título y la clave (hasta 256 caracteres) |
updated_since |
Marca de tiempo ISO |
is_archived · include_archived |
true o false: solo filas archivadas, o archivadas y activas juntas |
Dos formas de escribirlo que conviene recordar:
- Vencido es
due_before=<today>&state=open. No existestate=overdue: responde400 invalid_filter_value. - Trabajo bloqueado sobre el que se puede actuar es
blocked=true&state=open. Una tarea terminada puede seguir teniendo un bloqueo activo, así queblocked=truepor sí solo también devuelve trabajo terminado.
En la lista de tareas, un parámetro desconocido se ignora, pero la instantánea del tablero y GET /v1/plan/activity/ solo aceptan los parámetros que documentan y rechazan cualquier otro con 400 invalid_filter_value. Un valor que la API no puede interpretar es 400 invalid_filter_value en todos los casos. owner=me con una API key de la organización es 400 actor_required (consulta Autenticación en Plan).
Ordenamiento e include
sort recibe un solo campo, con el prefijo - para orden descendente (sort=-updated_at). Todo ordenamiento agrega un desempate estable, así que una fila nunca aparece en dos páginas. Un valor no soportado es 400 invalid_sort, nunca un respaldo silencioso.
Algunas lecturas incluyen datos adicionales si los pides con include, una lista separada por comas. Cada endpoint documenta sus tokens, por ejemplo:
| Endpoint | Tokens de include |
|---|---|
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 |
Un token desconocido es 400 invalid_filter_value; un include= vacío se ignora.
Errores
Los errores traen un detail legible para personas y, para todo lo que podrías usar en una bifurcación, un code estable y legible por máquinas:
{
"detail": "This task changed since you loaded it.",
"code": "version_conflict",
"extra": { "current_version": 9 }
}
Bifurca según code, nunca según detail. Los errores de validación en un cuerpo listan mensajes por campo. Un objeto inexistente y un objeto de otra organización devuelven el mismo cuerpo 404. Cada código, con lo que debes hacer a continuación, está en Errores de Plan.
Límites de solicitudes
Los límites se aplican por actor (una persona o una API key de la organización), por minuto:
| Llamadas | Límite |
|---|---|
| Lecturas | 120 por minuto |
| Escrituras | 60 por minuto |
| Llamadas masivas | 30 por minuto |
| Feed de cambios del tablero | 240 por minuto |
Si superas un límite, la API responde 429 con un encabezado Retry-After: espera esa cantidad de segundos antes de reintentar. Para un tablero en vivo, consulta el feed de cambios con el poll_after_seconds que sugiere, en lugar de hacerlo con un temporizador fijo.
Idempotencia
Las creaciones y muchas escrituras aceptan un encabezado Idempotency-Key (lo indica la tabla Headers de cada endpoint): una cadena única que generas para una intención.
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"}'
- Si vuelves a enviar la misma clave con el mismo cuerpo, recibes la primera respuesta, no se ejecuta nada nuevo y se agrega el encabezado
Idempotency-Replayed: true. Reintenta con tranquilidad después de un timeout. - Enviar la misma clave con un cuerpo distinto es
409 idempotency_key_payload_mismatch: una clave nombra una sola intención. - Una repetición enviada mientras la primera llamada sigue en curso recibe
409 idempotency_in_progressdurante hasta 120 segundos. - Las claves se recuerdan durante 24 horas.
- Las operaciones masivas la exigen:
POST /v1/plan/tasks/bulk/sin clave es400 idempotency_key_required.
Ediciones concurrentes
Cada tarea tiene un version entero. Para no sobrescribir el cambio de otra persona, envía la versión que cargaste en If-Match (entre comillas) o en el campo del cuerpo 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"}'
- Si la tarea cambió, la respuesta es
409 version_conflictconextra.current_version: vuelve a leer, concilia y reintenta. - Enviar
If-Matchyversioncon valores distintos es400 version_precondition_ambiguous. - Sin ninguno de los dos, gana la última escritura.
- Hoy verifican versiones la actualización de tareas, el movimiento y el movimiento a otro tablero.
Las vistas guardadas funcionan distinto: PUT …/views/ reemplaza toda tu lista, así que exige If-Match con el ETag de tu última lectura. Un valor desactualizado es 412 precondition_failed; si falta, es 428 precondition_required.
Lecturas condicionales
La instantánea del tablero, el detalle de la tarea y el pulse de inicio devuelven un ETag. Envíalo de vuelta en If-None-Match; si nada cambió, la API responde 304 Not Modified con el cuerpo vacío, así evitas procesar una respuesta que ya tienes:
curl -sS -i "https://api.dailybot.com/v1/plan/boards/$BOARD/board/" \
-H "Authorization: Bearer $DAILYBOT_TOKEN" \
-H "If-None-Match: $ETAG"
Vistas previas con dry_run
Los endpoints de archivado, la finalización de hitos y las llamadas masivas aceptan ?dry_run=true. La API calcula la consecuencia y la devuelve sin escribir nada, incluido consequence, una frase pensada para mostrársela a una persona antes de que confirme:
curl -sS -X POST "https://api.dailybot.com/v1/plan/boards/$BOARD/archive/?dry_run=true" \
-H "Authorization: Bearer $DAILYBOT_TOKEN"
Atribución de agente
Cuando un agente trabaja con la credencial de una persona, la persona es la autora de cada escritura y el agente se muestra como quien la ejecutó en su nombre. Nombra al agente en cada escritura:
| Escritura | Cómo enviar el nombre |
|---|---|
| Cuerpo JSON | El campo agent_name del cuerpo (canónico) |
Multipart, o sin cuerpo (DELETE, archivar, restaurar) |
El header X-Dailybot-Agent-Name, con el valor codificado con percent-encoding (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": "Guía de migración redactada.", "agent_name": "Agente de releases"}'
- Si vienen los dos, gana el cuerpo. Los caracteres de control se eliminan y un nombre vacío significa sin agente.
- El máximo es de 128 caracteres. Un nombre más largo o imposible de decodificar es
400 invalid_agent_attribution; nunca se trunca. - Todo endpoint de
/v1/plan/que modifica datos (POST,PUT,PATCH,DELETE) lo acepta. LosGETlo ignoran. - Una key de tipo agente, que no está ligada a una persona, recibe
400 invalid_agent_attributionsi envía un nombre. - El sello nunca cambia una respuesta de permisos.
- El nombre se resuelve contra el mismo registro de agentes que
/v1/agent-reports/(nombre, alias, avatar). - Un nombre solo puede usar letras, números, espacios y
. - _ ( ) ' # + / & , :. Cualquier otra cosa, y el nombre de un agente desactivado, es400 invalid_agent_attribution. - Un comentario escrito con una API key o sellado con un agente tiene
provenance: agent_authored.
En las respuestas, los comentarios, adjuntos y elementos de actividad llevan executed_by_agent ({uuid, name, username, avatar}, o null) junto al autor o actor. Una tarea gana executors, una lista de {uuid, name, username, avatar, first_at, last_at}, del más reciente al más antiguo. Es distinta del executor singular, que sigue siendo quien tiene la pelota ahora.
Desde el CLI de Dailybot (4.0.0 y posteriores): pasa --agent-name (o define DAILYBOT_AGENT_NAME; gana el flag). Sin ninguno, una persona actúa directamente y no se sella nada. Es una etiqueta de atribución, nunca una credencial, así que no cambia ningún permiso. El CLI envía agent_name en escrituras JSON y el header en cargas y escrituras sin cuerpo, nunca en lecturas, y rechaza localmente nombres de más de 128 caracteres.
dailybot --agent-name "Claude Code" plan task comment ENG-12 "Reproducido y corregido"
task comments muestra Jane Doe via "Claude Code", y task get agrega una línea Agents con los ejecutores, del más reciente al más antiguo. task brief [--download DIR] [--force] [--json] lee una tarjeta completa para un agente, adjuntos incluidos.
Cambios aditivos
Durante la Beta pueden aparecer en cualquier momento nuevos campos, parámetros, tipos de evento, tipos de relación y categorías de estado. Ignora los valores que no reconozcas y sigue el changelog de la API.