Skip to content
Keptslot
Start free

Move a booking

POST
/v1/bookings/{id}/reschedule
curl --request POST \
--url https://api.keptslot.com/v1/bookings/2489E9AD-2EE2-8E00-8EC9-32D5F69181C0/reschedule \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "starts_at": "2026-09-02T14:00:00+01:00" }'

This endpoint moves a booking to a new time, and to a different calendar when you give one. The booking keeps its id. The response is the moved booking.

The new time must be an open slot. Read the open slots from GET /v1/availability. Pass the id of this booking as exclude_booking_id. If you do not, the slot that the booking holds now counts as busy.

A booking that is not confirmed cannot move.

id
required
string format: uuid

The booking id.

Media type application/json
object
starts_at
required

An RFC3339 timestamp. The new start time.

string format: date-time
staff_id

Moves the booking to this calendar. If it is absent, the booking keeps its calendar.

string | null format: uuid
Example
{
"starts_at": "2026-09-02T14:00:00+01:00"
}

The moved booking, with the same id.

Media type application/json
object
id
required
string format: uuid
staff_id
required
string format: uuid
service_id
required
string format: uuid
customer_id
required
string format: uuid
starts_at
required
string format: date-time
ends_at
required
string format: date-time
status
required
string
updated_at
required

When the booking last changed. Send it back as updated_since on GET /v1/bookings to read everything that changed after it.

string format: date-time
access_token

The customer’s manage link token. It is present only on the response that creates the booking. Store it then. No later response repeats it.

string
hold_expires_at

When this booking stops holding its slot. It is present only while the booking is on a hold, and absent otherwise.

Show this time rather than counting a fixed number of minutes from now. The window differs between a booking waiting on a code and one waiting on a card, and it is longer for a card because a 3DS redirect goes to the customer’s bank and back.

string format: date-time
payment

Present only when the service takes a payment, and only on the response that creates the booking. The key is absent for a service that takes no payment. It is never null, so a client can test for the key itself.

Use these fields to mount Stripe’s Payment Element on the connected account and confirm the payment. The client secret works once, for this booking.

object
client_secret
required
string
amount_cents
required

What this booking costs, in the smallest unit of the currency. It is a snapshot taken when the booking was made. Changing the service’s price later never changes it.

integer
currency
required
string
connected_account_id
required

The business’s Stripe account. Pass it as stripeAccount when you construct Stripe.js.

string
stripe_publishable_key
required

The Stripe publishable key to construct Stripe.js with. It is not your Keptslot pk_ key.

string
extras
required

The extras booked with the service, in the order the business lists them. Empty when there are none.

Array<object>

One extra on a booking, as it was when the booking was made. A later change to the service does not change it.

object
service_id
required
string format: uuid
name
required
string
duration_minutes
required
integer
price_cents
required
integer | null
price_cents
required

The price of the whole visit when it was booked: the service plus every extra. It is null when none of them had a price.

integer | null
service_price_cents
required

The price of the service alone when it was booked, without its extras. It is null when the service had no price.

integer | null
metadata
required

The record sent with POST /v1/bookings. It is {} when none was sent, never null.

object
key
additional properties
string
Example generated
{
"id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0",
"staff_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0",
"service_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0",
"customer_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0",
"starts_at": "2026-04-15T12:00:00Z",
"ends_at": "2026-04-15T12:00:00Z",
"status": "example",
"updated_at": "2026-04-15T12:00:00Z",
"access_token": "example",
"hold_expires_at": "2026-04-15T12:00:00Z",
"payment": {
"client_secret": "example",
"amount_cents": 1,
"currency": "example",
"connected_account_id": "example",
"stripe_publishable_key": "example"
},
"extras": [
{
"service_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0",
"name": "example",
"duration_minutes": 1,
"price_cents": 1
}
],
"price_cents": 1,
"service_price_cents": 1,
"metadata": {
"additionalProperty": "example"
}
}

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

This is a server route and you sent a publishable key. Send a secret key from your server. The type field is secret_key_required.

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

Refusals here are told apart by error.type.

slot_unavailable means the new time is not available.

bookings_paused means the business has paused online bookings. The booking does not move. Cancelling still works.

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

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

Two writes contended and one was rolled back. The request did nothing. Retry it. The type field is transient_failure.

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