Tareas recurrentes y repetidas
Bordio tiene dos formas diferentes en que una tarea puede repetirse. Son mutuamente excluyentes — elige una:
- Según un horario (
recurrence) — la tarea/evento se repite en fechas fijas, p. ej. cada lunes. Usa unRRULEde iCalendar. Funciona para tareas y eventos. Se trata primero, más abajo. - Después de la finalización (
repeat_on_completion) — se crea una copia nueva solo cuando cierras la tarea, con las fechas desplazadas hacia adelante. Ideal para “hazlo de nuevo N días después de terminarlo”. Solo tareas. Consulta Repetir después de la finalización.
Según un horario (recurrence)
Sección titulada «Según un horario (recurrence)»Una tarea o evento puede repetirse según un horario — una tarea diaria, una reunión semanal, una revisión mensual. Creas una serie repetida añadiendo un objeto recurrence a la solicitud de creación normal. El resultado es una serie (una plantilla que direccionas por su id), no un montón de copias.
La recurrencia usa el formato RRULE de iCalendar (RFC 5545) — la misma sintaxis de reglas que Google Calendar y CalDAV. Si alguna vez has construido una recurrencia con una biblioteca de calendario, ya la conoces.
Crear un evento recurrente
Sección titulada «Crear un evento recurrente»Añade recurrence a POST /scheduled-events. La serie se ancla en start_at.
curl -X POST "https://api.bordio.com/public/v1/scheduled-events" \ -H "Authorization: Bearer brd_sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "title": "Team standup", "start_at": "2026-07-06T09:00:00Z", "end_at": "2026-07-06T09:15:00Z", "recurrence": { "rule": "RRULE:FREQ=WEEKLY;BYDAY=MO,WE,FR", "until": "2026-12-31" } }'Esto crea una reunión cada lunes, miércoles y viernes desde el 06 de julio de 2026 hasta fin de año.
Crear una tarea recurrente
Sección titulada «Crear una tarea recurrente»Añade recurrence a POST /tasks. La serie se ancla en la date de la tarea (el día en que la tarea está programada) — debes incluir date, de lo contrario la solicitud se rechaza con 422.
curl -X POST "https://api.bordio.com/public/v1/tasks" \ -H "Authorization: Bearer brd_sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "title": "Water the plants", "project_id": "proj_6594a1b2c3d4e5f6a7b8c9d0", "date": "2026-07-06", "recurrence": { "rule": "RRULE:FREQ=DAILY", "until": "2026-08-31" } }'El objeto recurrence
Sección titulada «El objeto recurrence»| Campo | Obligatorio | Descripción |
|---|---|---|
rule |
sí | Una cadena RRULE de iCalendar simple, p. ej. RRULE:FREQ=WEEKLY;BYDAY=MO,WE,FR. |
until |
no | Fecha/fecha-hora ISO-8601 — el final inclusivo de la serie. |
Escribir el rule
Sección titulada «Escribir el rule»El rule es una línea RRULE estándar. Las partes más comunes:
FREQ=— con qué frecuencia:DAILY,WEEKLY,MONTHLYoYEARLY.INTERVAL=— cada N períodos (p. ej.INTERVAL=2conFREQ=WEEKLY= cada dos semanas). El valor predeterminado es1.BYDAY=— días de la semana:MO,TU,WE,TH,FR,SA,SU(úsalo conFREQ=WEEKLY).BYMONTHDAY=— día del mes, p. ej.BYMONTHDAY=1(úsalo conFREQ=MONTHLY).
Ejemplos:
| Objetivo | rule |
|---|---|
| Cada día | RRULE:FREQ=DAILY |
| Cada día laborable | RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR |
| Un lunes sí y otro no | RRULE:FREQ=WEEKLY;INTERVAL=2;BYDAY=MO |
| El primero de cada mes | RRULE:FREQ=MONTHLY;BYMONTHDAY=1 |
| Cada año | RRULE:FREQ=YEARLY |
Reglas y límites
Sección titulada «Reglas y límites»Para mantener las series válidas y seguras, la API es estricta con el rule:
FREQes obligatorio y debe ser uno deDAILY,WEEKLY,MONTHLY,YEARLY. Las frecuencias inferiores a un día (HOURLY,MINUTELY,SECONDLY) se rechazan.- No incluyas
DTSTART. El inicio se deriva del recurso (start_atpara eventos,datepara tareas). UnDTSTARTen elrulese rechaza. - No uses
COUNT. Limita la serie con el campountilen su lugar. - La serie no puede empezar más de 730 días en el pasado (
start_aten eventos,dateen tareas). El historial más antiguo se importa como registros sueltos o como una serie anclada en una fecha reciente; las series existentes no se ven afectadas. INTERVAL, si está presente, debe ser un entero positivo (≤ 1000).- El
ruledebe ser una única líneaRRULE:(sinRDATE/EXDATE, sin saltos de línea).
Cualquier otra cosa devuelve 422 validation_error con el código invalid_recurrence_rule.
¿Cuándo termina?
Sección titulada «¿Cuándo termina?»Usa el campo until para la fecha de finalización. Si omites until, la serie es abierta y se repite indefinidamente.
Leer una serie
Sección titulada «Leer una serie»Los recursos recurrentes incluyen un objeto recurrence en las respuestas; los individuales (no recurrentes) devuelven recurrence: null.
{ "data": { "id": "event_6594a1b2c3d4e5f6a7b8c9d0", "title": "Team standup", "start_at": "2026-07-06T09:00:00.000Z", "recurrence": { "rule": "RRULE:FREQ=WEEKLY;BYDAY=MO,WE,FR", "until": "2026-12-31T00:00:00.000Z" } }}El rule devuelto está normalizado a la línea RRULE: — el DTSTART interno no se expone.
Repetir después de la finalización (solo tareas)
Sección titulada «Repetir después de la finalización (solo tareas)»En lugar de un horario fijo, una tarea puede generar una nueva copia de sí misma cuando la completas (moverla a un estado cerrado). Este es un mecanismo diferente del recurrence anterior — no hay RRULE, y no se crea nada hasta que la tarea se cierra realmente. Añade repeat_on_completion a POST /tasks:
curl -X POST "https://api.bordio.com/public/v1/tasks" \ -H "Authorization: Bearer brd_sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "title": "Change the water filter", "project_id": "proj_6594a1b2c3d4e5f6a7b8c9d0", "estimated_time_seconds": 1800, "repeat_on_completion": { "start_date_shift_days": 30, "due_date_shift_days": 2, "until": "2027-12-31" } }'Cuando esta tarea se cierra, Bordio crea una copia nueva: el mismo título, proyecto, asignados, prioridad y subtareas, con su fecha de inicio establecida 30 días después de la fecha de finalización y su fecha de vencimiento 2 días después de esa. La copia se repite de la misma manera, hasta que pasa until.
| Campo | Obligatorio | Descripción |
|---|---|---|
start_date_shift_days |
no | Días después de la finalización en que empieza la siguiente copia. 0 = el mismo día. Omítelo o pasa null para dejar la copia sin fecha — irá a la waiting list. |
due_date_shift_days |
no | Días después del inicio de la copia en que cae su fecha de vencimiento. Omítelo para no tener fecha de vencimiento. Se ignora cuando la copia no tiene fecha de inicio. |
until |
no | Deja de repetir después de esta fecha (ISO-8601). Omítelo para repetir indefinidamente. |
initial_task_status_id |
no | Estado en el que empieza cada nueva copia (task_status_...). Por defecto, el estado de la tarea — defínelo para que la copia empiece, por ejemplo, en “To do” en lugar del estado cerrado que la disparó. |
Notas:
- Solo cuando se cierra. La copia se crea cuando la tarea pasa a un estado cuya condición es cerrada — no en cada edición.
- Los campos se heredan. El título, la descripción, el proyecto, los asignados, los reporteros, la prioridad, las subtareas y la estimación de tiempo se transfieren automáticamente a cada copia; tú solo especificas los desplazamientos de fecha.
- Repetición en la waiting list. Sin
start_date_shift_days, la copia no tiene fecha de inicio y espera en la waiting list hasta que alguien la programe. Como la fecha de vencimiento se calcula a partir de la fecha de inicio,due_date_shift_daysno tiene efecto en este modo. - Mutuamente excluyente con
recurrence. Enviar ambos en la misma tarea devuelve422.
Las respuestas incluyen un objeto repeat_on_completion (o null):
{ "data": { "id": "task_6594a1b2c3d4e5f6a7b8c9d0", "repeat_on_completion": { "start_date_shift_days": 30, "due_date_shift_days": 2, "until": "2027-12-31T00:00:00.000Z" } }}Serie vs. ocurrencias
Sección titulada «Serie vs. ocurrencias»- La serie (la propia tarea recurrente) tiene un id simple —
task_<id>/event_<id>— el id que recibes al crearla. Leerla, actualizarla o eliminarla se aplica a toda la serie. - Una ocurrencia individual tiene su propio id que lleva la fecha de la ocurrencia, p. ej.
task_<id>_20260113T090000Z(consulta Identificadores). Léelo de vuelta desde la API y úsalo tal cual para direccionar esa única ocurrencia — nunca lo construyas tú mismo.
Listar tareas recurrentes
Sección titulada «Listar tareas recurrentes»GET /tasks responde a dos preguntas distintas, y cuál de ellas obtienes depende del parámetro view.
Sin view, la lista contiene tus tareas puntuales más las ocurrencias individuales que ya existen en el sistema: las que alguien editó, completó o reprogramó. La tarea recurrente en sí no está en esa lista, y una misma serie puede aparecer como varias filas. Es el comportamiento histórico y no cambia.
Con view=by_task, la lista contiene tareas: una tarea recurrente es una sola fila, con su regla y la ocurrencia de la que toca ocuparse. Sus ocurrencias no se listan por separado.
curl "https://api.bordio.com/public/v1/tasks?view=by_task&project_id=proj_6594a1b2c3d4e5f6a7b8c9d0" \ -H "Authorization: Bearer brd_sk_live_..."{ "data": [ { "id": "task_6594a1b2c3d4e5f6a7b8c9d0", "title": "Daily standup", "kind": "recurring_task", "series_id": "task_6594a1b2c3d4e5f6a7b8c9d0", "series_chain_id": "task_6594a1b2c3d4e5f6a7b8c9d0", "recurrence": { "rule": "RRULE:FREQ=WEEKLY;BYDAY=MO,WE,FR", "until": null }, "recurrence_summary": "every week on Monday, Wednesday, Friday", "current_occurrence": { "id": "task_6594a1b2c3d4e5f6a7b8c9d0_20260907T090000Z", "kind": "occurrence", "title": "Daily standup", "task_status_id": "task_status_6594a1b2c3d4e5f6a7b8c9d2" } } ]}Estos campos están presentes en toda tarea que devuelve la API, en cualquier modo — se añadieron junto a los existentes, así que un cliente que no los lea no nota ningún cambio:
| Campo | Significado |
|---|---|
kind |
task (puntual), recurring_task (se repite según una regla), occurrence (una ocurrencia con fecha) |
series_id |
La tarea recurrente a la que pertenece el registro. Una tarea recurrente lleva aquí su propio id; una puntual, null |
series_chain_id |
Agrupa las tareas recurrentes que provienen de una misma serie original — consulta Dividir una serie |
recurrence_summary |
La misma regla en palabras, p. ej. every weekday |
current_occurrence |
La ocurrencia de la que toca ocuparse, completa. null cuando a la tarea recurrente no le quedan ocurrencias abiertas |
current_occurrence es la ocurrencia abierta más antigua, incluidas las vencidas: en una tarea diaria abandonada verás lo más viejo que sigue pendiente, no la copia de mañana. Llega como objeto de tarea completo, porque una ocurrencia que alguien editó puede diferir de la serie — su propio título, responsables, estatus o fecha límite — y no deberías necesitar otra petición para enterarte.
Las integraciones nuevas deberían pasar view siempre de forma explícita.
Listar ocurrencias día a día (view=by_date)
Sección titulada «Listar ocurrencias día a día (view=by_date)»La tercera pregunta que puede responder una lista es «qué ocurre entre estas fechas». Pasa
view=by_date con un rango de fechas y cada día en el que cae la tarea recurrente será su propio
registro — incluidas las fechas que nadie ha tocado, que no existen como registros separados hasta
que las pides:
curl "$BASE/tasks?view=by_date&date_from=2026-09-01&date_to=2026-09-30" -H "$AUTH"{ "data": [ { "id": "task_..._20260901T090000Z", "kind": "occurrence", "date": "2026-09-01", "title": "Team standup" }, { "id": "task_..._20260902T090000Z", "kind": "occurrence", "date": "2026-09-02", "title": "Team standup" } ], "pagination": { "next_cursor": "…", "has_more": true }}Lo que conviene saber de esta vista:
- el rango es obligatorio y no puede superar los 31 días. Las ocurrencias futuras se generan a partir de la regla, así que una petición sin límite no tiene final;
- el rango se aplica a la
datede la tarea — el día para el que está programada — y no a su fecha límite.due_date_from/due_date_tosiguen funcionando como filtros normales dentro de la vista; - las tareas puntuales programadas en el rango se listan junto a las ocurrencias; una tarea sin fecha no aparece (no hay día en el que ponerla);
- una ocurrencia que alguien editó aparece una sola vez, como el registro que realmente es;
- los resultados van desde el inicio del rango y la paginación funciona como siempre:
next_cursorhasta quehas_moreseafalse. Los cursores no son intercambiables entre vistas: el cursor de una lista lo rechaza la otra con400; closed_sinceno se combina con ella (400): ese filtro ordena por momento de finalización, y una lista no puede tener dos órdenes. Pregunta por el trabajo terminado aparte;- en una petición se expanden como máximo 500 tareas recurrentes.
date_from / date_to también funcionan sin view, como un filtro normal por el día para el
que está programada la tarea.
Saber cuál es la próxima ocurrencia justo al cerrar una
Sección titulada «Saber cuál es la próxima ocurrencia justo al cerrar una»PATCH acepta el mismo parámetro view. Con view=by_task, cerrar una ocurrencia devuelve la propia tarea recurrente, con current_occurrence ya apuntando a la siguiente — sin necesidad de otra petición:
curl -X PATCH "https://api.bordio.com/public/v1/tasks/task_..._20260907T090000Z?view=by_task" \ -H "Authorization: Bearer brd_sk_live_..." \ -H "Content-Type: application/json" \ -d '{"task_status_id": "task_status_..."}'Sin view, la respuesta es el registro que modificaste, como antes.
Dividir una serie
Sección titulada «Dividir una serie»Editar “esta y las siguientes ocurrencias” deja dos tareas recurrentes: la parte anterior al cambio y la posterior. Así se comportan las aplicaciones de calendario, y por eso un proyecto puede contener dos filas que parecen el mismo standup.
series_chain_id indica que van juntas:
- editar propiedades (título, responsable, descripción) mantiene ambas mitades en la misma cadena — mismo
series_chain_id, distintoseries_id; - editar la hora o la regla de repetición inicia una serie realmente nueva, y la nueva mitad recibe su propio
series_chain_id.
Una mitad que se quedó sin ocurrencias sigue existiendo (sus ocurrencias pasadas son historia real), pero su current_occurrence es null.
Qué hacer ahora
Sección titulada «Qué hacer ahora»En la vista by_task, state pregunta si hay trabajo pendiente en lugar de mirar un campo de estatus:
curl "$BASE/tasks?view=by_task&state=open" -H "$AUTH"Una tarea recurrente cuenta como abierta mientras le quede una ocurrencia abierta, y como cerrada cuando no queda ninguna — sin importar el estatus con el que arranca cada ocurrencia. Sin view=by_task, el filtro trabaja sobre el estatus propio del registro, como siempre.
Lo completado recientemente es otra pregunta con otra respuesta: las ocurrencias completadas son registros individuales, así que se listan sin view=by_task:
curl "$BASE/tasks?closed_since=2026-07-28" -H "$AUTH"Ambos filtros en contexto, en Filtrar tareas.
Eventos recurrentes en una lista
Sección titulada «Eventos recurrentes en una lista»GET /scheduled-events acepta el mismo parámetro view, con el mismo significado:
view |
Un registro es |
|---|---|
omitido / by_date |
una ocurrencia — el comportamiento por defecto, sin cambios |
by_task |
un evento; el recurrente lleva su regla y, en current_occurrence, la próxima ocurrencia dentro del rango solicitado |
curl "$BASE/scheduled-events?from=2026-09-01T00:00:00Z&to=2026-09-30T00:00:00Z&view=by_task" -H "$AUTH"El rango from / to sigue siendo obligatorio en ambas vistas. Los eventos llevan los mismos
campos que las tareas — kind (event, recurring_event, occurrence), series_id,
series_chain_id, recurrence_summary, current_occurrence — así que un mismo código cliente
puede leer ambos.
Qué se admite hoy
Sección titulada «Qué se admite hoy»- Crear una tarea o evento recurrente.
- Leer, actualizar y eliminar tanto una serie como una ocurrencia individual por su id.
- Listar tareas y eventos recurrentes como tareas y eventos (
view=by_task), con la siguiente ocurrencia incluida. - Listar ocurrencias en un rango de fechas —
view=by_dateen tareas y la lista por defecto en eventos. - Elegir a qué afecta una edición con
scope— ver más abajo.
Editar una ocurrencia, las siguientes o todas
Sección titulada «Editar una ocurrencia, las siguientes o todas»PATCH y DELETE aceptan el parámetro de consulta scope:
scope |
A qué afecta |
|---|---|
omitido / only_this |
solo la ocurrencia que direccionaste — el valor por defecto y el comportamiento anterior |
this_and_following |
esa ocurrencia y todas las posteriores; divide la tarea recurrente en dos |
all |
todas las ocurrencias, incluidas las pasadas |
# Renombrar el resto de la serie sin tocar el historialcurl -X PATCH "https://api.bordio.com/public/v1/tasks/task_..._20260907T090000Z?scope=this_and_following" \ -H "Authorization: Bearer brd_sk_live_..." \ -H "Content-Type: application/json" \ -d '{"title": "Standup (asíncrono)"}'En una tarea que no se repite, el parámetro se ignora.
Direccionar la tarea recurrente en sí exige scope
Sección titulada «Direccionar la tarea recurrente en sí exige scope»Si el id al que aplicas PATCH o DELETE es la tarea recurrente completa (el series_id, no un id de ocurrencia) y no pasaste scope, la petición se rechaza con 400. Renombrar o borrar una serie entera por accidente no tiene vuelta atrás, y los dos identificadores se parecen: por eso la API te pide precisar qué querías — pasa scope=all o direcciona una ocurrencia concreta por su propio id.
Qué puede y qué no puede cambiar scope=all
Sección titulada «Qué puede y qué no puede cambiar scope=all»scope=all escribe los campos que envías en toda la serie y deja el resto intacto: si alguien movió un martes o reasignó una fecha suelta, eso se mantiene.
Algunos campos no pueden extenderse por la serie y se rechazan con 400 en lugar de aplicarse a medias:
| Campo | Por qué |
|---|---|
assignee_ids, custom_fields |
contienen identificadores propios de cada registro; copiarlos entre ocurrencias corrompería los registros |
tag_ids |
las etiquetas se aplican por su propio mecanismo, con su propio comportamiento ante la recurrencia |
due_date |
cada ocurrencia deriva su fecha límite de su propia fecha — no hay un valor común a la serie |
Cambiar la programación (la fecha o la regla de repetición) es this_and_following, no all: divide la serie en lugar de reescribir su pasado.
DELETE ?scope=all elimina la tarea recurrente junto con todas sus ocurrencias.