Availability is the one resource on this API that is computed rather than stored. There is no availability table. Every response is worked out at request time from the staff roster, the recurring breaks, the schedule exceptions, the appointments already booked, and the business's own booking rules.
Two consequences follow, and both of them are the whole content of this page: two identical requests can legitimately return different answers, and you must not cache the result.
| Method | Endpoint | Scope | Plan |
|---|---|---|---|
| GET | /api/v1/availability/slotsBookable start times for one date Query: | availability:read | Groei |
| GET | /api/v1/availability/datesBookable dates in a window Query: | availability:read | Groei |
Two endpoints, two questions
They exist as a pair because a booking interface asks two different questions and answering the second with the first is expensive.
GET /api/v1/availability/dates answers "which days in this window have anything at all". That
is the month calendar: which dates are clickable.
GET /api/v1/availability/slots answers "what times are free on this one day". That is the time
picker you show after somebody clicks a date.
curl "https://stippa.nl/api/v1/availability/dates?serviceId=b50661da-7649-4c14-b748-91bbee8e9643&from=2026-08-17&to=2026-08-23" \
-H "Authorization: Bearer $STIPPA_KEY"
{
"from": "2026-08-17",
"to": "2026-08-23",
"serviceId": "b50661da-7649-4c14-b748-91bbee8e9643",
"staffMemberId": null,
"timezone": "Europe/Amsterdam",
"durationMinutes": 15,
"dates": [
{ "date": "2026-08-17", "slotCount": 28 },
{ "date": "2026-08-18", "slotCount": 28 },
{ "date": "2026-08-19", "slotCount": 28 },
{ "date": "2026-08-20", "slotCount": 28 },
{ "date": "2026-08-21", "slotCount": 28 },
{ "date": "2026-08-22", "slotCount": 32 }
]
}
A date with nothing bookable is absent from the array rather than present with a zero. That window is seven days and the response has six entries: the Sunday is closed, so it is simply not there. Do not index the array by day offset. Build a set of the dates you were given and treat every other day in your window as unavailable.
slotCount is there so a month view can show "3 left" without a request per day. It is a count and
not a promise: by the time somebody picks that date, it may be a different number.
The two endpoints agree, and that is load bearing
slots for a date returns exactly as many entries as that date's slotCount in dates. Asking for
2026-08-20 above:
curl "https://stippa.nl/api/v1/availability/slots?serviceId=b50661da-7649-4c14-b748-91bbee8e9643&date=2026-08-20" \
-H "Authorization: Bearer $STIPPA_KEY"
{
"date": "2026-08-20",
"serviceId": "b50661da-7649-4c14-b748-91bbee8e9643",
"staffMemberId": null,
"timezone": "Europe/Amsterdam",
"durationMinutes": 15,
"slots": [
{ "startTime": "2026-08-20T07:00:00.000Z", "endTime": "2026-08-20T07:15:00.000Z" },
{ "startTime": "2026-08-20T07:15:00.000Z", "endTime": "2026-08-20T07:30:00.000Z" },
{ "startTime": "2026-08-20T09:45:00.000Z", "endTime": "2026-08-20T10:00:00.000Z" },
{ "startTime": "2026-08-20T11:00:00.000Z", "endTime": "2026-08-20T11:15:00.000Z" }
]
}
Twenty-eight slots, matching slotCount: 28. The gap between 10:00 and 11:00 UTC is the business's
lunch break, which is on the roster rather than being a booked appointment.
The agreement is worth stating because it has not always held. A month calendar and a time picker are two computations over the same inputs, and a business having a day off used to be subtracted in one of them and not the other, so a date was clickable and then had no times on it. They are one path now. If you ever see them disagree, that is a bug worth telling us about rather than a subtlety to work around.
Everything is UTC, and timezone is for display
startTime and endTime are UTC instants with a Z suffix, like every timestamp on this API.
date, from and to are plain calendar dates with no zone, and they mean dates in the
business's timezone, which the response tells you.
That combination is not sloppiness, it is the only pair that works. "Tuesday the 20th" is a local
calendar concept and a salon's Tuesday does not start at midnight UTC. "09:00 with a 15-minute
service" is an instant. Render the slots in timezone and your times will match what the business
sees on its own screen; render them in the browser's zone and a customer booking from Spain will see
times an hour off what the salon expects.
Do not cache this
Availability is the one resource on this API with no cacheable answer. A slot that was free when you read it can be taken before your customer clicks it, and there is no expiry you can pick that makes a stale answer safe.
So do not treat a slot list as a reservation. There is nothing to hold and no endpoint that holds it:
the way you find out whether a slot is really yours is by creating the appointment and handling
409 CONFLICT, which is a normal outcome of a correct booking flow rather than an error case. Fetch
slots again and let the customer pick another. The booking recipe
does exactly this.
Narrowing by staff member
Both endpoints take an optional staffMemberId. Without it, a slot is free if anybody bookable
can do that service then, which is what a customer with no preference wants.
With it, you get one person's availability. Do that when the customer has chosen somebody, and then
pass the same staffMemberId to the create along with staffLocked: true, so a later reschedule does
not silently reassign them.
Asking for slots without a staffMemberId and then creating with one is the mismatch worth avoiding:
the wider question was answered for the whole team, and the narrower create can still be refused.