Skip to content
ver .md sin procesar

Muestra un tablero y mantenlo al día

Muestra un tablero de Dailybot Plan en una sola solicitud y mantenlo al día con el feed de cambios: ETag y 304, consultas periódicas al ritmo del servidor, truncamiento y vencimiento del cursor.

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

Una pantalla de tablero necesita dos cosas: el tablero completo una vez y, después, solo lo que cambió. La API de Plan te da justo eso: una instantánea que muestra todas las columnas en una sola solicitud y un feed de cambios que devuelve las tareas modificadas desde un cursor. Consulta el feed; nunca vuelvas a leer el tablero completo con un temporizador.

1. Lee el tablero una vez

curl -sS -D headers.txt "https://api.dailybot.com/v1/plan/boards/$BOARD/board/" \
  -H "Authorization: Bearer $DAILYBOT_TOKEN" > board.json

La respuesta tiene una entrada en groups por cada columna, en el orden de las columnas. Cada grupo trae el task_count real de la columna, sus primeras tareas en orden de rango y has_more cuando la columna tiene más tarjetas que la página (hasta 50 por columna, configurable con tasks_per_state). Guarda dos cosas:

  • delta_cursor del cuerpo: el punto donde empieza el feed de cambios.
  • El encabezado ETag: envíalo de vuelta como If-None-Match para recibir 304 Not Modified cuando nada cambió.

Para paginar el resto de una columna larga, usa la lista de tareas con el mismo tablero y estado: GET /v1/plan/tasks/?board=$BOARD&state=<state uuid>.

2. Pide solo lo que cambió

curl -sS "https://api.dailybot.com/v1/plan/boards/$BOARD/delta/?updated_since=$CURSOR" \
  -H "Authorization: Bearer $DAILYBOT_TOKEN"
{
  "since": "2026-09-25T10:14:02.113954Z",
  "cursor": "2026-09-25T10:19:44.902311Z",
  "changed": [ { "uuid": "00000000-0000-4000-8000-000000000005", "key": "ENG-142", "state": { "name": "Done", "category": "done" }, "version": 9 } ],
  "removed": [ { "uuid": "00000000-0000-4000-8000-000000000105", "key": "ENG-77", "reason": "archived" } ],
  "states": null,
  "truncated": false,
  "poll_after_seconds": 15
}

Aplícalo en este orden:

  1. states: cuando no es null, se creó, renombró, reordenó o archivó una columna: reemplaza toda tu lista de columnas con este valor.
  2. changed: inserta o actualiza cada tarea por uuid (una entrada por tarea; gana la versión actual).
  3. removed: quita cada tarea del tablero.
  4. Guarda cursor y envíalo tal cual como updated_since la próxima vez. Nunca calcules un cursor con tu propio reloj.

La entrega es al menos una vez: una tarea que cambió en el mismo instante que tu cursor puede llegar dos veces, así que las inserciones y actualizaciones deben ser idempotentes.

3. Deja que el servidor marque el ritmo

  • Espera poll_after_seconds antes de la siguiente llamada. Empieza en 15 segundos, se duplica hasta 120 mientras el tablero está quieto y vuelve a bajar en cuanto algo cambia.
  • Si truncated es true, hay más cambios esperando: consulta de nuevo de inmediato.
  • Haz una pausa mientras la página está oculta y consulta una vez cuando vuelva a estar visible.
  • Un cursor con más de 7 días devuelve 400 delta_window_expired: lee la instantánea de nuevo y vuelve a empezar desde su delta_cursor.
  • El feed de cambios admite 240 llamadas por minuto por actor. Si respetas poll_after_seconds, te mantienes muy por debajo de ese límite.

Un ciclo de consultas completo

Un ciclo de shell para un script o un dashboard en la terminal:

CURSOR=$(jq -r .delta_cursor board.json)
while true; do
  RESPONSE=$(curl -sS "https://api.dailybot.com/v1/plan/boards/$BOARD/delta/?updated_since=$CURSOR" \
    -H "Authorization: Bearer $DAILYBOT_TOKEN")
  if [ "$(echo "$RESPONSE" | jq -r '.code // empty')" = "delta_window_expired" ]; then
    echo "Cursor expired: read the snapshot again"; break
  fi
  echo "$RESPONSE" | jq -c '{changed: [.changed[].key], removed: [.removed[].key]}'
  CURSOR=$(echo "$RESPONSE" | jq -r .cursor)
  if [ "$(echo "$RESPONSE" | jq -r .truncated)" = "true" ]; then continue; fi
  sleep "$(echo "$RESPONSE" | jq -r .poll_after_seconds)"
done

El mismo ciclo en un servicio de Node.js (Node 18 o posterior), con el token guardado en el servidor:

const API = 'https://api.dailybot.com/v1/plan';
const headers = { Authorization: `Bearer ${process.env.DAILYBOT_TOKEN}` };
const sleep = (s) => new Promise((resolve) => setTimeout(resolve, s * 1000));

async function follow(boardId, onChange) {
  let res = await fetch(`${API}/boards/${boardId}/board/`, { headers });
  let cursor = (await res.json()).delta_cursor;
  for (;;) {
    res = await fetch(`${API}/boards/${boardId}/delta/?updated_since=${encodeURIComponent(cursor)}`, { headers });
    const delta = await res.json();
    if (delta.code === 'delta_window_expired') return follow(boardId, onChange);
    onChange(delta); // replace columns if delta.states, upsert delta.changed, drop delta.removed
    cursor = delta.cursor;
    if (!delta.truncated) await sleep(delta.poll_after_seconds);
  }
}

En un navegador, llama a tu propio backend en lugar de poner un token de Dailybot en el código de la página, y pausa el ciclo con visibilitychange mientras la pestaña esté oculta.

Referencia