Event Standard
Calendar events: appointments, all-day entries, recurring series and the calendars that hold them. Designed to round-trip with iCalendar (RFC 5545), so events can be imported from and exported to Android, desktop and web calendars. Dates follow the date rules, people are contacts, places are locations.
Appointment
A timed event. $start/$end are local wall-clock times in $timezone.
{
$type: 'event',
$version: 1,
$kind: 'appointment',
$id: '7f3c2a1e-5b8d-4c1a-9e2f-0a6b3d4c5e6f',
$title: 'Dentist',
$description: 'Bring the insurance card',
$start: '2026-10-02T18:00:00',
$end: '2026-10-02T19:00:00',
$timezone: 'Europe/Vienna',
$location: {
$type: 'location',
$kind: 'point',
$name: 'Dr. Huber',
$lat: 48.20012,
$lng: 16.36921,
$address: { $street: 'Mariahilfer Straße 12', $city: 'Vienna', $postal: '1070', $country: 'AT' }
},
$status: 'confirmed',
$visibility: 'private',
$reminders: [
{ $before: 'PT1H' },
{ $days_before: 1, $time: '20:00' }
],
$created: '2026-09-25T09:12:00Z',
$updated: '2026-09-25T09:12:00Z'
}
Recurring Appointment with Attendees
{
$type: 'event',
$version: 1,
$kind: 'appointment',
$id: 'standup-3b9d',
$title: 'Team Standup',
$start: '2026-10-05T09:30:00',
$duration: 'PT15M',
$timezone: 'Europe/Vienna',
$location: 'Room 4',
$url: 'https://meet.example.com/standup',
$organizer: { $type: 'contact', $name: 'John', $email: '[email protected]' },
$attendees: [
{ $type: 'contact', $name: 'Jane', $email: '[email protected]', $role: 'required', $response: 'accepted' },
{ $type: 'contact', $name: 'Tom', $email: '[email protected]', $role: 'optional', $response: 'pending' }
],
$recurrence: {
$rule: 'FREQ=WEEKLY;BYDAY=MO,WE,FR;UNTIL=20261231T225959Z',
$exclude: ['2026-10-26T09:30:00']
},
$overrides: [
{ $recurrence_id: '2026-10-14T09:30:00', $start: '2026-10-14T11:00:00', $location: 'Room 1' },
{ $recurrence_id: '2026-11-02T09:30:00', $status: 'cancelled' }
],
$busy: true
}
All-Day Event
Dates without time. A yearly birthday with the default reminders replaced by custom ones:
{
$type: 'event',
$version: 1,
$kind: 'allday',
$id: 'bday-jane',
$title: 'Jane\'s Birthday',
$start: '2026-11-03',
$recurrence: 'FREQ=YEARLY',
$reminders: [
{ $days_before: 1, $time: '20:00' },
{ $days_before: 0, $time: '08:00' }
],
$busy: false
}
// multi-day: $end is exclusive -- this is 20 to 24 December
{ $type: 'event', $version: 1, $kind: 'allday', $title: 'Vacation', $start: '2026-12-20', $end: '2026-12-25' }
Calendar
A named collection of events. Carries the defaults its events inherit.
{
$type: 'event',
$version: 1,
$kind: 'calendar',
$id: 'cal-private',
$name: 'Private',
$color: '#3b82f6',
$timezone: 'Europe/Vienna',
$visibility: 'private',
$owner: { $type: 'contact', $name: 'John', $platforms: [{ $url: 'https://ident.worldapi.org', $identifier: 'john' }] },
$reminders: [
{ $before: 'PT1H' },
{ $days_before: 1, $time: '20:00' }
],
$events: [ ... ]
}
Object Kinds
| $kind | Use case |
|---|---|
appointment |
Timed event: $start/$end are datetimes |
allday |
Whole days: $start/$end are dates (YYYY-MM-DD) |
calendar |
A collection of events with shared defaults |
Standard Properties
| Property | Type | Required | Description |
|---|---|---|---|
$type |
string | yes | Always 'event' |
$version |
number | yes | Standard version |
$kind |
string | yes | appointment, allday, or calendar |
$id |
string | yes | Globally unique, stable ID (becomes iCal UID) |
$title |
string | yes* | Short summary (events) |
$description |
string | no | Longer text |
$start |
string | yes* | Local datetime, UTC instant, or date (allday) |
$end |
string | no | Exclusive end, same format as $start |
$duration |
string | no | ISO 8601 duration, alternative to $end |
$timezone |
string | no | IANA zone, e.g. Europe/Vienna |
$location |
string | object | no | Free text or a location object |
$url |
string | no | Link, e.g. online meeting |
$organizer |
object | no | Contact |
$attendees |
array | no | Contacts with $role and $response |
$recurrence |
string | object | no | RRULE string, or { $rule, $exclude, $include } |
$overrides |
array | no | Changed instances of a recurring event |
$reminders |
array | no | Reminders; omitted = inherit defaults, [] = none |
$status |
string | no | confirmed (default), tentative, cancelled |
$visibility |
string | no | private (default), busy, public |
$busy |
boolean | no | Blocks time. Default true for appointment, false for allday |
$color |
string | no | CSS color |
$calendar |
string | no | $id of the calendar the event belongs to |
$name |
string | yes* | Calendar name |
$owner |
object | no | Calendar owner (contact) |
$events |
array | no | Events in a calendar |
$created |
string | no | UTC instant |
$updated |
string | no | UTC instant of last change |
* Required depends on $kind: events need $title and $start, calendars need $name.
Time Rules
$startwithout offset +$timezone-- wall-clock time in that zone (recommended). The event stays at 18:00 local across DST changes.$startwithout offset and no$timezone-- floating: same clock time wherever the viewer is.$timezonefalls back to the calendar's.$startending inZ-- an absolute instant. Use for events whose start and end are in different zones (flights).alldayuses dates only and never shifts with timezones.$endis exclusive (as in iCal). Missing$endand$duration: appointment = zero length, allday = one day.
Recurrence
$recurrence uses iCalendar RRULE syntax verbatim (RFC 5545 §3.3.10), evaluated in the event's $timezone. The short form is the rule string alone.
| Property | Description |
|---|---|
$rule |
RRULE value, e.g. FREQ=MONTHLY;BYMONTHDAY=1;COUNT=12 |
$exclude |
Instances removed from the series (their original $start) |
$include |
Extra instances outside the rule |
$overrides entries hold $recurrence_id (the original $start of the instance) plus only the properties that differ. $status: 'cancelled' in an override equals an $exclude. To change "this and all following", end the series with UNTIL and start a new event.
Reminders
| Property | Type | Description |
|---|---|---|
$before |
string | ISO 8601 duration before $start, e.g. PT1H, P1D. PT0S = at start |
$days_before |
number | Calendar days before the start date (0 = same day) |
$time |
string | Wall-clock HH:MM on that day, in the event's timezone. Use with $days_before |
$at |
string | Absolute UTC instant (one-off events only) |
$kind |
string | notification (default) or email |
Each reminder uses exactly one of $before, $days_before+$time, or $at. For all-day events, $before counts back from 00:00 of the start date. For recurring events, reminders apply to every instance. Resolution order: the event's $reminders, then the calendar's $reminders, then the app settings. A list that is present replaces the defaults. It does not merge with them.
Attendees
Attendees are contact objects with two extra properties:
| Property | Values |
|---|---|
$role |
required (default), optional, chair |
$response |
pending (default), accepted, declined, tentative |
Privacy
$visibility controls what others see when a calendar is shared: private = nothing, busy = only the time slot, public = everything. Single private fields (notes, internal IDs) use the _ prefix, e.g. _notes, _android_id, and are never exported to shared or public views.
Mapping to iCalendar (RFC 5545)
| WorldAPI | iCalendar |
|---|---|
calendar |
VCALENDAR (X-WR-CALNAME, X-WR-TIMEZONE) |
appointment / allday |
VEVENT |
$id |
UID |
$title / $description |
SUMMARY / DESCRIPTION |
$start + $timezone |
DTSTART;TZID=Europe/Vienna:20261002T180000 |
$start UTC / floating |
DTSTART:20261002T160000Z / DTSTART:20261002T180000 |
$start allday |
DTSTART;VALUE=DATE:20261103 |
$end / $duration |
DTEND (exclusive in both) / DURATION |
$location |
LOCATION (name + address as text), GEO:lat;lng |
$url / $color |
URL / COLOR (RFC 7986) |
$organizer |
ORGANIZER;CN=John:mailto:[email protected] |
$attendees |
ATTENDEE;CN=..;ROLE=REQ-PARTICIPANT|OPT-PARTICIPANT|CHAIR;PARTSTAT=NEEDS-ACTION|ACCEPTED|DECLINED|TENTATIVE |
$recurrence |
RRULE, EXDATE, RDATE |
$overrides[] |
Extra VEVENT with same UID + RECURRENCE-ID |
$status |
STATUS:CONFIRMED|TENTATIVE|CANCELLED |
$visibility |
CLASS:PRIVATE|CONFIDENTIAL|PUBLIC |
$busy |
TRANSP:OPAQUE|TRANSPARENT |
$reminders $before |
VALARM TRIGGER:-PT1H |
$reminders $days_before+$time |
VALARM TRIGGER computed against DTSTART (18:00 event, day before 20:00 = -PT22H; allday = -PT4H) |
$reminders $at |
VALARM TRIGGER;VALUE=DATE-TIME:20261001T180000Z |
reminder $kind |
ACTION:DISPLAY / ACTION:EMAIL |
$created / $updated |
CREATED / LAST-MODIFIED |
Exports only reach inherited default reminders if they are resolved and written as explicit VALARMs. On import, a TRIGGER becomes $before. Android stores reminders as minutes before the start. A $days_before+$time reminder has to be converted to minutes for each event, and it can be off by an hour across a DST change.