Skip to content
Keptslot
Start free

List open slots

GET
/v1/availability
curl --request GET \
--url 'https://api.keptslot.com/v1/availability?service_id=2489E9AD-2EE2-8E00-8EC9-32D5F69181C0&from=2026-04-15T12%3A00%3A00Z&to=2026-04-15T12%3A00%3A00Z' \
--header 'Authorization: Bearer <token>'

This endpoint gives the times a customer can book for one service. The response groups the slots by calendar.

Give a range of 62 days or less. If the range is longer, the endpoint refuses the request with a 422.

All times in the response use the workspace timezone.

Rate limited when called with a publishable key: 300 requests a minute shared by the workspace’s whole booking page (every customer on it at once), and 60 a minute for one caller address within that. A secret key is not subject to either – see the workspace-wide ceiling on SecretKey instead.

service_id
required
string format: uuid

The service to book. It must be active and bookable on its own (standalone), or the endpoint refuses the request with a 422 on service_id. With exclude_booking_id, it must be the service of that booking, which may have been deactivated since.

extra_ids
string

Extras to add to the service, as comma-separated ids. Each slot is as long as the service plus every extra, and only calendars that perform all of them are listed.

Each id must be an extra offered with service_id, at most 10 and each once, or the endpoint refuses the request with a 422 on extra_ids. An empty value is refused too. Leave it out with exclude_booking_id.

from
required
string format: date-time

An RFC3339 timestamp. The start of the range.

to
required
string format: date-time

An RFC3339 timestamp. It must be after from, and no more than 62 days after it.

exclude_booking_id
string format: uuid

Removes one booking from the busy times. If you move a booking, use this parameter. Then the slot that the booking holds now stays available to it.

The slots are as long as that booking, extras included, and only calendars that perform its service and every extra on it are listed. Do not send extra_ids with it: the endpoint refuses that with a 422. If the workspace has no booking with this id, the endpoint answers 404 with param set to exclude_booking_id.

If you send this parameter with an empty value, the endpoint refuses the request with a 422. An empty value is not the same as an absent one.

The open slots in the range, grouped by calendar.

Media type application/json
object
data
required
Array<object>
object
staff_id
required

The calendar these slots belong to.

string format: uuid
slots
required
Array<object>
object
start
required

The start of the slot, in the workspace timezone. The appointment happens at the business, so the offset is the business’s and not the customer’s.

string format: date-time
end
required
string format: date-time
Example
{
"data": [
{
"staff_id": "6f1c0b52-9a3e-4f77-8d21-0b5a2c9e4d10",
"slots": [
{
"start": "2026-09-01T09:00:00+01:00",
"end": "2026-09-01T09:30:00+01:00"
},
{
"start": "2026-09-01T09:30:00+01:00",
"end": "2026-09-01T10:00:00+01:00"
}
]
}
]
}

The key is missing, malformed or revoked.

Media type application/json
object
error
required
object
type
required

A stable machine-readable code. Match on this.

string
message
required

A sentence for a person. Do not match on this.

string
param

The field that caused the refusal, if one field did.

string
dimension

The plan limit that refused the request. It is present only on a 402, and only for a secret key or an account key. See the errors guide.

string
used

The current count for that dimension. It is present only on a 402, and only for an account key. It is absent when the refusal has no count to report. Absent and zero are different answers.

integer format: int64
limit

The cap for that dimension. It is present only on a 402, and only for an account key. A cap of zero is a real value.

integer format: int64
Example generated
{
"error": {
"type": "example",
"message": "example",
"param": "example",
"dimension": "example",
"used": 1,
"limit": 1
}
}

The request used a publishable key and sent an Origin header that is not on the allowed list of that key. Add the origin to the key, or call the endpoint with a secret key from a server. The type field is origin_not_allowed.

Media type application/json
object
error
required
object
type
required

A stable machine-readable code. Match on this.

string
message
required

A sentence for a person. Do not match on this.

string
param

The field that caused the refusal, if one field did.

string
dimension

The plan limit that refused the request. It is present only on a 402, and only for a secret key or an account key. See the errors guide.

string
used

The current count for that dimension. It is present only on a 402, and only for an account key. It is absent when the refusal has no count to report. Absent and zero are different answers.

integer format: int64
limit

The cap for that dimension. It is present only on a 402, and only for an account key. A cap of zero is a real value.

integer format: int64
Example generated
{
"error": {
"type": "example",
"message": "example",
"param": "example",
"dimension": "example",
"used": 1,
"limit": 1
}
}

There is no such resource in this workspace.

Media type application/json
object
error
required
object
type
required

A stable machine-readable code. Match on this.

string
message
required

A sentence for a person. Do not match on this.

string
param

The field that caused the refusal, if one field did.

string
dimension

The plan limit that refused the request. It is present only on a 402, and only for a secret key or an account key. See the errors guide.

