Managing Events
Calendar is in private beta in US production (api.agentmail.to). The beta is
not available in EU production (api.agentmail.eu). Organizations without
access receive a 403 with the message Calendar access is not enabled for this organization. Email support@agentmail.to
to request access. Upgrade to the latest Python or TypeScript SDK to use the
calendar methods.
This page covers events that happen once. Recurring events use the same endpoints with a few additions; see Recurring Events.
Create an event
POST /v0/inboxes/{inbox_id}/calendar/events creates an event. Only title, start and end are required.
The response is 201 with the stored event and an operation_id:
operation_id identifies this change. The calendar.event.created webhook it triggers carries the same value as its event_id, so you can tell your own changes apart from changes made elsewhere.
Fields
All-day events
This event covers three days, November 2 through November 4. Its start_at is midnight at the start of November 2 in London and its end_at is midnight at the end of November 4.
Safe retries with client_id
Networks fail. If you retry a create without a client_id, you can end up with two events. With a client_id, a retry that has the same client_id and the same body returns the original event with status 200 instead of creating another. Sending the same client_id with a different body returns 409 idempotency_conflict.
Retries are recognized for as long as the event exists. Once it has been deleted, a retry returns 404.
Derive the client_id from something stable in your system, such as the meeting request it fulfills. A create with send_invites: true is charged against your send limits, but a retry that returns the original event is not charged again; see Send limits.
A create that returns 409 race_condition either conflicted with a concurrent change or hit
the calendar’s event limit. Retry it with the same client_id; if it keeps failing, check
event_count with Get Calendar, because retries cannot succeed once the calendar is full.
Get an event
Reads can trail a write made moments earlier by a few seconds. Pass consistency="primary" to always see the latest state.
List events
The agenda: what is on the calendar
GET /calendar/agenda returns every date in a window, in start order. Recurring events are expanded into their dates, and cancelled dates are left out.
- The window defaults to now through 90 days from now, and can be at most 366 days wide. A wider window returns
400query_range_too_wide; page through long periods in pieces. - Dates of recurring events appear only up to about 90 days from now. To list a recurring event’s later dates, use List Event Instances.
- By default an event that started before
afterbut is still running is included. Passinclude_overlapping=falseto get only events that start inside the window. - Agenda items leave out
description,metadataandattendeesto stay small (attendee_countis still there). Get an event by itsevent_idfor the full object. - Like other reads, the agenda can trail a change made moments earlier by a few seconds. Pass
consistency=primaryto see an event you just created or changed.
Stored events: what you have created
GET /calendar/events returns one item per one-off or recurring event, plus one item per individually edited date of a recurring event. Items are full event objects ordered by most recent update, and cancelled events are included. Use it to sync the calendar into your own database. It is always read in the region that serves you, so it can trail a change by a few seconds and does not take consistency.
Pagination
Both lists return up to limit items (1 to 100, default 50). When there are more, the response has a next_page_token; pass it as page_token with the same other parameters to get the next page.
Update an event
PATCH an event with only the fields you want to change:
The update applies to the event as it is when the request arrives. If another change lands while your request is running, you get 409 race_condition; retry it. To make an update conditional, send the event’s etag from your last read or write in If-Match. If someone changed the event in between, you get 412 precondition_failed instead; read it again, reapply your change and retry. Use If-Match when you replace attendees, so you don’t overwrite a response that arrived by email in the meantime:
Rules for updates:
- Send
startandendtogether, even to change only one of them: send the other unchanged. (A single date of a recurring event also acceptsendalone; see Recurring Events.) - Set a field to
nullto clear it (description,location,metadata).attendeesreplaces the whole list. - To switch between timed and all-day, send
all_day,start,endandtimezonetogether. - Add
send_invites: trueto email the change to attendees. Only events your inbox organizes can send.
The calendar.event.updated webhook includes a previous object with the old values of the fields that changed.
Events that have started or ended
An event’s start becomes fixed once it begins:
A schedule change that is no longer allowed returns 409 event_already_started.
Delete an event
The event disappears from reads right away, and the response is 202 with a deletion_id. Cleanup finishes in the background, usually within seconds. A retry returns the same deletion_id while cleanup runs. Once cleanup has finished the event no longer exists, so a retry returns 404; treat that as success.
Two headers are optional. If-Match with the event’s etag makes the delete conditional (412 if the event changed). Idempotency-Key (1 to 128 visible ASCII characters) names the delete; without one, retries of the same delete share a key derived from the event and send_invites, so keep send_invites the same when you retry. A keyless retry that changes it is a different delete: while removal runs it returns 404 and sends no cancellations. Keys are unique across your organization, so reusing one to delete a different event returns 409 idempotency_conflict.
Once a delete is accepted, no further calendar.event.starting or calendar.event.ending webhook is sent for the event. A calendar.event.deleted webhook is sent.
To keep an event on the calendar but stop its start and end webhooks, set status to cancelled instead of deleting it.
Change the calendar’s default time zone
New events without a timezone use the calendar’s timezone, which starts as UTC. Changing it does not move existing events.
As with events, send the calendar’s etag in If-Match to make the change conditional.
