Skip to content

Events

Events are time-bound entries within a calendar. They support statuses, metadata, and trigger webhooks on changes.

POST /v1/calendars/:cal_id/events
FieldTypeRequiredDefaultDescription
titlestringYes1–500 characters
start_timestringYesISO 8601 datetime (UTC)
end_timestringYesISO 8601 datetime (must be after start_time)
descriptionstringNoFree-text description
all_daybooleanNofalseMark as all-day event
statusstringNoconfirmedconfirmed, tentative, cancelled, or hold
metadataobjectNoKey-value pairs, max 16KB JSON
remindersinteger[] | nullNoinheritMinutes before start_time to fire an event.reminder webhook (e.g. [10, 1440] = 10 min and 1 day before). Max 5 entries, each 1–40320 (28 days). null inherits the calendar’s default_reminders; [] means no reminders. See Reminders.
recurrence_rulestringNoRFC 5545 RRULE subset (without the RRULE: prefix) that makes this event a recurring series, e.g. FREQ=WEEKLY;BYDAY=MO,WE;COUNT=12. Not allowed with status=hold. See Recurring events.
hold_expires_atstringConditionalRequired when status=hold. ISO 8601 datetime 30s–15min in the future.
hold_priorityintegerNo0Only valid with status=hold. 0–100. Higher-priority holds pre-empt lower.
Terminal window
curl -X POST https://api.chronary.ai/v1/calendars/cal_x1y2z3/events \
-H "Authorization: Bearer chr_sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"title": "Strategy sync with Acme Corp",
"start_time": "2026-04-07T14:00:00Z",
"end_time": "2026-04-07T14:30:00Z",
"description": "Quarterly strategy alignment",
"metadata": { "deal_id": "deal_789" }
}'
{
"id": "evt_m1n2o3",
"calendarId": "cal_x1y2z3",
"orgId": "org_a1b2c3",
"title": "Strategy sync with Acme Corp",
"description": "Quarterly strategy alignment",
"startTime": "2026-04-07T14:00:00Z",
"endTime": "2026-04-07T14:30:00Z",
"allDay": false,
"status": "confirmed",
"source": "internal",
"metadata": { "deal_id": "deal_789" },
"reminders": null,
"holdExpiresAt": null,
"holdPriority": 0,
"recurrenceRule": null,
"recurrenceExdates": [],
"deletedAt": null,
"createdAt": "2026-04-04T12:00:00Z",
"updatedAt": "2026-04-04T12:00:00Z"
}

reminders is null when the event inherits its calendar’s default_reminders.

Triggers an event.created webhook.

StatusTypeCause
400bad_requestInvalid recurrence_rule (unsupported key, COUNT and UNTIL together, start_time doesn’t match the rule pattern, …)
403plan_requiredFree-plan recurrence_rule is unbounded or ends more than 90 days after the series start (feature: "unbounded_recurrence")
404not_foundCalendar not found
429quota_exceededActive recurring-series limit for your plan reached

GET /v1/calendars/:cal_id/events
ParameterTypeDefaultDescription
start_afterdatetimeEvents starting after this time
start_beforedatetimeEvents starting before this time
statusstringconfirmed, tentative, cancelled, or hold
sourcestringinternal or external_ical
limitinteger501–200
offsetinteger0Pagination offset
expandbooleanfalseExpand recurring series into per-occurrence instances. Requires both start_after and start_before (window at most 366 days). See Expanding occurrences.
Terminal window
curl "https://api.chronary.ai/v1/calendars/cal_x1y2z3/events?start_after=2026-04-01T00:00:00Z&start_before=2026-04-30T23:59:59Z&status=confirmed" \
-H "Authorization: Bearer chr_sk_your_key_here"
{
"data": [ ... ],
"total": 15,
"limit": 50,
"offset": 0
}

With expand=true, total is the number of items in the requested window (masters expanded into instances) rather than the number of stored events.

StatusTypeCause
400validation_errorexpand=true without both time bounds, or a window wider than 366 days
404not_foundCalendar not found

Returns events across all calendars owned by an agent.

GET /v1/agents/:agent_id/events

Same query parameters as calendar-scoped listing.

Terminal window
curl "https://api.chronary.ai/v1/agents/agt_a1b2c3d4/events?start_after=2026-04-07T00:00:00Z" \
-H "Authorization: Bearer chr_sk_your_key_here"

GET /v1/calendars/:cal_id/events/:id
StatusTypeCause
404not_foundEvent not found

PATCH /v1/calendars/:cal_id/events/:id
FieldTypeDescription
titlestring1–500 characters
descriptionstring | nullSet to null to clear
start_timedatetimeISO 8601
end_timedatetimeISO 8601
all_dayboolean
statusstringconfirmed, tentative, or cancelled
metadataobjectReplaces existing metadata
remindersinteger[] | nullReplaces the event’s reminders. null reverts to inheriting the calendar’s default_reminders; [] disables reminders. See Reminders.
recurrence_rulestring | nullSets or changes the series rule — a full-series edit. null converts the series back to a one-off event. Changing the rule or start_time clears any individually cancelled occurrences. See Recurring events.

