Availability
The availability engine finds open time slots across Chronary calendars and any Google Calendar or Microsoft Outlook sources a human has authorized for the queried agents. It supports single-agent, single-calendar, and cross-agent queries.
Three availability endpoints
Section titled “Three availability endpoints”| Endpoint | Scope | Use case |
|---|---|---|
GET /v1/agents/:id/availability | All calendars for one agent | ”When is this agent free?” |
GET /v1/calendars/:id/availability | Single calendar | ”When is this calendar free?” |
GET /v1/availability | Multiple agents | ”When are all these agents free?” |
Single-agent availability
Section titled “Single-agent availability”Returns available time slots across all of an agent’s calendars:
curl "https://api.chronary.ai/v1/agents/agt_a1b2c3d4/availability?start=2026-04-07T09:00:00Z&end=2026-04-07T17:00:00Z&duration=30m" \ -H "Authorization: Bearer chr_sk_your_key_here"chronary availability agent agt_a1b2c3d4 \ --start "2026-04-07T09:00:00Z" \ --end "2026-04-07T17:00:00Z" \ --slot-duration 30mconst params = new URLSearchParams({ start: '2026-04-07T09:00:00Z', end: '2026-04-07T17:00:00Z', duration: '30m',});const response = await fetch( `https://api.chronary.ai/v1/agents/${agentId}/availability?${params}`, { headers: { 'Authorization': 'Bearer chr_sk_your_key_here' } });const { slots } = await response.json();from chronary import Chronary
client = Chronary()
availability = client.availability.get( agent_id="agt_a1b2c3d4", start="2026-04-07T09:00:00Z", end="2026-04-07T17:00:00Z", duration="30m",)print(f"{len(availability.slots)} available slots")Response:
{ "agents": ["agt_a1b2c3d4"], "slots": [ { "start": "2026-04-07T09:00:00Z", "end": "2026-04-07T09:30:00Z" }, { "start": "2026-04-07T09:30:00Z", "end": "2026-04-07T10:00:00Z" }, { "start": "2026-04-07T10:00:00Z", "end": "2026-04-07T10:30:00Z" }, { "start": "2026-04-07T15:00:00Z", "end": "2026-04-07T15:30:00Z" } ], "availability_state": "complete", "sources": { "chronary": "current", "external": "current", "last_synced_at": "2026-04-07T08:58:00Z" }, "warnings": []}Query parameters
Section titled “Query parameters”| Parameter | Type | Required | Description |
|---|---|---|---|
start | datetime | Yes | Start of the time range (ISO 8601) |
end | datetime | Yes | End of the time range (must be after start) |
duration | string | No | 15m, 30m (default), 45m, 1h, or 2h |
slot_duration | string | No | Deprecated alias for duration. Still accepted; sending both with different values returns 400 Bad Request |
include_busy | boolean | No | Include busy slots in response (default false) |
allow_stale | boolean | No | Return slots from a usable stale human-calendar cache. Defaults to false (fail closed); use only for non-committing exploration |
Single-calendar availability
Section titled “Single-calendar availability”Same parameters, scoped to one calendar:
curl "https://api.chronary.ai/v1/calendars/cal_x1y2z3/availability?start=2026-04-07T09:00:00Z&end=2026-04-07T17:00:00Z&duration=1h" \ -H "Authorization: Bearer chr_sk_your_key_here"Cross-agent availability
Section titled “Cross-agent availability”Find time slots where multiple agents are all free:
curl "https://api.chronary.ai/v1/availability?agents=agt_a1b2c3d4,agt_e5f6g7h8&start=2026-04-07T09:00:00Z&end=2026-04-07T17:00:00Z&duration=30m" \ -H "Authorization: Bearer chr_sk_your_key_here"chronary availability cross \ --agents "agt_a1b2c3d4,agt_e5f6g7h8" \ --start "2026-04-07T09:00:00Z" \ --end "2026-04-07T17:00:00Z" \ --slot-duration 30mconst params = new URLSearchParams({ agents: 'agt_a1b2c3d4,agt_e5f6g7h8', start: '2026-04-07T09:00:00Z', end: '2026-04-07T17:00:00Z', duration: '30m',});const response = await fetch( `https://api.chronary.ai/v1/availability?${params}`, { headers: { 'Authorization': 'Bearer chr_sk_your_key_here' } });const { slots } = await response.json();availability = client.availability.find_meeting_time( agents=["agt_a1b2c3d4", "agt_e5f6g7h8"], start="2026-04-07T09:00:00Z", end="2026-04-07T17:00:00Z", duration="30m",)for slot in availability.slots: print(f"{slot.start} — {slot.end}")Additional parameters for cross-agent queries
Section titled “Additional parameters for cross-agent queries”| Parameter | Type | Description |
|---|---|---|
agents | string | Required. Comma-separated agent IDs |
calendars | string | Optional. Comma-separated calendar IDs to narrow scope |
Plan limits
Section titled “Plan limits”Cross-agent availability has plan-based limits:
| Plan | Max agents per query | Max date range |
|---|---|---|
| Free | 5 | 30 days |
| Pro | 20 | 90 days |
| Custom | Custom | Custom |
Exceeding these limits returns 400 Bad Request.
How availability is calculated
Section titled “How availability is calculated”- The engine collects all events from the relevant calendars in the requested time range
- Events with status
confirmedortentativeblock time slots - Events with status
cancelleddo not block time - Opaque busy intervals from human calendars granted to each queried agent are included
- Events imported via iCal subscriptions are also included
- Recurring series are expanded into their occurrences within the range — each occurrence blocks slots like a regular event (individually cancelled occurrences do not)
- The time range is divided into slots of the requested duration
- Slots that overlap with any blocking event are excluded (or marked busy if
include_busy=true)
Availability completeness and source health
Section titled “Availability completeness and source health”Every response includes availability_state, sources, and warnings. Treat slots as safe to schedule only when availability_state is complete.
| State | Meaning |
|---|---|
complete | Chronary and all required authorized human sources are current |
stale | A usable human-calendar cache exists but is older than the freshness target |
partial | One or more required sources cannot cover the full requested window |
unavailable | Required source data is unavailable |
By default, stale, partial, or unavailable human data produces no slots and an availability_incomplete warning. Pass allow_stale=true only when showing exploratory suggestions that will be rechecked before committing. MCP stays fail-closed and intentionally has no override.
See Human Calendar Connections for setup, agent mappings, privacy, and publishing.
Working hours and buffers
Section titled “Working hours and buffers”Every calendar can have optional availability rules that shape the busy blocks fed into the engine. Rules live under PUT /v1/calendars/:id/availability-rules — see the availability rules reference for the full schema.
Two things you can configure:
- Buffers —
buffer_before_minutesandbuffer_after_minutes(0–120) expand every event on the calendar. A 30-minute event with a 15-minute before/after buffer occupies 60 minutes of busy time in free/busy calculations. - Working hours — per-day local start/end times in an IANA
timezone. Any time outside working hours is treated as busy. Days you omit are entirely non-working.
Let’s walk through it.
Step 1 — Set the rules
Section titled “Step 1 — Set the rules”Set 15-minute buffers and 9am–5pm Mon–Fri working hours in America/New_York:
curl -X PUT https://api.chronary.ai/v1/calendars/cal_01H9X4M2P5R8T6V0/availability-rules \ -H "Authorization: Bearer chr_sk_your_key_here" \ -H "Content-Type: application/json" \ -d '{ "buffer_before_minutes": 15, "buffer_after_minutes": 15, "working_hours": { "mon": { "start": "09:00", "end": "17:00" }, "tue": { "start": "09:00", "end": "17:00" }, "wed": { "start": "09:00", "end": "17:00" }, "thu": { "start": "09:00", "end": "17:00" }, "fri": { "start": "09:00", "end": "17:00" } }, "timezone": "America/New_York" }'import Chronary from '@chronary/sdk';
const client = new Chronary();
await client.calendars.setAvailabilityRules('cal_01H9X4M2P5R8T6V0', { buffer_before_minutes: 15, buffer_after_minutes: 15, working_hours: { mon: { start: '09:00', end: '17:00' }, tue: { start: '09:00', end: '17:00' }, wed: { start: '09:00', end: '17:00' }, thu: { start: '09:00', end: '17:00' }, fri: { start: '09:00', end: '17:00' }, }, timezone: 'America/New_York',});Saturday and Sunday are omitted, so they will be treated as fully non-working.
Step 2 — Put an event on the calendar
Section titled “Step 2 — Put an event on the calendar”Assume there’s one confirmed event on Wednesday, 2026-04-08, from 14:00–14:30 Eastern Time (18:00–18:30 UTC).
Step 3 — Query availability for that day
Section titled “Step 3 — Query availability for that day”Query the full local day (midnight-to-midnight Eastern, which in UTC is 2026-04-08T04:00:00Z through 2026-04-09T04:00:00Z) with include_busy=true so you can see what was blocked:
curl "https://api.chronary.ai/v1/calendars/cal_01H9X4M2P5R8T6V0/availability?start=2026-04-08T04:00:00Z&end=2026-04-09T04:00:00Z&duration=30m&include_busy=true" \ -H "Authorization: Bearer chr_sk_your_key_here"Step 4 — Read the response
Section titled “Step 4 — Read the response”{ "calendar_id": "cal_01H9X4M2P5R8T6V0", "slots": [ { "start": "2026-04-08T13:00:00.000Z", "end": "2026-04-08T17:45:00.000Z" }, { "start": "2026-04-08T18:45:00.000Z", "end": "2026-04-08T21:00:00.000Z" } ], "busy": [ { "start": "2026-04-08T18:00:00.000Z", "end": "2026-04-08T18:30:00.000Z" } ]}A few things to notice:
slotsare contiguous free-gap intervals, not per-durationchunks.durationis a minimum-gap filter — gaps shorter thandurationare dropped from the response — not a chunk size. An agent that wants 30-minute sub-slots walks each gap itself, stepping by its preferred stride.- Free time only shows up between
13:00Z(9:00 AM ET) and21:00Z(5:00 PM ET) — the working-hours window. Before 9 AM and after 5 PM local, nothing is bookable. - The raw event at
18:00Z–18:30Zexpands to a17:45Z–18:45Zbusy block in the engine because of the 15-minute buffers, which is why the first gap ends at17:45Zand the second starts at18:45Z. busyonly reports the raw event — buffers and working-hours blocks are an implementation detail of the engine, not part of thebusyarray.