Pular para o conteúdo

Tarefas recorrentes e repetitivas

O Bordio tem duas maneiras diferentes de fazer uma tarefa se repetir. Elas são mutuamente exclusivas — escolha uma:

  • Em um cronograma (recurrence) — a tarefa/evento se repete em datas fixas, por exemplo, toda segunda-feira. Usa um RRULE do iCalendar. Funciona para tarefas e eventos. Abordado primeiro, abaixo.
  • Após a conclusão (repeat_on_completion) — uma nova cópia é criada somente quando você fecha a tarefa, com as datas deslocadas para frente. Bom para “faça isto novamente N dias depois de eu terminá-lo”. Apenas tarefas. Veja Repetir após a conclusão.

Uma tarefa ou evento pode se repetir em um cronograma — uma tarefa diária, uma reunião semanal, uma revisão mensal. Você cria uma série repetitiva adicionando um objeto recurrence à requisição normal de criação. O resultado é uma série (um template que você referencia pelo seu id), não um monte de cópias.

A recorrência usa o formato RRULE do iCalendar (RFC 5545) — a mesma sintaxe de regra do Google Calendar e do CalDAV. Se você já construiu uma recorrência com alguma biblioteca de calendário, você já sabe como funciona.

Adicione recurrence ao POST /scheduled-events. A série é ancorada em start_at.

Terminal window
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"
}
}'

Isso cria uma reunião toda segunda, quarta e sexta-feira, de 06 de julho de 2026 até o final do ano.

Adicione recurrence ao POST /tasks. A série é ancorada pela date da tarefa (o dia em que a tarefa está agendada) — você deve incluir date, caso contrário a requisição é rejeitada com 422.

Terminal window
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"
}
}'
Campo Obrigatório Descrição
rule sim Uma string RRULE pura do iCalendar, por exemplo, RRULE:FREQ=WEEKLY;BYDAY=MO,WE,FR.
until não Data/data-hora ISO-8601 — o fim inclusivo da série.

O rule é uma linha RRULE padrão. As partes mais comuns:

  • FREQ= — com que frequência: DAILY, WEEKLY, MONTHLY ou YEARLY.
  • INTERVAL= — a cada N períodos (por exemplo, INTERVAL=2 com FREQ=WEEKLY = a cada duas semanas). O padrão é 1.
  • BYDAY= — dias da semana: MO,TU,WE,TH,FR,SA,SU (use com FREQ=WEEKLY).
  • BYMONTHDAY= — dia do mês, por exemplo, BYMONTHDAY=1 (use com FREQ=MONTHLY).

Exemplos:

Objetivo rule
Todo dia RRULE:FREQ=DAILY
Todo dia útil RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR
A cada duas segundas-feiras RRULE:FREQ=WEEKLY;INTERVAL=2;BYDAY=MO
No primeiro dia de cada mês RRULE:FREQ=MONTHLY;BYMONTHDAY=1
Todo ano RRULE:FREQ=YEARLY

Para manter as séries válidas e seguras, a API é rigorosa quanto ao rule:

  • FREQ é obrigatório e deve ser um dos valores DAILY, WEEKLY, MONTHLY, YEARLY. Frequências sub-diárias (HOURLY, MINUTELY, SECONDLY) são rejeitadas.
  • Não inclua DTSTART. O início é derivado do recurso (start_at para eventos, date para tarefas). Um DTSTART no rule é rejeitado.
  • Não use COUNT. Delimite a série com o campo until em vez disso.
  • A série não pode começar mais de 730 dias no passado (start_at em eventos, date em tarefas). Histórico mais antigo é importado como registros avulsos ou como uma série ancorada em uma data recente; séries existentes não são afetadas.
  • INTERVAL, se presente, deve ser um número inteiro positivo (≤ 1000).
  • O rule deve ser uma única linha RRULE: (sem RDATE/EXDATE, sem quebras de linha).

Qualquer outra coisa retorna 422 validation_error com o código invalid_recurrence_rule.

Use o campo until para a data de término. Se você omitir until, a série é aberta e se repete indefinidamente.

Recursos recorrentes incluem um objeto recurrence nas respostas; os individuais (não recorrentes) retornam 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"
}
}
}

O rule retornado é normalizado para a linha RRULE: — o DTSTART interno não é exposto.

Em vez de um cronograma fixo, uma tarefa pode gerar uma nova cópia de si mesma quando você a conclui (move-a para um status fechado). Este é um mecanismo diferente do recurrence acima — não há RRULE, e nada é criado até que a tarefa seja de fato fechada. Adicione repeat_on_completion ao POST /tasks:

Terminal window
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"
}
}'

Quando esta tarefa é fechada, o Bordio cria uma nova cópia: mesmo título, projeto, responsáveis, prioridade e subtarefas, com a data de início definida para 30 dias após a data de conclusão e a data de vencimento 2 dias depois disso. A cópia se repete da mesma forma, até que until seja ultrapassado.

