Machine-to-machine access for hotel and business systems — guest passes, bookings, catalog, staff.
Base URL: https://<your-salon-domain>/api/v1
Authentication: every request carries your API key (issued by the salon in Admin → API keys):
Authorization: Bearer mp_live_...
Conventions: you address every resource by your own extern_id, which you supply on creation. All timestamps are ISO 8601 with offset (UTC recommended). Prices are integer cents. DELETE on catalog resources deactivates them ("passive") — history is preserved and they can be reactivated with PATCH {"active": true}.
| Status | Body | Meaning |
|---|---|---|
| 401 | {"error":"invalid_api_key"} | Missing, malformed, or revoked key |
| 403 | {"error":"insufficient_scope","required":"..."} | Key lacks the required permission |
| 400 | {"error":"invalid_request","detail":...} | Validation failed (detail explains) |
| 404 | {"error":"not_found"} | No resource with that extern_id |
| 409 | {"error":"extern_id_conflict"} | You already used this extern_id |
| 409 | {"error":"slot_unavailable"} | Booking time no longer bookable |
| 400 | {"error":"overlapping_shifts","weekday":n} | Working-hours intervals overlap |
A guest pass is a personal booking link. The guest's details are pre-filled on the booking page, the reservation number is locked in, and the link stops working at valid_until (checkout).
POST/guest-passes
GET/guest-passes/{extern_id}
PATCH/guest-passes/{extern_id} — e.g. extend valid_until on late checkout
POST/guest-passes/{extern_id}/revoke — e.g. early checkout
POST /api/v1/guest-passes
Authorization: Bearer mp_live_...
Content-Type: application/json
{
"extern_id": "pms-checkin-49821",
"guest_name": "Nesrin Yilmaz",
"guest_email": "nesrin@example.com",
"guest_phone": "+32 470 00 00 00",
"reservation_ref": "HTL-2026-777",
"valid_from": "2026-09-05T00:00:00Z",
"valid_until": "2026-09-08T23:59:59Z"
}
Response 201 — email or print booking_url for the guest:
{
"guest_pass": {
"extern_id": "pms-checkin-49821",
"guest_name": "Nesrin Yilmaz",
"guest_email": "nesrin@example.com",
"reservation_ref": "HTL-2026-777",
"valid_from": "2026-09-05T00:00:00+00:00",
"valid_until": "2026-09-08T23:59:59+00:00",
"revoked_at": null,
"booking_url": "https://weedoo.massageplanner.app/g/60778b98..."
}
}
POST/bookings — 1 member = solo, 2 members = couple
GET/bookings?from=&to=
POST/bookings/{ref}/cancel
POST/bookings/{ref}/approve — confirm a booking in status pending
{ref} is your extern_id, or the numeric booking number. A booking returns status pending when the salon must move portable equipment into the room first; it holds its slot either way.
POST /api/v1/bookings
{
"extern_id": "pms-book-1201",
"starts_at": "2026-09-06T13:00:00Z",
"reservation_ref": "HTL-2026-777",
"members": [
{
"service_extern_id": "classic-60",
"customer": { "name": "Nesrin Yilmaz", "email": "nesrin@example.com" }
}
]
}
Response 201:
{
"booking": {
"booking_no": 42,
"status": "confirmed",
"starts_at": "2026-09-06T13:00:00+00:00",
"ends_at": "2026-09-06T14:00:00+00:00",
"manage_token": "1d02...",
"group_id": "afc2..."
}
}
staff_extern_id (per member) and room_extern_id are optional — omitted, the engine picks any qualified therapist and suitable room. If the time cannot be satisfied you get 409 slot_unavailable.
GET/services · POST/services · PATCH/services/{extern_id} · DELETE/services/{extern_id}
PUT/services/{extern_id}/required-items — replaces the full list
GET/items · POST/items · PATCH/items/{extern_id} · DELETE/items/{extern_id}
GET/rooms · POST/rooms · PATCH/rooms/{extern_id} · DELETE/rooms/{extern_id}
PUT/rooms/{extern_id}/fixed-items · PUT/rooms/{extern_id}/restrictions
POST /api/v1/services
{ "extern_id": "hotstone-90", "name": "Hot Stone Massage",
"description": "Warmed basalt stones melt deep muscle tension",
"duration_min": 90, "price_cents": 9500 }
PUT /api/v1/services/hotstone-90/required-items
[ { "item_extern_id": "stone-heater", "quantity": 1 } ]
POST /api/v1/items
{ "extern_id": "stone-heater", "name": "Hot Stone Heater", "portable": false }
POST /api/v1/rooms
{ "extern_id": "room-2", "name": "Room 2", "capacity": 1 }
PUT /api/v1/rooms/room-2/fixed-items
[ { "item_extern_id": "stone-heater", "quantity": 1 } ]
PUT /api/v1/rooms/room-2/restrictions
[ "aroma-diffuser" ]
GET/staff · POST/staff · PATCH/staff/{extern_id} · DELETE/staff/{extern_id}
PUT/staff/{extern_id}/skills — array of service extern_ids, replaces the set
PUT/staff/{extern_id}/working-hours — replaces the weekly schedule
GET/time-off · POST/time-off · DELETE/time-off/{extern_id}
PUT /api/v1/staff/therapist-ayla/working-hours
[
{ "weekday": 1, "start_time": "08:00", "end_time": "12:00" },
{ "weekday": 1, "start_time": "14:00", "end_time": "18:00" },
{ "weekday": 2, "start_time": "09:00", "end_time": "17:00" }
]
Weekday 0 = Sunday. Shifts on one day must not overlap (touching boundaries like 12:00/12:00 are fine).
POST /api/v1/time-off
{ "extern_id": "leave-2026-09-10", "staff_extern_id": "therapist-ayla",
"starts_at": "2026-09-10T00:00:00Z", "ends_at": "2026-09-11T00:00:00Z",
"reason": "Day off" }
Omit both staff_extern_id and room_extern_id to close the whole salon; pass room_extern_id for room maintenance.
Powered by massageplanner.app