Public API
The public API powers the customer-facing booking flow. It requires no auth, is heavily rate limited (30 requests / 60s per IP), and is the only API surface you can hit without a token.
Conventions
- All endpoints accept and return JSON.
- Successful responses are wrapped in
{ data: ... }. - Errors follow the Errors conventions.
- All times are ISO-8601 strings in UTC.
- All prices are in minor units (cents) unless otherwise noted.
GET /api/public/:slug
/api/public/:slugReturns the public profile of a business. Field names mirror the tenants table (snake_case). Only active tenants are returned; inactive or suspended tenants return 404.
{
"data": {
"id": "tn_xxx",
"slug": "demo-salon",
"business_name": "Demo Salon",
"business_type": "salon",
"description": "Full-service hair and nails.",
"logo_url": "https://...",
"cover_image_url": null,
"timezone": "America/Vancouver",
"currency": "CAD",
"country_code": "CA",
"city": "Vancouver",
"province_state": "BC",
"country": "Canada",
"address_line1": "1234 Main St",
"phone": "+16045551234",
"email": "hello@demosalon.example",
"cancellation_hours": 24,
"cancellation_policy": "Free cancellation up to 24h before...",
"allow_guest_booking": 1,
"require_payment": 0,
"widget_color": "#6366F1",
"widget_mode": "popup",
"template": "atelier",
"template_accent": "#6366F1"
}
}GET /api/public/:slug/services
/api/public/:slug/servicesReturns the list of services the business offers. Active AND online-bookable only, sorted by order_index then name. snake_case fields.
{
"data": [
{
"id": "sv_abc",
"name": "Haircut",
"description": "Classic cut, wash, and style",
"duration_minutes": 45,
"price": 6500,
"deposit_percent": 25,
"image_url": null,
"is_online_bookable": 1,
"order_index": 0
}
]
}GET /api/public/:slug/staff
/api/public/:slug/staffReturns the list of staff profiles marked as bookable. snake_case fields. Sorted by order_index. The API does NOT return a per-staff service mapping; staff availability is computed from the working_hours table per request.
{
"data": [
{
"id": "sp_alice",
"display_name": "Alice",
"title": "Senior Stylist",
"avatar_url": null,
"color": "#6366F1"
}
]
}GET /api/public/:slug/availability
/api/public/:slug/availabilityReturns bookable time slots for one service, one optional staff, and one date. Slots are 15-minute increments inside each staff member's working hours, less any existing booking.
serviceIdstringrequiredThe service ID (e.g. sv_abc). Mandatory.
dateYYYY-MM-DDrequiredThe date in the tenant's timezone.
staffIdstringOptional. A specific staff ID. Omit for "any bookable staff".
timezoneIANADefaults to UTC. Currently informational only — slots are returned in the tenant's local YYYY-MM-DDTHH:MM:SS form.
{
"data": {
"date": "2026-08-15",
"timezone": "America/Vancouver",
"slots": [
{ "staffProfileId": "sp_alice", "staffName": "Alice", "startTime": "2026-08-15T09:00:00", "endTime": "2026-08-15T09:45:00" },
{ "staffProfileId": "sp_alice", "staffName": "Alice", "startTime": "2026-08-15T09:15:00", "endTime": "2026-08-15T10:00:00" }
]
}
}POST /api/public/:slug/bookings
/api/public/:slug/bookingsCreate a new booking. Returns 403 with code guest_disabled if the tenant does not allow guest checkout. Returns 409 with code slot_taken if the slot is gone.
serviceIdstringrequiredThe service ID.
staffProfileIdstringrequiredThe staff ID.
startDatetimeISO-8601requiredBooking start time. Treated as tenant-local; the first 10 chars (YYYY-MM-DD) are the date the slot was found on.
customer.firstNamestringrequiredFirst name (1-100 chars).
customer.lastNamestringLast name (max 100 chars).
customer.emailstringrequiredEmail for confirmation.
customer.phonestringPhone (max 40 chars).
customer.notesstringNotes for the business (max 2000 chars).
sourceenumonline | widget | walk_in | phone | admin. Default online.
{
"data": {
"id": "bk_abc",
"confirmationCode": "BF-A4F2X9",
"status": "confirmed",
"customerUserId": null
}
}If a customer JWT is provided in the Authorization header (aud: customer), the booking is also linked to that global account and customerUserId is set.
code: "slot_taken". Re-fetch availability and ask the user to pick a new time.GET /api/public/booking/:code
/api/public/booking/:codeLook up a booking by its confirmation code (9 chars, prefixed BF-, e.g. BF-A4F2X9). Anyone with the code can read it. Returns id, tenant_id, start_datetime, end_datetime, status, confirmation_code, notes, and the business slug + name.
POST /api/public/booking/:code/reschedule
/api/public/booking/:code/rescheduleMove a booking to a new time. The code is the full confirmation_code including the BF- prefix.
newStartDatetimeISO-8601requiredNew start time.
staffProfileIdstringNew staff. Defaults to the current staff.
POST /api/public/booking/:code/cancel
/api/public/booking/:code/cancelCancel a booking. Idempotent: if the booking is already cancelled, returns 200 with alreadyCancelled: true.
reasonstringFree-text reason. Stored on the booking for audit.
GET /api/public/:slug/reviews
/api/public/:slug/reviewsList reviews for the business. The reviews table is not yet shipped in v1, so this endpoint currently returns an empty array. The route exists for forward compatibility.
{
"data": []
}Email hello@netwit.ca or call +1-604-206-8169. NetWit responds in 1 business day.