Campo Obrigatório Descrição
start_date_shift_days não Dias após a conclusão em que a próxima cópia começa. 0 = mesmo dia. Omita ou passe null para deixar a cópia sem data — ela vai para a waiting list.
due_date_shift_days não Dias após o início da cópia em que sua data de vencimento cai. Omita para não ter data de vencimento. Ignorado quando a cópia não tem data de início.
until não Parar de repetir após esta data (ISO-8601). Omita para repetir indefinidamente.
initial_task_status_id não Status em que cada nova cópia começa (task_status_...). Por padrão, o status da tarefa — defina-o para que a cópia comece, por exemplo, em “To do” em vez do status fechado que a disparou.

Notas:

  • Somente quando fechada. A cópia é criada quando a tarefa passa para um status cujo estado é fechado — não a cada edição.
  • Os campos são herdados. Título, descrição, projeto, responsáveis, relatores, prioridade, subtarefas e a estimativa de tempo são transferidos para cada cópia automaticamente; você especifica apenas os deslocamentos de data.
  • Repetição na waiting list. Sem start_date_shift_days, a cópia não tem data de início e aguarda na waiting list até ser agendada. Como a data de vencimento é calculada a partir da data de início, due_date_shift_days não tem efeito nesse modo.
  • Mutuamente exclusivo com recurrence. Enviar ambos na mesma tarefa retorna 422.

As respostas incluem um objeto repeat_on_completion (ou 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"
}
}
}
  • A série (a própria tarefa recorrente) tem um id simples — task_<id> / event_<id> — o id que você recebe de volta ao criar. Ler, atualizar ou excluir a série aplica-se a toda a série.
  • Uma ocorrência individual tem seu próprio id que carrega a data da ocorrência, por exemplo, task_<id>_20260113T090000Z (veja Identificadores). Leia-o de volta a partir da API e use-o exatamente como está para referenciar aquela ocorrência única — nunca o construa você mesmo.

GET /tasks responde a duas perguntas diferentes, e qual delas você recebe depende do parâmetro view.

Sem view, a lista contém suas tarefas avulsas mais as ocorrências individuais que já existem no sistema: aquelas que alguém editou, concluiu ou reagendou. A tarefa recorrente em si não está nessa lista, e uma mesma série pode aparecer como várias linhas. Esse é o comportamento histórico e ele não muda.

Com view=by_task, a lista contém tarefas: uma tarefa recorrente é uma única linha, com sua regra e a ocorrência de que cuidar em seguida. Suas ocorrências não são listadas separadamente.

Terminal window
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"
}
}
]
}

Esses campos estão presentes em toda tarefa que a API devolve, em qualquer modo — foram acrescentados ao lado dos existentes, então um cliente que não os lê não percebe mudança:

Campo Significado
kind task (avulsa), recurring_task (repete-se por uma regra), occurrence (uma ocorrência com data)
series_id A tarefa recorrente à qual o registro pertence. Uma tarefa recorrente carrega aqui o próprio id; uma avulsa, null
series_chain_id Agrupa tarefas recorrentes originadas de uma mesma série — veja Dividindo uma série
recurrence_summary A mesma regra em palavras, por exemplo, every weekday
current_occurrence A ocorrência de que cuidar em seguida, completa. null quando a tarefa recorrente não tem mais ocorrências abertas

current_occurrence é a ocorrência aberta mais antiga, incluindo as atrasadas: em uma tarefa diária abandonada você vê o mais antigo que ainda está pendente, não a cópia de amanhã. Ela vem como objeto de tarefa completo, porque uma ocorrência que alguém editou pode diferir da série — título, responsáveis, status ou data limite próprios — e você não deveria precisar de outra requisição para descobrir isso.

Integrações novas devem sempre passar view explicitamente.

A terceira pergunta que uma listagem pode responder é «o que acontece entre estas datas». Passe view=by_date com um intervalo de datas e cada dia em que a tarefa recorrente cai vira um registro próprio — inclusive as datas que ninguém tocou, que não existem como registros separados até você pedir por elas:

Terminal window
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 }
}

O que vale saber sobre essa visão:

  • o intervalo é obrigatório e não pode passar de 31 dias. As ocorrências futuras são geradas a partir da regra, então um pedido sem limite não tem fim;
  • o intervalo se aplica à date da tarefa — o dia para o qual ela está agendada — e não à data limite. due_date_from / due_date_to continuam funcionando como filtros comuns dentro da visão;
  • tarefas avulsas agendadas no intervalo aparecem ao lado das ocorrências; uma tarefa sem data, não (não há dia onde colocá-la);
  • uma ocorrência que alguém editou aparece uma única vez, como o registro que ela realmente é;
  • os resultados vão do início do intervalo e a paginação funciona como sempre: next_cursor até has_more ser false. Os cursores não são intercambiáveis entre visões: o cursor de uma listagem é recusado pela outra com 400;
  • closed_since não se combina com ela (400): aquele filtro ordena por momento de conclusão, e uma lista não pode ter duas ordens. Pergunte pelo trabalho concluído separadamente;
  • no máximo 500 tarefas recorrentes são expandidas por requisição.

