Skip to content
Keptslot
Start free

Download a calendar invite for the booking

GET
/v1/public/bookings/{token}/calendar.ics
curl --request GET \
--url https://api.keptslot.com/v1/public/bookings/example/calendar.ics

Serves the booking as an iCalendar (RFC 5545) file, for a customer’s own calendar app – the “Add to Apple, Outlook and others” link alongside the Google Calendar one on the booking and manage pages. The token in the path is the only credential, the same as GET /v1/public/bookings/{token}.

A pending booking answers the same 404 as an unknown or cancelled token, even though the manage endpoint above shows a pending booking: a calendar invite for a hold that may never be confirmed would advertise an appointment that does not exist yet.

The event’s UID is derived from the booking id and the number of times it has been moved, so each move produces a new event in the customer’s calendar rather than updating the saved one. The description includes the full manage link (base and token), the same link the confirmation email already sends this customer, so opening the file is not a new disclosure.

token
required
string

The booking as an iCalendar file.

Media type text/calendar
string

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
}
}

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
}
}