At least one field must be provided. Triggers an event.updated webhook.

StatusTypeCause
400bad_requestInvalid recurrence_rule, or the (new) start_time doesn’t match the rule pattern
403forbiddenExternal iCal events are read-only
403plan_requiredFree-plan recurrence_rule is unbounded or ends more than 90 days after the series start
404not_foundEvent or calendar not found
429quota_exceededAdding a rule to an existing event would exceed the active recurring-series limit

DELETE /v1/calendars/:cal_id/events/:id

Returns 204 No Content. Triggers an event.deleted webhook. On a recurring series, a bare DELETE cancels the whole series.

ParameterTypeDefaultDescription
occurrence_startdatetimeRecurring series only: cancel just the occurrence starting at this ISO 8601 time. The rest of the series is unaffected.

With occurrence_start, the response is 200 OK with the updated series master (the cancelled start is appended to recurrenceExdates) and an event.updated webhook fires instead of event.deleted. See Cancelling occurrences.

StatusTypeCause
400bad_requestoccurrence_start is not an active occurrence of this series
403forbiddenExternal iCal events are read-only
404not_foundEvent or calendar not found
409conflictoccurrence_start provided but the event is not a recurring series

Reminders are timed notifications fired ahead of an event’s start_time. Each entry is an integer number of minutes before start — e.g. [10, 1440] fires 10 minutes and 1 day before. An array may hold up to 5 entries, each between 1 and 40320 (28 days).

Reminders resolve in precedence order:

  1. The event’s own reminders field, if set to an array.
  2. The calendar’s default_reminders, if the event’s reminders is null.
  3. The system default of [10] (10 minutes before) if neither is set.

A null value means “inherit from the level above”; an empty array [] means “explicitly no reminders” and stops resolution — it does not fall through to the default.

Each resolved reminder fires an event.reminder webhook at start_time minus the offset. Reminders fire only for events with status: "confirmed" — tentative events, holds, and cancelled events do not fire reminders. Reminders are also emitted as VALARM components in the iCal feed so subscribed calendar apps display the alarm.


Pass recurrence_rule when creating an event to turn it into a recurring series. One event row is the series — it counts once toward your monthly events quota no matter how many times it repeats, and once toward your plan’s active recurring-series cap.

recurrence_rule is an RFC 5545 RRULE subset without the RRULE: prefix, e.g. FREQ=WEEKLY;BYDAY=MO,WE,FR;COUNT=12:

PartValuesNotes
FREQDAILY, WEEKLY, MONTHLY, YEARLYRequired
INTERVAL1–99Optional, default 1 (e.g. INTERVAL=2 with FREQ=WEEKLY = every other week)
COUNT1–730Total number of occurrences. Mutually exclusive with UNTIL.
UNTILYYYYMMDDTHHMMSSZ or YYYYMMDDLast possible occurrence start (inclusive), UTC basic format. Mutually exclusive with COUNT.
BYDAYWeekly: list like MO,WE,FR · Monthly: single ordinal like 2TU or -1FRWeekly and monthly only
BYMONTHDAY1–28 or -1 (last day of the month)Monthly only; mutually exclusive with BYDAY

Rules the engine cannot expand are rejected with 400 bad_request. Two behaviors to know:

  • Skipped, never clamped — occurrences that don’t exist in a month (day 29–31, Feb 29 on YEARLY) are skipped, which is why BYMONTHDAY stops at 28 (use -1 for “last day”).
  • start_time must match the rule — the series master’s start_time is always the first occurrence, so a FREQ=WEEKLY;BYDAY=MO rule requires a start_time that falls on a Monday.

recurrence_rule is not allowed on holds.

PlanActive seriesRule bounds
Free5Must be bounded (COUNT or UNTIL) and end within 90 days of the series start
Pro250Unbounded rules allowed
CustomCustomCustom

Exceeding the active-series cap returns 429 quota_exceeded; an unbounded (or over-window) rule on the Free plan returns 403 plan_required with feature: "unbounded_recurrence". Deleting a series frees its slot. Your current count is reported by GET /v1/usage as recurring_events.

List endpoints return the series master as a single event by default. To see individual occurrences, pass expand=true with both start_after and start_before (window at most 366 days):

Terminal window
curl "https://api.chronary.ai/v1/calendars/cal_x1y2z3/events?expand=true&start_after=2026-04-01T00:00:00Z&start_before=2026-04-30T23:59:59Z" \
-H "Authorization: Bearer chr_sk_your_key_here"

Expanded instances share the master’s id and carry two extra fields:

