Search the docs

Find a page, a section, or an endpoint.

Recurring events

A rule rather than rows, and what changing one date means.

A repeating event is one row and a rule. Put an RRULE on it and the dates are expanded when something asks for them.

json
{
"title": "Standup",
"start": "2026-11-03T09:00:00",
"end": "2026-11-03T09:15:00",
"timezone": "Europe/London",
"recurrence": { "rule": "FREQ=WEEKLY;BYDAY=TU" }
}

The rule is RFC 5545's, the same one every calendar speaks. It comes back normalised, so what you read may not be character-for-character what you sent.

It repeats in wall-clock time. A weekly meeting at 09:00 in London is 09:00 in London in November and in June, across the clock change in between. That is why the event stores a wall time and a zone rather than an instant — adding seven days of milliseconds would drift it an hour twice a year.

Most dates have no row

The series is stored; its dates are not. That is what makes a meeting that runs for three years cheap, and it is why there is an endpoint for asking which dates a rule actually produces:

curl "https://pidgeon.ai/api/v1/identities/idn_4f2a1/calendar/events/evt_8c21/instances?after=2026-11-01T00:00:00Z&before=2026-12-01T00:00:00Z" \
-H "Authorization: Bearer $PIDGEON_API_KEY"

An instance's id is {seriesId}::{originalStart} — derived from the date rather than allocated. It does not change when that date is edited, which an id minted the first time somebody touched it could not promise.

The agenda expands series too, so you rarely need this directly. Reach for it when you want the dates themselves rather than what is on a day.

Changing one date, or the rest of them

A write against a series takes mode, and the id you name is a date, not the series.

modeWhat it means
single (default)That date, and only that one
futureThat date and everything after it

There is no all, and the absence is deliberate: naming the series id rather than an instance id already means the whole thing, so a third value would be a second way to say something the API can already say.

sh
PATCH /v1/identities/{id}/calendar/events/{seriesId}::{originalStart}?mode=single

future splits the series: the rule is truncated at that date and a new series starts there carrying the change. A weekly meeting moved four times is five series, for ever, which is the cost of being able to say "from here on" without rewriting history.

Deleting one date

DELETE with mode=single does not remove a row — there was no row. It writes an exception date onto the rule, because a rule with nothing else done to it would simply generate that date again the next time anybody asked.

You can see them: recurrence.exdates holds the dates that have been taken out, and rdates holds any added to the series that the rule does not produce.