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:
-
Slot generation — The service's
slot_rulesdefine when slots begin (day of week, start/end time, recurrence pattern). The servicedurationdetermines the length of each slot. Together, these produce a set of candidate time slots for the requested date range. -
Provider working hours — Each provider's
ProviderScheduledefinesweekly_ruleswith working hours per day of the week. Candidate slots that fall outside a provider's working hours for the effective date range are removed. -
Existing appointments — Any scheduled
Appointmentthat overlaps a candidate slot removes that slot from availability. -
Blocks — Any
Blockthat overlaps a candidate slot removes it, regardless of the block'sattachment_type(provider, service, service_provider, or location). -
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.
-
Booking intent slot holds — Active holds from
BookingIntentstemporarily remove held slots until the hold expires or the intent is completed or abandoned.
Querying available slots
Three endpoints return slot availability data:
| Endpoint | Path | Auth | Use case |
|---|---|---|---|
| List available time slots | GET /v1/services/{service_id}/slots | API key | Server-side UI with per-provider breakdowns |
| List available time slots (public) | GET /v1/public/services/{service_id}/slots | None | Client-facing booking UI |
| Get earliest available slot | GET /v1/public/services/{service_id}/earliest_slot | None | "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:
| Parameter | Type | Required | Description |
|---|---|---|---|
service_id | string | Yes (path) | The service to query slots for. |
from | ISO date | No | Start of the date range. Defaults to today. |
until | ISO date | Conditional | End of the date range. Either until or limit is required. |
limit | integer | Conditional | Maximum number of aggregated slots to return. Either limit or until is required. |
time_zone | string | No | IANA time zone (e.g., America/New_York). Defaults to Etc/UTC. |
provider_ids | string | No | Comma-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:
| Property | Type | Description |
|---|---|---|
object | string | Always "aggregated_slot". |
start_at | string | Offset-aware ISO datetime in the requested time zone. |
start_at_ts | integer | Unix timestamp in seconds. |
end_at | string | Offset-aware ISO datetime in the requested time zone. |
end_at_ts | integer | Unix timestamp in seconds. |
time_zone | string | IANA time zone name. |
count | integer | Number 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):
| Property | Type | Description |
|---|---|---|
object | string | Always "slot". |
start_at | string | Offset-aware ISO datetime in the requested time zone. |
start_at_ts | integer | Unix timestamp in seconds. |
end_at | string | Offset-aware ISO datetime in the requested time zone. |
end_at_ts | integer | Unix timestamp in seconds. |
time_zone | string | IANA time zone name. |
Time zone handling
The time_zone query parameter controls two things:
- Offset in returned datetime strings — The
start_atandend_atvalues include the UTC offset for the requested time zone (e.g.,-04:00forAmerica/New_Yorkduring EDT). - Date boundary interpretation — The
fromanduntildate 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:
| Resource | Effect | Learn more |
|---|---|---|
SlotRule | Defines candidate start times and recurrence | Data Model — Services |
Service duration | Determines slot length | Service schema |
ProviderSchedule weekly_rules | Filters to working hours | Data Model — Provider Schedules |
ProviderSchedule public_bookings | Controls public endpoint visibility | ProviderSchedule schema |
Appointment | Removes booked slots | Data Model — Appointments |
Block | Removes blocked time ranges | Data Model — Blocks |
ConnectedAccount (calendar) | Removes externally busy times | Connected Accounts |
BookingIntent hold | Temporarily removes held slots | Booking Intents — Slot holding |