{
"id": "evt_m1n2o3",
"title": "Weekly sync",
"startTime": "2026-04-13T09:00:00Z",
"endTime": "2026-04-13T09:30:00Z",
"recurrenceRule": "FREQ=WEEKLY;BYDAY=MO;COUNT=12",
"recurringEventId": "evt_m1n2o3",
"originalStartTime": "2026-04-13T09:00:00Z"
}
FieldDescription
recurringEventIdThe series master’s id (present only on expanded instances)
originalStartTimeThe occurrence start this instance was generated for — pass it to occurrence_start to cancel that occurrence

Expansion also works on GET /v1/agents/:agent_id/events. Availability queries always account for recurring occurrences — each one blocks slots like a regular event.

PATCH operates on the whole series: changing title, times, or the rule applies to every occurrence. Set recurrence_rule: null to convert the series back to a one-off event. Changing the rule or start_time resets any individually cancelled occurrences (clears recurrenceExdates).

  • One occurrenceDELETE .../events/:id?occurrence_start=2026-04-13T09:00:00Z returns 200 OK with the updated master; the series lives on and an event.updated webhook fires.
  • Whole series — bare DELETE .../events/:id returns 204 No Content and fires event.deleted.

To modify a single occurrence (different time or title for one week), cancel that occurrence and create a standalone event in its place:

Terminal window
# 1. Cancel the April 13 occurrence
curl -X DELETE "https://api.chronary.ai/v1/calendars/cal_x1y2z3/events/evt_m1n2o3?occurrence_start=2026-04-13T09:00:00Z" \
-H "Authorization: Bearer chr_sk_your_key_here"
# 2. Create a one-off replacement
curl -X POST https://api.chronary.ai/v1/calendars/cal_x1y2z3/events \
-H "Authorization: Bearer chr_sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "title": "Weekly sync (moved)", "start_time": "2026-04-13T14:00:00Z", "end_time": "2026-04-13T14:30:00Z" }'

Creating, updating, and deleting a series fires the regular event.created / event.updated / event.deleted webhooks for the master. Cancelling a single occurrence (?occurrence_start=) fires event.updated.

Each occurrence also fires its own event.started, event.ended, and event.reminder lifecycle webhooks at the occurrence’s times. Those payloads carry occurrence_start (the ISO start of the occurrence), and their start_time / end_time are the occurrence’s own times while event_id remains the series master’s id. Occurrences cancelled via occurrence_start do not fire.

In the iCal feed, a series is emitted as a single VEVENT with RRULE (and EXDATE for cancelled occurrences) — calendar apps expand it client-side.


Holds are tentative reservations that auto-release after a short TTL (max 15 minutes). Use them to eliminate race conditions when multiple agents compete for the same slot: place a hold first, decide second.

A hold behaves like a normal event for availability purposes — it blocks the slot against queries and other holds — but auto-expires if not confirmed.

  1. Create a holdPOST /v1/calendars/:cal_id/events with status: "hold" and hold_expires_at (30s–15min in the future). Fires event.hold_created.
  2. ConfirmPUT /v1/events/:id/confirm promotes it to status: "confirmed". Fires event.hold_confirmed. event.started / event.ended lifecycle webhooks fire at the scheduled times.
  3. ReleasePUT /v1/events/:id/release manually releases the hold. Fires event.hold_released.
  4. Auto-expire — if not confirmed or released by hold_expires_at, the hold is cancelled and event.hold_expired fires.

When creating a hold that overlaps an existing hold on the same calendar:

  • Higher priority wins. If the new hold’s hold_priority is strictly greater than the existing one’s, the existing hold is cancelled (event.hold_expired fires) and the new hold is created.
  • Equal or lower priority → 409 conflict. The new hold is rejected with error hold_conflict.

A single create request with priority bumping therefore fires two webhooks in order: event.hold_expired (for the bumped hold), then event.hold_created (for the new one).

PUT /v1/events/:id/confirm
Terminal window
curl -X PUT https://api.chronary.ai/v1/events/evt_m1n2o3/confirm \
-H "Authorization: Bearer chr_sk_your_key_here"

Returns the confirmed event (200 OK). Fires event.hold_confirmed.

StatusTypeCause
403forbiddenExternal iCal events cannot be confirmed
404not_foundEvent not found
409conflictEvent is not a hold (not_a_hold), or the hold has already expired (hold_expired)
PUT /v1/events/:id/release
Terminal window
curl -X PUT https://api.chronary.ai/v1/events/evt_m1n2o3/release \
-H "Authorization: Bearer chr_sk_your_key_here"

Returns the released (now cancelled) event (200 OK). Fires event.hold_released.

StatusTypeCause
403forbiddenExternal iCal events cannot be released
404not_foundEvent not found
409conflictEvent is not a hold
  • PATCH on a hold is rejected with 400 invalid_transition — use /confirm, /release, or wait for TTL.
  • PATCH cannot set status=hold on a non-hold event.
  • hold_expires_at is write-once at create time.
  • Holds count against your events quota (same as regular event creation).