The calendar.
Every organisation on AI3 has one: when it can take people, what is already in the diary, and the bookings its agents make. It exists so that an agent that says “booked” is telling the truth without the owner having connected anything. Google Calendar, Outlook and the rest are mirrors of it; the AI3 copy is the one the agents read.
What makes it a calendar for agents
- Availability is an answer. Ask for a date and get the times that may be offered, each with its spoken form (“Wednesday 23 September at 2pm”). The answer already allows for the opening hours, what is booked, the clear time the business wants between jobs, how many bookings can run at once (or which named resources are free), the notice it needs and how far ahead it books. Nothing an agent offers can be a time the business cannot take.
- A booking is checked when it is written, under a lock per organisation, so two agents cannot take the same slot. The one that loses is told the time is taken and given the nearest free ones.
- Writes are idempotent. Pass a
key(the receptionist uses the call id and the time) and a retried call returns the booking it already made. - Holds. A slot can be held for a few minutes while a caller decides; the hold becomes the booking or expires on its own.
- Every event says who made it (an agent by name, the owner, the customer), from where, and keeps a history of every move. The person on it is the customer record’s: the booking is a line on their timeline and an open item until it happens.
Over HTTP
An owner’s session, or a personal token from Settings as Authorization: Bearer ai3t_…. Times are the business’s own wall clock, YYYY-MM-DDTHH:MM; the timezone is on the settings, and a full ISO instant is accepted and converted.
GET https://ai3.co/api/o/<slug>/calendar settings, summary, the next fortnight
PATCH https://ai3.co/api/o/<slug>/calendar {"timezone","hours":{"mon":[["09:00","17:00"]]},"slotMinutes","defaultMinutes","bufferMinutes","capacity","resources":[],"services":[{"name","minutes"}],"leadMinutes","horizonDays","holdMinutes"}
GET https://ai3.co/api/o/<slug>/calendar/availability?date=2026-09-23&minutes=60&service=&resource=&window=afternoon&days=1
GET https://ai3.co/api/o/<slug>/calendar/events?from=2026-09-21&to=2026-09-28&status=confirmed&phone=&email=&q=
POST https://ai3.co/api/o/<slug>/calendar/events {"start","minutes","kind","title","service","resource","name","phone","email","notes","location","key","holdId","force"} → 201, or 409 with alternatives[]
POST https://ai3.co/api/o/<slug>/calendar/hold {"start","minutes","service","resource"} → {event, expiresAt}
GET https://ai3.co/api/o/<slug>/calendar/events/ev_… one event with its history
PATCH https://ai3.co/api/o/<slug>/calendar/events/ev_… {"title","service","name","phone","email","notes","location","externalRef"}
POST https://ai3.co/api/o/<slug>/calendar/events/ev_…/move {"start","minutes","resource","force"} → 200, or 409 with alternatives[]
POST https://ai3.co/api/o/<slug>/calendar/events/ev_…/close {"status":"cancelled"|"done"|"no-show","reason"}
GET https://ai3.co/api/o/<slug>/calendar.ics?key=… the diary as a calendar feed (the key is on the owner’s page)
A refusal to book carries alternatives: the nearest free times, with their spoken forms, so an agent can offer them in the same breath.
Over MCP
The same calendar through the AI3 MCP server: get_availability, list_events, book_event, hold_slot, reschedule_event, close_event. Writes need confirm: true; without it they say what they would do.
The rules an agent books by
Set on the owner’s calendar page. Hours per day, in spans; until set here they come from the receptionist’s profile or the listing. Slot every N minutes; a booking lasts the default unless the service names its own length. Clear time between bookings. At once: how many can overlap; or name resources (chairs, tables, vans) and each takes one at a time, with the free one assigned. Notice needed and books ahead bound the window. The owner may put anything in by force; an agent may not.
The webhook
With a webhook set, every change is one signed POST (x-ai3-signature: sha256=<hmac of the body with your secret>): {"action":"event.created"|"event.moved"|"event.cancelled"|"event.done"|"event.no_show","business":"…","event":{…}}. A reply with reference is kept on the event. A post that fails is noted on the owner’s page and never fails the booking.
What the receptionist does
It calls check_availability before offering a time and book_appointment once the caller agrees; both go through this calendar. A caller who wants to move or cancel is found by number with find_booking, and reschedule_booking or cancel_booking does the rest, with the reference read back.