Floyd provides three ways to query availability depending on your use case:
| Endpoint | Scope | Returns | Best for |
|---|---|---|---|
| Resource availability | Raw resources | Free/busy timeline | Low-level checks |
| Slots | Service + policy | Discrete bookable positions | Appointment booking (salon, doctor) |
| Windows | Service + policy | Continuous available ranges | Rental booking (kayak, meeting room) |
Returns discrete, grid-aligned time positions where a booking of the given duration could be placed. Policy rules (working hours, grid, buffers, lead time, horizon) are applied automatically.
curl -X POST "$FLOYD_BASE_URL/v1/ledgers/$LEDGER_ID/services/$SERVICE_ID/availability/slots" \
-H "Content-Type: application/json" \
-d '{
"startTime": "2026-03-16T00:00:00Z",
"endTime": "2026-03-16T23:59:59Z",
"durationMs": 3600000
}'Response:
{
"data": [
{
"resourceId": "rsc_01abc123def456ghi789jkl012",
"timezone": "America/New_York",
"slots": [
{ "startTime": "2026-03-16T13:00:00.000Z", "endTime": "2026-03-16T14:00:00.000Z" },
{ "startTime": "2026-03-16T13:30:00.000Z", "endTime": "2026-03-16T14:30:00.000Z" },
{ "startTime": "2026-03-16T14:00:00.000Z", "endTime": "2026-03-16T15:00:00.000Z" }
]
}
],
"meta": {
"serverTime": "2026-03-16T10:00:00.000Z"
}
}| Field | Type | Required | Description |
|---|---|---|---|
startTime |
string | Yes | Start of query window (ISO 8601) |
endTime |
string | Yes | End of query window (ISO 8601). Max 7 days from startTime. |
durationMs |
number | Yes | Desired booking duration in milliseconds |
resourceIds |
string[] | No | Filter to specific resources. Defaults to all resources in the service. |
includeUnavailable |
boolean | No | When true, returns all grid positions with status field. Default false. |
- Day resolution: Each day in the query range is resolved against the service's policy. Closed days and blackout dates are skipped.
- Grid alignment: Within each day's open windows, candidate positions are placed at grid intervals (from
constraints.grid.interval_ms). If no grid is configured, the step defaults todurationMs. - Duration validation: If the day's constraints restrict durations (
allowed_ms,min_ms,max_ms) anddurationMsdoesn't pass, the entire day is skipped. - Conflict check: Each candidate is expanded by buffer times (
before_ms/after_ms) and checked against existing allocations. Overlapping candidates are excluded. - Lead time / horizon: Candidates too close to
serverTime(belowmin_ms) or too far out (beyondmax_ms) are excluded.
When the grid interval is smaller than the duration, slots overlap. For example, 60-minute slots on a 30-minute grid produce:
09:00–10:00, 09:30–10:30, 10:00–11:00, ...
This lets users pick the best start time rather than being locked to non-overlapping blocks.
By default, only available slots are returned (no status field). With includeUnavailable: true, every grid position is returned with a status of "available" or "unavailable":
{
"slots": [
{ "startTime": "...", "endTime": "...", "status": "available" },
{ "startTime": "...", "endTime": "...", "status": "unavailable" },
{ "startTime": "...", "endTime": "...", "status": "available" }
]
}This is useful for rendering a full calendar grid with booked slots grayed out.
Returns continuous available time ranges after subtracting existing allocations and applying buffer shrinkage. No grid alignment — windows represent the full bookable gaps.
curl -X POST "$FLOYD_BASE_URL/v1/ledgers/$LEDGER_ID/services/$SERVICE_ID/availability/windows" \
-H "Content-Type: application/json" \
-d '{
"startTime": "2026-03-16T00:00:00Z",
"endTime": "2026-03-20T00:00:00Z"
}'Response:
{
"data": [
{
"resourceId": "rsc_01abc123def456ghi789jkl012",
"timezone": "America/New_York",
"windows": [
{ "startTime": "2026-03-16T13:00:00.000Z", "endTime": "2026-03-16T22:00:00.000Z" },
{ "startTime": "2026-03-17T13:00:00.000Z", "endTime": "2026-03-17T16:00:00.000Z" },
{ "startTime": "2026-03-17T18:00:00.000Z", "endTime": "2026-03-17T22:00:00.000Z" }
]
}
],
"meta": {
"serverTime": "2026-03-16T10:00:00.000Z"
}
}| Field | Type | Required | Description |
|---|---|---|---|
startTime |
string | Yes | Start of query window (ISO 8601) |
endTime |
string | Yes | End of query window (ISO 8601). Max 31 days from startTime. |
resourceIds |
string[] | No | Filter to specific resources. Defaults to all resources in the service. |
includeUnavailable |
boolean | No | When true, also returns allocation times as "unavailable" windows. Default false. |
- Day resolution: Same as slots — each day resolved against the policy.
- Schedule assembly: Open windows across all days are converted to absolute time intervals and merged if contiguous (e.g., two 24h days merge into one 48h window).
- Allocation subtraction: Existing allocations are carved out of the schedule windows.
- Buffer shrinkage: Gaps adjacent to allocations are shrunk to account for new-booking buffer requirements:
- Gap start touches an allocation → shrink start by
before_ms - Gap end touches an allocation → shrink end by
after_ms - Gap start/end at a schedule boundary → no shrinkage (buffers extend outside schedule)
- Gap start touches an allocation → shrink start by
- Minimum duration filter: Windows shorter than
constraints.duration.min_msare discarded. - Lead time / horizon: Windows outside the lead time range are filtered.
- Merge: Adjacent windows are merged into single ranges.
Given a schedule of 09:00–17:00 with before_ms: 15min and after_ms: 10min, and an existing allocation at 12:00–13:00:
Schedule: |========================================|
09:00 17:00
Alloc: |======|
12:00 13:00
Raw gaps: |=============| |===================|
09:00 12:00 13:00 17:00
After shrinkage:
|=========| |=================|
09:00 11:50 13:15 17:00
↑ -10min ↑ +15min
(after_ms) (before_ms)
The gap before the allocation shrinks at its end by after_ms (the new booking would need a post-buffer). The gap after the allocation shrinks at its start by before_ms (the new booking would need a pre-buffer). Schedule boundaries don't shrink — buffers are allowed to extend outside the schedule.
With includeUnavailable: true, allocation times within schedule hours are also returned with status: "unavailable":
{
"windows": [
{ "startTime": "...", "endTime": "...", "status": "available" },
{ "startTime": "...", "endTime": "...", "status": "unavailable" },
{ "startTime": "...", "endTime": "...", "status": "available" }
]
}Both slots and windows accept an optional resourceIds array to query specific resources within the service:
curl -X POST "$FLOYD_BASE_URL/v1/ledgers/$LEDGER_ID/services/$SERVICE_ID/availability/slots" \
-H "Content-Type: application/json" \
-d '{
"startTime": "2026-03-16T00:00:00Z",
"endTime": "2026-03-16T23:59:59Z",
"durationMs": 3600000,
"resourceIds": ["rsc_01abc123def456ghi789jkl012"]
}'If omitted, all resources belonging to the service are included. Each resource gets independent results in the data array. Returns 422 if a resource ID doesn't belong to the service.
If the service has no policy attached:
- All times are considered open (24h schedule)
- No duration, grid, buffer, or lead time constraints
- Only existing allocations block time
- Slots step defaults to
durationMs
Every resource has a required timezone field (IANA format, e.g., "America/New_York"). Schedule windows (e.g., "09:00–17:00") are interpreted in the resource's timezone, including DST transitions. The resolved timezone is included in each resource's response entry.
For low-level free/busy checks without policy evaluation, query resource availability directly:
curl -X POST "$FLOYD_BASE_URL/v1/ledgers/$LEDGER_ID/availability" \
-H "Content-Type: application/json" \
-d '{
"resourceIds": ["rsc_01abc123def456ghi789jkl012"],
"startTime": "2026-01-04T10:00:00Z",
"endTime": "2026-01-04T18:00:00Z"
}'Response:
{
"data": [
{
"resourceId": "rsc_01abc123def456ghi789jkl012",
"timeline": [
{
"startTime": "2026-01-04T10:00:00.000Z",
"endTime": "2026-01-04T11:00:00.000Z",
"status": "free"
},
{
"startTime": "2026-01-04T11:00:00.000Z",
"endTime": "2026-01-04T12:00:00.000Z",
"status": "busy"
},
{
"startTime": "2026-01-04T12:00:00.000Z",
"endTime": "2026-01-04T18:00:00.000Z",
"status": "free"
}
]
}
]
}This endpoint doesn't know about services or policies — it only reports raw allocation conflicts.
A time slot is marked busy if it overlaps with an active, non-expired allocation:
active = trueandexpiresAtis null (permanent block)active = trueandexpiresAt > now()(temporary block, not yet expired)
These do not block time:
active = false(canceled/expired booking allocations)- Expired allocations (
expiresAt <= now())
Both booking-owned and raw allocations are considered.
- Clamping: Allocations extending outside the query window are clamped to fit
- Merging: Overlapping and adjacent busy blocks are merged into single blocks
- Gaps filled: Free blocks are automatically generated for unoccupied time
const { data, meta } = await fetch(
`${baseUrl}/v1/ledgers/${ledgerId}/services/${serviceId}/availability/slots`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ startTime, endTime, durationMs: 3600000 }),
},
).then((r) => r.json());
// Pick the first available slot for any resource
for (const resource of data) {
if (resource.slots.length > 0) {
const slot = resource.slots[0];
console.log(`Book ${resource.resourceId} at ${slot.startTime}`);
break;
}
}const { data } = await fetch(
`${baseUrl}/v1/ledgers/${ledgerId}/services/${serviceId}/availability/windows`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ startTime, endTime }),
},
).then((r) => r.json());
// Find a window that fits the desired rental duration
const minDuration = 4 * 60 * 60 * 1000; // 4 hours
for (const resource of data) {
const suitable = resource.windows.find((w) => {
const duration = new Date(w.endTime) - new Date(w.startTime);
return duration >= minDuration;
});
if (suitable) {
console.log(`Rent ${resource.resourceId}: ${suitable.startTime} to ${suitable.endTime}`);
break;
}
}