A staff member is somebody who performs services. You need this resource for two jobs: letting a customer choose who they see, and knowing which services a given person can actually do.
Read-only, permanently.
| Method | Endpoint | Scope | Plan |
|---|---|---|---|
| GET | /api/v1/staffList staff members Query: | staff:read | Groei |
| GET | /api/v1/staff/{id}Retrieve a staff member | staff:read | Groei |
What you get back
{
"id": "db8914de-a84e-49f9-8625-f44b4abcf4d6",
"name": "Sanne Jansen",
"isActive": true,
"isBookable": true,
"assignedServiceIds": [
"b50661da-7649-4c14-b748-91bbee8e9643",
"14fb4b5e-1eb9-4e88-b128-77862e0553d6"
],
"createdAt": "2026-07-29T17:57:46.245Z",
"updatedAt": "2026-07-29T17:57:46.245Z"
}
That is the whole record. There is no email, no phone number, no address, no employment detail, and no photo.
No contact details, and it is not an oversight
This is the resource most likely to make somebody ask where the rest of it is, so the answer is here rather than in a changelog entry later.
A staff member is an employee of the business, not a party to your integration. The business has their phone number because it employs them; a key handed to a contractor to build a booking page has no reason to receive the personal mobile number of everybody who works at a salon, and once a field is in a v1 response it is there for the life of the version. So the resource carries what a booking flow needs, which is a name to display and an id to book against.
A staff member is also not a user account. There is no login behind one, no role, and no relationship to whoever created the API key. If you need to know who is allowed to do what in the dashboard, that is a different concept entirely and it is not on this API. See permissions.
isActive and isBookable are two different questions
They look like a pair and they are not.
| Field | Question it answers |
|---|---|
isActive | Does this person still work here |
isBookable | May a customer choose this person when booking online |
Both have to be true for a customer-facing picker. A person who is isActive: true and
isBookable: false is a real, current employee that the business does not offer for online booking:
an apprentice whose diary the owner fills by hand, or a manager who takes appointments only by phone.
They still appear on appointments, so you still need to resolve their name.
Filtering a booking picker on isActive alone will offer customers somebody the business has
deliberately kept off the public widget, and the create will then be refused with
422 STAFF_NOT_BOOKABLE. Filter on both.
The same asymmetry is why POST /api/v1/appointments with a staffMemberId can fail where the
availability endpoint succeeded: GET /api/v1/availability/slots without a staffMemberId answers
for anybody bookable, and pinning a specific person afterwards is a stricter question.
assignedServiceIds is the source of truth for the pairing
Which services a person performs is a property of the person, not of the service, so there is no
staffMemberIds on a service. Build the reverse index yourself if you need it: one
GET /api/v1/staff?limit=100 gives you every pairing the business has.
A business may register at most a few dozen staff members, so the list fits in one page at
limit=100 for every realistic tenant. It is still cursor-paged like everything else, and you
should still walk the cursor rather than assuming one page, because the day a business proves
otherwise is not a day you want to find out by dropping half the team.
An empty assignedServiceIds means the person is assigned to nothing and cannot be booked for
anything, whatever isBookable says.