date_from / date_to também funcionam sem view, como um filtro comum pelo dia para o qual a tarefa está agendada.

Saber qual é a próxima ocorrência logo ao fechar uma

Seção intitulada “Saber qual é a próxima ocorrência logo ao fechar uma”

PATCH aceita o mesmo parâmetro view. Com view=by_task, fechar uma ocorrência retorna a própria tarefa recorrente, com current_occurrence já apontando para a seguinte — sem precisar de outra requisição:

Terminal window
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_..."}'

Sem view, a resposta é o registro que você alterou, como antes.

Editar “esta e as ocorrências seguintes” deixa duas tarefas recorrentes: a parte anterior à mudança e a posterior. É assim que os aplicativos de calendário se comportam, e é por isso que um projeto pode conter duas linhas que parecem o mesmo standup.

O series_chain_id mostra que elas andam juntas:

  • editar propriedades (título, responsável, descrição) mantém as duas metades na mesma cadeia — mesmo series_chain_id, series_id diferentes;
  • editar o horário ou a regra de repetição inicia uma série genuinamente nova, e a nova metade recebe seu próprio series_chain_id.

Uma metade que ficou sem ocorrências continua existindo (suas ocorrências passadas são história real), mas seu current_occurrence é null.

Na visão by_task, state pergunta se há trabalho a fazer, em vez de olhar um campo de status:

Terminal window
curl "$BASE/tasks?view=by_task&state=open" -H "$AUTH"

Uma tarefa recorrente conta como aberta enquanto ainda tiver uma ocorrência aberta, e como fechada quando não sobra nenhuma — independentemente do status com que cada ocorrência começa. Sem view=by_task, o filtro trabalha sobre o status do próprio registro, como sempre.

O que foi concluído recentemente é outra pergunta com outra resposta: ocorrências concluídas são registros individuais, então são listadas sem view=by_task:

Terminal window
curl "$BASE/tasks?closed_since=2026-07-28" -H "$AUTH"

Os dois filtros em contexto estão em Filtrando tarefas.

GET /scheduled-events aceita o mesmo parâmetro view, com o mesmo significado:

view Um registro é
omitido / by_date uma ocorrência — o comportamento padrão, inalterado
by_task um evento; o recorrente carrega sua regra e, em current_occurrence, a próxima ocorrência dentro do intervalo solicitado
Terminal window
curl "$BASE/scheduled-events?from=2026-09-01T00:00:00Z&to=2026-09-30T00:00:00Z&view=by_task" -H "$AUTH"

O intervalo from / to continua obrigatório nas duas visões. Eventos carregam os mesmos campos das tarefas — kind (event, recurring_event, occurrence), series_id, series_chain_id, recurrence_summary, current_occurrence — então um mesmo código cliente lê os dois.

  • Criar uma tarefa ou evento recorrente.
  • Ler, atualizar e excluir tanto uma série quanto uma ocorrência individual pelo seu id.
  • Listar tarefas e eventos recorrentes como tarefas e eventos (view=by_task), com a próxima ocorrência incluída.
  • Listar ocorrências em um intervalo de datas — view=by_date nas tarefas e a listagem padrão nos eventos.
  • Escolher o que uma edição afeta com scope — veja abaixo.

PATCH e DELETE aceitam o parâmetro de consulta scope:

scope O que afeta
omitido / only_this apenas a ocorrência que você referenciou — o padrão e o comportamento anterior
this_and_following essa ocorrência e todas as posteriores; divide a tarefa recorrente em duas
all todas as ocorrências, incluindo as passadas
Terminal window
# Renomear o restante da série sem mexer no histórico
curl -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 (assíncrono)"}'

Em uma tarefa que não se repete, o parâmetro é ignorado.

Referenciar a própria tarefa recorrente exige scope

Seção intitulada “Referenciar a própria tarefa recorrente exige scope”

Se o id ao qual você aplica PATCH ou DELETE é a tarefa recorrente inteira (o series_id, não um id de ocorrência) e você não passou scope, a requisição é rejeitada com 400. Renomear ou excluir uma série inteira por acidente não tem desfazer, e os dois identificadores se parecem: por isso a API pede que você diga o que quis dizer — passe scope=all ou referencie uma ocorrência específica pelo id dela.

scope=all grava os campos que você envia em toda a série e deixa o resto intacto: se alguém moveu uma terça-feira ou trocou o responsável em uma data isolada, isso permanece.

Alguns campos não podem ser espalhados pela série e são rejeitados com 400 em vez de aplicados pela metade:

Campo Por quê
assignee_ids, custom_fields contêm identificadores próprios de cada registro; copiá-los entre ocorrências corromperia os registros
tag_ids tags são aplicadas por mecanismo próprio, com comportamento próprio diante da recorrência
due_date cada ocorrência deriva seu prazo da própria data — não existe um valor comum à série

Mudar a programação (a data ou a regra de repetição) é this_and_following, não all: isso divide a série em vez de reescrever o passado dela.

DELETE ?scope=all remove a tarefa recorrente junto com todas as suas ocorrências.