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 umRRULEdo 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.
Em um cronograma (recurrence)
Seção intitulada “Em um cronograma (recurrence)”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.
Criando um evento recorrente
Seção intitulada “Criando um evento recorrente”Adicione recurrence ao POST /scheduled-events. A série é ancorada em 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" } }'Isso cria uma reunião toda segunda, quarta e sexta-feira, de 06 de julho de 2026 até o final do ano.
Criando uma tarefa recorrente
Seção intitulada “Criando uma tarefa recorrente”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.
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" } }'O objeto recurrence
Seção intitulada “O objeto recurrence”| 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. |
Escrevendo o rule
Seção intitulada “Escrevendo o rule”O rule é uma linha RRULE padrão. As partes mais comuns:
FREQ=— com que frequência:DAILY,WEEKLY,MONTHLYouYEARLY.INTERVAL=— a cada N períodos (por exemplo,INTERVAL=2comFREQ=WEEKLY= a cada duas semanas). O padrão é1.BYDAY=— dias da semana:MO,TU,WE,TH,FR,SA,SU(use comFREQ=WEEKLY).BYMONTHDAY=— dia do mês, por exemplo,BYMONTHDAY=1(use comFREQ=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 |
Regras e limites
Seção intitulada “Regras e limites”Para manter as séries válidas e seguras, a API é rigorosa quanto ao rule:
FREQé obrigatório e deve ser um dos valoresDAILY,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_atpara eventos,datepara tarefas). UmDTSTARTnoruleé rejeitado. - Não use
COUNT. Delimite a série com o campountilem vez disso. - A série não pode começar mais de 730 dias no passado (
start_atem eventos,dateem 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
ruledeve ser uma única linhaRRULE:(semRDATE/EXDATE, sem quebras de linha).
Qualquer outra coisa retorna 422 validation_error com o código invalid_recurrence_rule.
Quando ela termina?
Seção intitulada “Quando ela termina?”Use o campo until para a data de término. Se você omitir until, a série é aberta e se repete indefinidamente.
Lendo uma série de volta
Seção intitulada “Lendo uma série de volta”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.
Repetir após a conclusão (apenas tarefas)
Seção intitulada “Repetir após a conclusão (apenas tarefas)”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:
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_daysnão tem efeito nesse modo. - Mutuamente exclusivo com
recurrence. Enviar ambos na mesma tarefa retorna422.
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" } }}Série vs. ocorrências
Seção intitulada “Série vs. ocorrências”- 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.
Listando tarefas recorrentes
Seção intitulada “Listando tarefas recorrentes”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.
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.
Listando ocorrências dia a dia (view=by_date)
Seção intitulada “Listando ocorrências dia a dia (view=by_date)”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:
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 à
dateda tarefa — o dia para o qual ela está agendada — e não à data limite.due_date_from/due_date_tocontinuam 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_cursoratéhas_moreserfalse. Os cursores não são intercambiáveis entre visões: o cursor de uma listagem é recusado pela outra com400; closed_sincenã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:
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.
Dividindo uma série
Seção intitulada “Dividindo uma série”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_iddiferentes; - 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.
O que fazer agora
Seção intitulada “O que fazer agora”Na visão by_task, state pergunta se há trabalho a fazer, em vez de olhar um campo de status:
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:
curl "$BASE/tasks?closed_since=2026-07-28" -H "$AUTH"Os dois filtros em contexto estão em Filtrando tarefas.
Eventos recorrentes em uma listagem
Seção intitulada “Eventos recorrentes em uma listagem”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 |
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.
O que é suportado atualmente
Seção intitulada “O que é suportado atualmente”- 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_datenas tarefas e a listagem padrão nos eventos. - Escolher o que uma edição afeta com
scope— veja abaixo.
Editar uma ocorrência, as seguintes ou todas
Seção intitulada “Editar uma ocorrência, as seguintes ou todas”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 |
# Renomear o restante da série sem mexer no históricocurl -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.
O que scope=all pode e não pode mudar
Seção intitulada “O que scope=all pode e não pode mudar”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.