The one thing to read first
A repeating event does not happen at an instant. “Every weekday at 09:00” means nine o’clock on the clock on the wall — and the instant that names moves by an hour when the clocks change. A series stored as a UTC instant plus a rule is therefore wrong for half the year. So Ruber stores a local time, an IANA zone and a duration, and this API is explicit about which of the two you are looking at:What is on
One day at a time
date together with from or to is refused rather than merged —
they would have to agree, and there is no useful answer when they do not.
08:00Z and then 09:00Z — Britain left summer
time on the 25th. The meeting did not move; 09:00 stayed 09:00, which is the
whole point of storing a local time.
Overlap, not containment. An event that starts before your window and ends
inside it is returned. Asking only for events starting in a range is how a
“what is on today” call loses the overnight shift.
One series
starts_at_local and time_zone (the rule’s
definition), starts_at (the instant the first occurrence begins),
duration_minutes, rrule, exdates and series_ends_at — null for a rule
with no end.
Create an event
The three forms of starts_at
Both timed forms are unambiguous and both are honoured exactly as written. The
middle one is what a repeating event wants.
Prefer
duration_minutes over ends_at for a repeating event. An end
instant describes the first occurrence only, so a series expressed that way
changes length halfway through the year, when the clocks go back.2026-02-30 is a 400 —
not the 2nd of March, which is what naive date parsing produces and what would
otherwise silently anchor a whole series two days out.
Change or delete a series
PATCH changes only the fields you name — but timing is one field. Naming
time_zone, starts_at, duration_minutes, ends_at, all_day or rrule
recomputes the whole timing together, because writing a new zone against an
unchanged local time does not translate an event, it reinterprets it.
starts_at_local stays 09:00; starts_at moves from 08:00Z to 07:00Z.
When does this one repeat
GET /v1/events answers “what is on” across the whole calendar. This answers
“when does this happen”, which is the other question — and getting it from
the list endpoint means asking for every event in a window and discarding all
but one, over and over, to walk a series forward.
Takes from/to (default: now to 90 days out), or date and time_zone, and
limit.
08:00Z, then 09:00Z. The meeting stayed at
09:00 local throughout.
has_more is true when the limit was reached, so a caller walking a long series
knows to ask again from the last occurrence rather than concluding it ended.
When is anybody free
from/to (default: now to 7 days out, at most 92 days), or date and
time_zone, and calendar.
free is the gaps between them, clipped to
the window.
No working hours are applied here. This endpoint does not know whose day it
is describing or when they work, and inventing 09:00–17:00 would make “free”
mean something you did not ask for.Working hours live on a booking link — see
its slots, which applies opening
hours, a buffer, a lead time and a horizon on top of exactly this.
Calendars
name is required and unique to nothing — two calendars may share one.
time_zone is the default every new event on the calendar inherits; changing it
does not move events that already exist.
Not available yet
Editing one occurrence of a series. “Move just next Tuesday’s” splits the rule and writes a separate override row. The app does it; the API does not yet, because a half-right implementation leaves a series with a hole or a duplicate. Overrides made in the app are readable here, flaggedis_override.
Encrypted events are never returned. A Private event’s title, location and
description are ciphertext, and the key that reads them is in one person’s
browser. Such events are omitted rather than returned nameless — see
Private mailboxes for
the same rule applied to mail.
Attendees. Invitations and RSVPs are in the app and are not exposed yet.
Booking links are — see Booking.
Errors
See Errors for the full response shape.