Повторяющиеся задачи и события
В Bordio есть два разных способа повторения задачи. Они взаимоисключающие — выберите один:
- По расписанию (
recurrence) — задача/событие повторяется в фиксированные даты, например каждый понедельник. Использует iCalendarRRULE. Работает для задач и событий. Рассматривается первым, ниже. - После завершения (
repeat_on_completion) — новая копия создаётся только когда вы закрываете задачу, со сдвигом дат вперёд. Подходит для «сделать это снова через N дней после того, как я закончу». Только задачи. См. Повтор после завершения.
По расписанию (recurrence)
Заголовок раздела «По расписанию (recurrence)»Задача или событие может повторяться по расписанию — ежедневная задача, еженедельный стендап, ежемесячный обзор. Вы создаёте повторяющуюся серию, добавляя объект recurrence в обычный запрос на создание. Результат — это серия (один шаблон, к которому вы обращаетесь по его id), а не набор копий.
Повторение использует формат iCalendar RRULE (RFC 5545) — тот же синтаксис правил, что и в Google Calendar и CalDAV. Если вы когда-либо создавали повторение с помощью календарной библиотеки, вы уже с ним знакомы.
Создание повторяющегося события
Заголовок раздела «Создание повторяющегося события»Добавьте recurrence в POST /scheduled-events. Серия привязывается к 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" } }'Это создаёт стендап каждый понедельник, среду и пятницу с 06 июля 2026 года до конца года.
Создание повторяющейся задачи
Заголовок раздела «Создание повторяющейся задачи»Добавьте recurrence в POST /tasks. Серия привязывается к date задачи (день, на который назначена задача) — вы обязаны указать date, иначе запрос будет отклонён с ошибкой 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" } }'Объект recurrence
Заголовок раздела «Объект recurrence»| Поле | Обязательно | Описание |
|---|---|---|
rule |
да | Чистая строка iCalendar RRULE, например RRULE:FREQ=WEEKLY;BYDAY=MO,WE,FR. |
until |
нет | Дата/дата-время в формате ISO-8601 — включительная граница окончания серии. |
Как написать rule
Заголовок раздела «Как написать rule»rule — это стандартная строка RRULE. Наиболее часто используемые части:
FREQ=— как часто:DAILY,WEEKLY,MONTHLYилиYEARLY.INTERVAL=— каждые N периодов (например,INTERVAL=2сFREQ=WEEKLY= раз в две недели). По умолчанию1.BYDAY=— дни недели:MO,TU,WE,TH,FR,SA,SU(используется сFREQ=WEEKLY).BYMONTHDAY=— день месяца, напримерBYMONTHDAY=1(используется сFREQ=MONTHLY).
Примеры:
| Цель | rule |
|---|---|
| Каждый день | RRULE:FREQ=DAILY |
| Каждый будний день | RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR |
| Каждый второй понедельник | RRULE:FREQ=WEEKLY;INTERVAL=2;BYDAY=MO |
| Первое число каждого месяца | RRULE:FREQ=MONTHLY;BYMONTHDAY=1 |
| Каждый год | RRULE:FREQ=YEARLY |
Правила и ограничения
Заголовок раздела «Правила и ограничения»Чтобы серии оставались валидными и безопасными, API строг к rule:
FREQобязателен и должен быть одним изDAILY,WEEKLY,MONTHLY,YEARLY. Частоты чаще, чем раз в день (HOURLY,MINUTELY,SECONDLY), отклоняются.- Не включайте
DTSTART. Начало выводится из ресурса (start_atдля событий,dateдля задач).DTSTARTвruleотклоняется. - Не используйте
COUNT. Вместо этого ограничивайте серию полемuntil. - Серия не может начинаться раньше, чем 730 дней назад (
start_atу событий,dateу задач). Более старую историю переносят разовыми записями или серией с недавней датой; существующих серий это не касается. INTERVAL, если присутствует, должен быть положительным целым числом (≤ 1000).ruleдолжен быть единственной строкойRRULE:(безRDATE/EXDATE, без переносов строк).
Всё остальное возвращает 422 validation_error с кодом invalid_recurrence_rule.
Когда серия заканчивается?
Заголовок раздела «Когда серия заканчивается?»Используйте поле until для даты окончания. Если вы опустите until, серия будет бессрочной и будет повторяться неограниченно.
Чтение серии
Заголовок раздела «Чтение серии»Повторяющиеся ресурсы включают в ответах объект recurrence; одиночные (не повторяющиеся) возвращают 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" } }}Возвращаемый rule нормализуется до строки RRULE: — внутренний DTSTART не раскрывается.
Повтор после завершения (только задачи)
Заголовок раздела «Повтор после завершения (только задачи)»Вместо фиксированного расписания задача может порождать новую копию себя, когда вы её завершаете (переводите в закрытый статус). Это иной механизм, нежели recurrence выше — здесь нет RRULE, и ничего не создаётся, пока задача действительно не закрыта. Добавьте repeat_on_completion в 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" } }'Когда эта задача закрывается, Bordio создаёт новую копию: тот же заголовок, проект, исполнители, приоритет и подзадачи, с датой начала, установленной через 30 дней после даты завершения, и датой выполнения через 2 дня после этого. Копия повторяется тем же образом, пока не пройдёт until.
| Поле | Обязательно | Описание |
|---|---|---|
start_date_shift_days |
нет | Через сколько дней после завершения начинается следующая копия. 0 = в тот же день. Опустите или передайте null, чтобы копия осталась без даты — она попадёт в waiting list. |
due_date_shift_days |
нет | Через сколько дней после начала копии наступает её срок выполнения. Опустите, чтобы не было срока выполнения. Игнорируется, если у копии нет даты начала. |
until |
нет | Прекратить повторение после этой даты (ISO-8601). Опустите для бессрочного повторения. |
initial_task_status_id |
нет | Статус, в котором стартует каждая новая копия (task_status_...). По умолчанию — статус задачи; задайте, чтобы копия начиналась, например, с «To do», а не с закрывшего её статуса. |
Примечания:
- Только при закрытии. Копия создаётся, когда задача переходит в статус, состояние которого — закрытое, а не при каждом изменении.
- Поля наследуются. Заголовок, описание, проект, исполнители, репортеры, приоритет, подзадачи и оценка времени переносятся в каждую копию автоматически; вы указываете только сдвиги дат.
- Повтор в waiting list. Без
start_date_shift_daysкопия не имеет даты начала и ждёт в waiting list, пока её не запланируют. Так как срок выполнения отсчитывается от даты начала,due_date_shift_daysв этом режиме не действует. - Взаимоисключимо с
recurrence. Отправка обоих для одной задачи возвращает422.
Ответы включают объект repeat_on_completion (или 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" } }}Серия и её вхождения
Заголовок раздела «Серия и её вхождения»- Серия (сама повторяющаяся задача) имеет обычный id —
task_<id>/event_<id>— тот id, который вы получаете при создании. Чтение, обновление или удаление применяется ко всей серии. - Отдельное вхождение имеет собственный id, содержащий дату вхождения, например
task_<id>_20260113T090000Z(см. Идентификаторы). Считайте его из API и используйте дословно для обращения к этому единственному вхождению — никогда не собирайте его самостоятельно.
Списки повторяющихся задач
Заголовок раздела «Списки повторяющихся задач»GET /tasks отвечает на два разных вопроса, и какой именно ответ вы получите — решает параметр view.
Без view список содержит разовые задачи плюс те отдельные вхождения, которые уже существуют в системе: те, что кто-то редактировал, завершил или перенёс. Самой повторяющейся задачи в этом списке нет, а одна серия может дать несколько строк. Это историческое поведение, и оно не меняется.
С view=by_task список содержит задачи: повторяющаяся задача — это одна строка с её правилом и ближайшим вхождением, которым предстоит заняться. Отдельными строками её вхождения не выводятся.
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": "Ежедневный стендап", "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": "Ежедневный стендап", "task_status_id": "task_status_6594a1b2c3d4e5f6a7b8c9d2" } } ]}Эти поля есть у каждой задачи, которую отдаёт API, в любом режиме — они добавлены рядом с существующими, поэтому клиент, который их не читает, изменений не заметит:
| Поле | Значение |
|---|---|
kind |
task (разовая), recurring_task (повторяется по правилу), occurrence (одно вхождение с датой) |
series_id |
Повторяющаяся задача, которой принадлежит запись. У самой повторяющейся задачи здесь её собственный id; у разовой — null |
series_chain_id |
Связывает повторяющиеся задачи, произошедшие от одной исходной серии — см. Разделение серии |
recurrence_summary |
То же правило словами, например every weekday |
current_occurrence |
Вхождение, которым предстоит заняться, целиком. null, когда открытых вхождений у повторяющейся задачи не осталось |
current_occurrence — это самое раннее открытое вхождение, включая просроченные: у заброшенной ежедневной задачи вы увидите самое старое невыполненное, а не завтрашнюю копию. Оно приходит полным объектом задачи, потому что отредактированное вхождение может отличаться от серии — своим названием, исполнителями, статусом или дедлайном, — и узнавать об этом отдельным запросом не нужно.
Новым интеграциям стоит всегда передавать view явно.
Список вхождений по дням (view=by_date)
Заголовок раздела «Список вхождений по дням (view=by_date)»Третий вопрос, на который отвечает список, — «что происходит между этими датами». Передайте
view=by_date вместе с диапазоном дат, и каждый день, на который выпадает повторяющаяся задача,
станет отдельной записью — включая даты, которых никто не касался: до вашего запроса отдельными
записями они не существуют.
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 }}Что важно знать про этот разрез:
- диапазон обязателен и не может превышать 31 день. Будущие вхождения собираются из правила, поэтому у запроса без границы нет конца;
- диапазон применяется к
dateзадачи — дню, на который она назначена, — а не к дедлайну.due_date_from/due_date_toпродолжают работать как обычные фильтры внутри разреза; - разовые задачи, назначенные на дни диапазона, приходят вместе с вхождениями; задача без даты — нет: её нечем поставить в день;
- отредактированное вхождение приходит один раз — той записью, которой оно на самом деле является;
- порядок — от начала диапазона, пагинация обычная:
next_cursor, покаhas_moreне станетfalse. Курсоры разрезов не взаимозаменяемы: курсор одного списка другой отклонит с400; closed_sinceс этим разрезом не сочетается (400) — тот фильтр сортирует по моменту выполнения, а двух порядков в одном списке не бывает. Про выполненное спрашивайте отдельно;- за один запрос разворачивается не больше 500 повторяющихся задач.
date_from / date_to работают и без view — как обычный фильтр по дню, на который
назначена задача.
Узнать следующее вхождение сразу после закрытия текущего
Заголовок раздела «Узнать следующее вхождение сразу после закрытия текущего»PATCH принимает тот же параметр view. С view=by_task закрытие вхождения возвращается самой повторяющейся задачей, у которой current_occurrence уже указывает на следующее, — дополнительный запрос не нужен:
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_..."}'Без view в ответе придёт изменённая запись, как и раньше.
Разделение серии
Заголовок раздела «Разделение серии»Правка «это и последующие вхождения» оставляет две повторяющиеся задачи — часть до изменения и часть после. Так же ведут себя календари, и именно поэтому в проекте могут оказаться две строки, выглядящие как один и тот же стендап.
Что они связаны, показывает series_chain_id:
- правка свойств (название, исполнитель, описание) оставляет обе половины в одной цепочке — общий
series_chain_id, разныеseries_id; - правка времени или правила повторения начинает по-настоящему новую серию, и у новой половины будет свой
series_chain_id.
Половина, у которой вхождения закончились, продолжает существовать (её прошлые вхождения — реальная история), но current_occurrence у неё null.
Чем заняться сейчас
Заголовок раздела «Чем заняться сейчас»В разрезе by_task фильтр state спрашивает, есть ли работа, а не смотрит на поле статуса:
curl "$BASE/tasks?view=by_task&state=open" -H "$AUTH"Повторяющаяся задача считается открытой, пока у неё есть открытое вхождение, и закрытой, когда их не осталось, — независимо от того, с какого статуса начинается каждое вхождение. Без view=by_task фильтр работает по собственному статусу записи, как и раньше.
Недавно выполненное — отдельный вопрос с отдельным ответом: выполненные вхождения это самостоятельные записи, поэтому они запрашиваются без view=by_task:
curl "$BASE/tasks?closed_since=2026-07-28" -H "$AUTH"Оба фильтра в контексте — в Фильтрации задач.
Повторяющиеся события в списке
Заголовок раздела «Повторяющиеся события в списке»GET /scheduled-events принимает тот же параметр view с тем же смыслом:
view |
Одна запись — это |
|---|---|
не указан / by_date |
одно вхождение: поведение по умолчанию, оно не менялось |
by_task |
одно событие; у повторяющегося внутри правило и ближайшее вхождение запрошенного диапазона в current_occurrence |
curl "$BASE/scheduled-events?from=2026-09-01T00:00:00Z&to=2026-09-30T00:00:00Z&view=by_task" -H "$AUTH"Диапазон from / to остаётся обязательным в обоих разрезах. Поля у события те же, что у задачи, —
kind (event, recurring_event, occurrence), series_id, series_chain_id,
recurrence_summary, current_occurrence, — поэтому один и тот же код читает и то, и другое.
Что поддерживается сегодня
Заголовок раздела «Что поддерживается сегодня»- Создание повторяющейся задачи или события.
- Чтение, обновление и удаление как серии, так и отдельного вхождения по его id.
- Список повторяющихся задач и событий как задач и событий (
view=by_task), с ближайшим вхождением внутри. - Список вхождений за период —
view=by_dateу задач и список по умолчанию у событий. - Выбор области действия правки через
scope— см. ниже.
Правка одного вхождения, последующих или всех
Заголовок раздела «Правка одного вхождения, последующих или всех»PATCH и DELETE принимают query-параметр scope:
scope |
На что действует |
|---|---|
не указан / only_this |
только то вхождение, к которому вы обратились — по умолчанию, как и раньше |
this_and_following |
это вхождение и все последующие; серия делится надвое |
all |
все вхождения, включая прошлые |
# Переименовать остаток серии, не трогая историю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": "Стендап (асинхронный)"}'Для задачи, которая не повторяется, параметр игнорируется.
Обращение к самой повторяющейся задаче требует scope
Заголовок раздела «Обращение к самой повторяющейся задаче требует scope»Если id, к которому вы обращаетесь в PATCH или DELETE, — это повторяющаяся задача целиком (series_id, а не id вхождения), и scope не передан, запрос отклоняется с 400. Случайно переименовать или удалить всю серию — необратимо, а два идентификатора похожи, поэтому API просит сказать, что вы имели в виду: передайте scope=all либо обратитесь к конкретному вхождению по его собственному id.
Что scope=all менять умеет, а что нет
Заголовок раздела «Что scope=all менять умеет, а что нет»scope=all записывает переданные поля по всей серии и не трогает остальное: если кто-то перенёс один вторник или сменил исполнителя на одной дате, это сохранится.
Некоторые поля размножить по серии нельзя, и они отклоняются с 400, а не применяются наполовину:
| Поле | Почему |
|---|---|
assignee_ids, custom_fields |
внутри лежат идентификаторы, привязанные к конкретной записи; копирование по вхождениям испортило бы записи |
tag_ids |
теги ставятся своим механизмом, со своим поведением для повторений |
due_date |
дедлайн каждого вхождения вычисляется из его собственной даты — общего значения у серии нет |
Смена расписания (даты или правила повторения) — это this_and_following, а не all: она делит серию, а не переписывает её прошлое.
DELETE ?scope=all удаляет повторяющуюся задачу вместе со всеми её вхождениями.