SapanCalDeveloper Docs

Free/busy and availability

Read busy time and find slots that suit one person or a hundred.

BentoCal has two scheduling calls:

Both read calendars live from Google and Microsoft, so results are always current.

Which calendars are read

By default, BentoCal reads the primary calendar of every active connection the user has. A user with a work Microsoft account and a personal Google account is busy whenever either calendar is.

To read other calendars, pass calendar_ids (up to 50 per user). Each entry can be:

  • a calendar id from any of the user's connections (list them with GET /connections/{connection_id}/calendars),
  • primary, meaning the default connection's primary calendar, or
  • an account's email address, meaning that account's primary calendar.

Free/busy

Bash
curl -X POST https://api.bentocal.com/au/v1/free_busy \
  -H "Authorization: Bearer $BENTOCAL_TEST_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "user_ids": ["user_123", "user_456"],
    "start": "2026-10-20T00:00:00Z",
    "end": "2026-10-27T00:00:00Z",
    "calendar_ids": { "user_456": ["primary", "team-rota@example.com"] }
  }'
JSON
{
  "users": [
    { "user_id": "user_123", "busy": [{ "start": "2026-10-20T01:00:00Z", "end": "2026-10-20T02:30:00Z" }] },
    { "user_id": "user_456", "busy": [] }
  ],
  "errors": []
}
  • Up to 100 users and a range of up to 62 days.
  • Busy means the provider's busy, tentative and out-of-office statuses. Free time and "working elsewhere" don't count.
  • Intervals are merged across calendars, sorted, and returned in UTC.

Availability

Availability takes the people who need to attend and the windows to search, and returns times that work:

Bash
curl -X POST https://api.bentocal.com/au/v1/availability \
  -H "Authorization: Bearer $BENTOCAL_TEST_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "participants": [{ "user_id": "user_123" }, { "user_id": "user_456" }],
    "required": "all",
    "periods": [
      { "start": "2026-10-20T09:00:00+11:00", "end": "2026-10-20T17:00:00+11:00" },
      { "start": "2026-10-21T09:00:00+11:00", "end": "2026-10-21T17:00:00+11:00" }
    ],
    "slot_duration_minutes": 45,
    "slot_interval_minutes": 15,
    "buffer_after_minutes": 10
  }'
JSON
{
  "slots": [
    { "start": "2026-10-19T22:00:00Z", "end": "2026-10-19T22:45:00Z", "user_ids": ["user_123", "user_456"] },
    { "start": "2026-10-19T22:15:00Z", "end": "2026-10-19T23:00:00Z", "user_ids": ["user_123", "user_456"] }
  ],
  "errors": []
}

Options

FieldDefaultWhat it does
participants1 to 100 users, each with optional calendar_ids.
requiredallall: every participant must be free. any: at least one must be; each result lists who is in user_ids.
periods1 to 200 windows to search, spanning at most 62 days in total. Use them for working hours, for example one period per weekday.
slot_duration_minutesLength of each slot: 5 to 1440, a multiple of 5.
slot_interval_minutesslot_duration_minutesGap between slot start times: 5 to 1440, a multiple of 5. Slots start on this grid counted from 00:00 UTC.
buffer_before_minutes0Free time needed before each slot, up to 240.
buffer_after_minutes0Free time needed after each slot, up to 240.
formatslotsslots or periods. See below.

A slot is returned only when it fits inside a period and every required participant is free for the whole slot plus its buffers.

Slots or periods

  • format: "slots" returns bookable slots of slot_duration_minutes on the slot_interval_minutes grid. Use it to show a list of times to pick from.
  • format: "periods" returns the longest free stretches (no grid) that are at least slot_duration_minutes long. Use it to draw free time on a calendar. With required: "any", a period ends wherever the set of free participants changes.
Because the grid counts from 00:00 UTC, intervals of 15, 30 or 60 minutes give slots on the quarter or half hour in any time zone with a whole or half-hour offset.

When a calendar can't be read

Both calls return 200 even when some users fail. Those users are left out of the results and listed in errors:

JSON
{
  "errors": [
    {
      "user_id": "user_456",
      "connection_id": "conn_7rD1sW8eYq2KjH6b",
      "code": "relink_required",
      "message": "The calendar connection must be re-linked by the user."
    }
  ]
}
codeWhat to do
relink_requiredAsk the user to reconnect. See Relink a connection.
provider_unavailableGoogle or Microsoft failed. Retry shortly.
not_foundNo such user, or a calendar in calendar_ids doesn't exist.
no_connectionsThe user has no active connections.
With required: "all", a participant you can't read means you can't know when everyone is free, so any error gives no results. Check errors before telling a user there are no times available.