Skip to content
Keptslot
Start free

Create a service

POST
/v1/services
curl --request POST \
--url https://api.keptslot.com/v1/services \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "name": "Cut and finish", "duration_minutes": 45, "buffer_after_minutes": 10, "min_lead_minutes": 120, "max_advance_days": 60, "min_cancel_notice_minutes": 1440, "price_cents": 4000 }'

This endpoint adds a service. A new service is active.

If you send max_advance_days as 0, the API stores 60.

If you send category_id, the service goes in that category. A category that is not in this workspace is a 404, and no service is created.

Media type application/json

The writable fields of a service. Create and replace use the same shape. active is not here: use the activate and deactivate endpoints.

A service can be booked on its own, added to a visit as an extra, or both; standalone and extra_scope say which. A write that leaves both out makes a service that is booked on its own and is never an extra. PUT /v1/services/{id} replaces the whole service, so a replace that leaves them out turns an extra back into a plain service.

A barber’s beard trim is booked on its own and can be added to any service: standalone: true, extra_scope: all. A salon’s gloss treatment is only ever added to a colour: standalone: false, extra_scope: selected, and offered_with_ids holding the colour service’s id.

object
name
required
string
duration_minutes
required
integer
buffer_before_minutes
integer
buffer_after_minutes
integer
min_lead_minutes
integer
max_advance_days

If you send 0, the API stores 60.

integer
min_cancel_notice_minutes
integer
price_cents
integer | null
description

Plain text. It renders on a public page. Render it as text. Do not render it as markup.

string | null
payment_mode

Whether booking this service takes a payment. One of none, full, deposit_fixed or deposit_percent. Left out, it is none.

The other three fields must match the mode. full needs price_cents and neither deposit field. deposit_fixed needs deposit_amount_cents and no percent. deposit_percent needs deposit_percent and price_cents. none needs neither deposit field.

Whatever the mode charges must come to more than zero. A price or deposit of 0 is rejected: it would save a service that no customer could ever book, because the payment would be refused at booking time rather than here.

string
deposit_amount_cents

The fixed deposit, in the smallest currency unit. Required by deposit_fixed and rejected by every other mode.

integer | null
deposit_percent

The deposit as a whole-number percentage of price_cents, from 1 to 100. Required by deposit_percent and rejected by every other mode. We round the resulting amount half up.

integer | null
category_id

A category of this workspace. POST /v1/services stores it; a category from another workspace, or one that does not exist, is a 404. PUT /v1/services/{id} ignores it: use PUT /v1/services/{id}/category.

string | null format: uuid
standalone

Whether the service can be booked on its own. Left out, it is true. When true, duration_minutes must be at least 1; an extra that is not booked on its own may take 0 minutes.

boolean
extra_scope

Which services this one can be added to as an extra. Left out, it is none. A service with standalone: false must not be none.

string
Allowed values: none all selected
offered_with_ids

Required when extra_scope is selected. Each id must then be an active service of this workspace that can be booked on its own, and not this service; an id that is not is a 422 naming it. The list replaces the one stored before.

Under all and none it is ignored and the stored list is kept, so the list read from ServiceDetail can be sent back as it is.

Array<string>
Example
{
"name": "Cut and finish",
"duration_minutes": 45,
"buffer_after_minutes": 10,
"min_lead_minutes": 120,
"max_advance_days": 60,
"min_cancel_notice_minutes": 1440,
"price_cents": 4000
}

The service is created.

Media type application/json

The full service, with the rules that drive availability. The Service schema is the smaller customer-facing view of the same row.

object
id
required
string format: uuid
name
required
string
duration_minutes
required
integer
buffer_before_minutes
required

Free time held before the appointment.

integer
buffer_after_minutes
required

Free time held after the appointment.

integer
min_lead_minutes
required

How far ahead a customer must book.

integer
max_advance_days
required

How far ahead a customer can book. If you send 0 on a write, the API stores 60.

integer
min_cancel_notice_minutes
required

How long before the start time cancellation closes. It binds customers only. A secret key cancels at any time. A value of 0 keeps cancellation open until the start time.

integer
active
required
boolean
price_cents
integer | null
description
string | null
payment_mode
string
deposit_amount_cents
integer | null
deposit_percent
integer | null
category_id

The category this service is in. Null means it is in none. Change it with PUT /v1/services/{id}/category.

string | null format: uuid
standalone
required

See ServiceWrite.

boolean
extra_scope
required

See ServiceWrite.

string
Allowed values: none all selected
offered_with_ids
required

The services this extra can be added to when extra_scope is selected. Always present, and [] when there are none. It is returned under all and none too, so a client switching back to selected can send the same list again. A service that has since been deactivated, or can no longer be booked on its own, is left out.

Array<string>
Example
{
"extra_scope": "none"
}

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

A plan limit refused the request. The fields in the body change with the key type. See the errors guide.

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

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