Skip to main content

Slot Availability

This guide explains how SavvyCal calculates available time slots for a service and how to query them through the API.

How availability is calculated

When you query for available time slots, SavvyCal runs a pipeline that starts with candidate slots and progressively removes unavailable times:

Slot Rules (Service) ──▶ Generate candidate slots ──▶ Remove unavailable slots ──▶ Available slots

Each stage of the pipeline works as follows:

  1. Slot generation — The service's slot_rules define when slots begin (day of week, start/end time, recurrence pattern). The service duration determines the length of each slot. Together, these produce a set of candidate time slots for the requested date range.

  2. Provider working hours — Each provider's ProviderSchedule defines weekly_rules with working hours per day of the week. Candidate slots that fall outside a provider's working hours for the effective date range are removed.

  3. Existing appointments — Any scheduled Appointment that overlaps a candidate slot removes that slot from availability.

  4. Blocks — Any Block that overlaps a candidate slot removes it, regardless of the block's attachment_type (provider, service, service_provider, or location).

  5. Connected calendar conflicts — When a provider has a connected calendar account (Google Calendar, Microsoft 365), busy times from the external calendar are removed from availability.

  6. Booking intent slot holds — Active holds from BookingIntents temporarily remove held slots until the hold expires or the intent is completed or abandoned.

Querying available slots

Three endpoints return slot availability data:

EndpointPathAuthUse case
List available time slotsGET /v1/services/{service_id}/slotsAPI keyServer-side UI with per-provider breakdowns
List available time slots (public)GET /v1/public/services/{service_id}/slotsNoneClient-facing booking UI
Get earliest available slotGET /v1/public/services/{service_id}/earliest_slotNone"Next available" display

The public endpoints only return slots for providers whose ProviderSchedule has public_bookings.enabled set to true.

Request parameters

The authenticated and public list endpoints share these query parameters:

ParameterTypeRequiredDescription
service_idstringYes (path)The service to query slots for.
fromISO dateNoStart of the date range. Defaults to today.
untilISO dateConditionalEnd of the date range. Either until or limit is required.
limitintegerConditionalMaximum number of aggregated slots to return. Either limit or until is required.
time_zonestringNoIANA time zone (e.g., America/New_York). Defaults to Etc/UTC.
provider_idsstringNoComma-separated provider IDs to filter by.

The earliest-slot endpoint accepts only service_id (path), time_zone, and provider_ids.

Authenticated request

curl -G "https://api.savvycal.app/v1/services/srv_d025a96ac0c6/slots" \
-H "Authorization: Bearer sk_live_..." \
-d from=2025-03-10 \
-d until=2025-03-14 \
-d time_zone=America/New_York

Public request

curl -G "https://api.savvycal.app/v1/public/services/srv_d025a96ac0c6/slots" \
-H "X-SavvyCal-Account: acct_d025a96ac0c6" \
-d from=2025-03-10 \
-d limit=10 \
-d time_zone=America/New_York

Earliest slot request

curl -G "https://api.savvycal.app/v1/public/services/srv_d025a96ac0c6/earliest_slot" \
-H "X-SavvyCal-Account: acct_d025a96ac0c6" \
-d time_zone=America/New_York

Response structure

Authenticated response

The authenticated endpoint returns a ServiceSlotResponse with two arrays inside data: aggregated_slots (deduplicated by time, with a count of available providers) and provider_slots (per-provider breakdowns).

{
"data": {
"aggregated_slots": [
{
"object": "aggregated_slot",
"start_at": "2025-03-10T09:00:00-04:00",
"start_at_ts": 1741615200,
"end_at": "2025-03-10T10:00:00-04:00",
"end_at_ts": 1741618800,
"time_zone": "America/New_York",
"count": 2
}
],
"provider_slots": [
{
"service_provider": {
"object": "service_provider",
"id": "sp_d025a96ac0c6",
"service_id": "srv_d025a96ac0c6",
"provider": {
"object": "provider",
"id": "prov_d025a96ac0c6",
"display_name": "Dr. Evelyn Brooks"
}
},
"open": [
{
"object": "slot",
"start_at": "2025-03-10T09:00:00-04:00",
"start_at_ts": 1741615200,
"end_at": "2025-03-10T10:00:00-04:00",
"end_at_ts": 1741618800,
"time_zone": "America/New_York"
}
]
}
]
}
}

AggregatedSlot properties:

PropertyTypeDescription
objectstringAlways "aggregated_slot".
start_atstringOffset-aware ISO datetime in the requested time zone.
start_at_tsintegerUnix timestamp in seconds.
end_atstringOffset-aware ISO datetime in the requested time zone.
end_at_tsintegerUnix timestamp in seconds.
time_zonestringIANA time zone name.
countintegerNumber of providers available for this slot.

Public response

The public list endpoint returns a PublicServiceSlotsResponse with a flat data array of Slot objects.

{
"data": [
{
"object": "slot",
"start_at": "2025-03-10T09:00:00-04:00",
"start_at_ts": 1741615200,
"end_at": "2025-03-10T10:00:00-04:00",
"end_at_ts": 1741618800,
"time_zone": "America/New_York"
}
]
}

Earliest slot response

The earliest-slot endpoint returns a PublicServiceEarliestSlotResponse where data is a single Slot object, or null if no slots are available.

{
"data": {
"object": "slot",
"start_at": "2025-03-10T09:00:00-04:00",
"start_at_ts": 1741615200,
"end_at": "2025-03-10T10:00:00-04:00",
"end_at_ts": 1741618800,
"time_zone": "America/New_York"
}
}

Slot properties (used by both public responses):

PropertyTypeDescription
objectstringAlways "slot".
start_atstringOffset-aware ISO datetime in the requested time zone.
start_at_tsintegerUnix timestamp in seconds.
end_atstringOffset-aware ISO datetime in the requested time zone.
end_at_tsintegerUnix timestamp in seconds.
time_zonestringIANA time zone name.

Time zone handling

The time_zone query parameter controls two things:

  1. Offset in returned datetime strings — The start_at and end_at values include the UTC offset for the requested time zone (e.g., -04:00 for America/New_York during EDT).
  2. Date boundary interpretation — The from and until date parameters are interpreted as dates in the requested time zone.

The default time zone is Etc/UTC. Unix timestamps (start_at_ts, end_at_ts) are absolute and unaffected by the time zone parameter.

See Working with Dates — Slots for more on slot date formats.

What affects availability

This table summarizes the resources that influence slot availability:

ResourceEffectLearn more
SlotRuleDefines candidate start times and recurrenceData Model — Services
Service durationDetermines slot lengthService schema
ProviderSchedule weekly_rulesFilters to working hoursData Model — Provider Schedules
ProviderSchedule public_bookingsControls public endpoint visibilityProviderSchedule schema
AppointmentRemoves booked slotsData Model — Appointments
BlockRemoves blocked time rangesData Model — Blocks
ConnectedAccount (calendar)Removes externally busy timesConnected Accounts
BookingIntent holdTemporarily removes held slotsBooking Intents — Slot holding