string
used

The current count for that dimension. It is present only on a 402, and only for an account key. It is absent when the refusal has no count to report. Absent and zero are different answers.

integer format: int64
limit

The cap for that dimension. It is present only on a 402, and only for an account key. A cap of zero is a real value.

integer format: int64
Example generated
{
"error": {
"type": "example",
"message": "example",
"param": "example",
"dimension": "example",
"used": 1,
"limit": 1
}
}

bookings_paused: the business has paused online bookings, so the endpoint lists no slots. A workspace with no free times answers 200 with empty slots instead.

Media type application/json
object
error
required
object
type
required

A stable machine-readable code. Match on this.

string
message
required

A sentence for a person. Do not match on this.

string
param

The field that caused the refusal, if one field did.

string
dimension

The plan limit that refused the request. It is present only on a 402, and only for a secret key or an account key. See the errors guide.

string
used

The current count for that dimension. It is present only on a 402, and only for an account key. It is absent when the refusal has no count to report. Absent and zero are different answers.

integer format: int64
limit

The cap for that dimension. It is present only on a 402, and only for an account key. A cap of zero is a real value.

integer format: int64
Example generated
{
"error": {
"type": "example",
"message": "example",
"param": "example",
"dimension": "example",
"used": 1,
"limit": 1
}
}

The request body or the query is not valid.

Media type application/json
object
error
required
object
type
required

A stable machine-readable code. Match on this.

string
message
required

A sentence for a person. Do not match on this.

string
param

The field that caused the refusal, if one field did.

string
dimension

The plan limit that refused the request. It is present only on a 402, and only for a secret key or an account key. See the errors guide.

string
used

The current count for that dimension. It is present only on a 402, and only for an account key. It is absent when the refusal has no count to report. Absent and zero are different answers.

integer format: int64
limit

The cap for that dimension. It is present only on a 402, and only for an account key. A cap of zero is a real value.

integer format: int64
Example generated
{
"error": {
"type": "example",
"message": "example",
"param": "example",
"dimension": "example",
"used": 1,
"limit": 1
}
}

This endpoint’s request budget is spent. The type field is rate_limited.

The budget is a token bucket, not a calendar-minute counter: it refills CONTINUOUSLY as time passes, a few tokens’ worth every second, rather than resetting all at once at the top of the minute. There is no wall-clock boundary to wait for. A REFUSED request costs nothing – the check happens before a token is spent, so hammering this endpoint while refused does not delay when it starts working again. What you get back is Retry-After: seconds until the bucket has refilled enough to admit one more request, computed from exactly how empty the bucket is right now. Retrying before then just gets refused again; retrying at or after Retry-After succeeds. See the endpoint’s own description for the actual numbers – they differ by endpoint and, for the publishable-key routes, by whether the budget is shared by the whole workspace or is one caller’s own share of it.

The response also carries RateLimit-Remaining (requests left in the bucket – always 0 on this response, since a refusal only happens once it reaches zero). Both headers are ADDITIVE: type alone is enough to act correctly, and a client that cannot read response headers loses nothing. Access-Control-Expose-Headers names both, so a browser-based integration on a publishable key can read them too, the same as it reads type from the body.

Media type application/json
object
error
required
object
type
required

A stable machine-readable code. Match on this.

string
message
required

A sentence for a person. Do not match on this.

string
param

The field that caused the refusal, if one field did.

string
dimension

The plan limit that refused the request. It is present only on a 402, and only for a secret key or an account key. See the errors guide.

string
used

The current count for that dimension. It is present only on a 402, and only for an account key. It is absent when the refusal has no count to report. Absent and zero are different answers.

integer format: int64
limit

The cap for that dimension. It is present only on a 402, and only for an account key. A cap of zero is a real value.

integer format: int64
Example generated
{
"error": {
"type": "example",
"message": "example",
"param": "example",
"dimension": "example",
"used": 1,
"limit": 1
}
}
Retry-After
integer

Seconds until this bucket admits another request.

RateLimit-Remaining
integer

Requests left in the bucket. Always 0 on this response.

A fault on our side. Do not retry the same request in a loop. The type field is internal_error.

Media type application/json
object
error
required
object
type
required

A stable machine-readable code. Match on this.

string
message
required

A sentence for a person. Do not match on this.

string
param

The field that caused the refusal, if one field did.

string
dimension

The plan limit that refused the request. It is present only on a 402, and only for a secret key or an account key. See the errors guide.

string
used

The current count for that dimension. It is present only on a 402, and only for an account key. It is absent when the refusal has no count to report. Absent and zero are different answers.

integer format: int64
limit

The cap for that dimension. It is present only on a 402, and only for an account key. A cap of zero is a real value.

integer format: int64
Example generated
{
"error": {
"type": "example",
"message": "example",
"param": "example",
"dimension": "example",
"used": 1,
"limit": 1
}
}