Customer API
The customer API is what a logged-in customer (someone with a BookFlow account who books appointments) can do. Tokens are issued by POST /api/customer-auth/login and target the aud: customer audience.
A single customer account can have bookings with many tenants. The token is not tenant-scoped — the customer's /me page shows their full booking history across all BookFlow businesses.
Auth
POST /api/customer-auth/register— create a new customer accountPOST /api/customer-auth/login— sign in, returns tokenPOST /api/customer-auth/logout— invalidate sessionGET /api/customer-auth/me— current customerPATCH /api/customer-auth/me— update profilePOST /api/customer-auth/change-password— change passwordPOST /api/customer-auth/forgot-password— start reset flow
Self-service
The customer profile is at /api/customer-auth/me. Booking self-service is at /api/me/*:
GET /api/me/bookings— every booking this customer has, across all tenantsGET /api/me/bookings/:id— single booking detailPOST /api/me/bookings/:id/reschedule— move to a new timePOST /api/me/bookings/:id/cancel— cancel a bookingGET /api/me/stats— counts (total bookings, completed, no-shows, favorite salon, etc.)
Register
/api/customer-auth/registerCreate a new customer account. Rate limited to 5 / 60s per IP.
emailstringrequiredThe customer's email (1-200 chars).
passwordstringrequiredPlain-text, 8-200 chars.
firstNamestringrequiredFirst name (1-100 chars).
lastNamestringLast name (1-100 chars).
phonestringPhone (max 40 chars).
marketingOptInbooleanDefault false.
timezonestringIANA timezone (max 64 chars). Default America/Vancouver.
{
"data": {
"token": "eyJ...",
"sessionId": "...",
"customer": {
"id": "cu_abc",
"email": "jane@example.com",
"firstName": "Jane",
"lastName": "Doe",
"phone": null,
"defaultTimezone": "America/Vancouver",
"emailVerified": false,
"marketingOptIn": false,
"createdAt": "2026-08-15T18:00:00Z",
"lastLoginAt": null
}
}
}Login
/api/customer-auth/loginSign in an existing customer. Rate limited to 10 / 60s per IP.
emailstringrequiredpasswordstringrequired{`{ "data": { "token": "eyJ...", "sessionId": "...", "customer": { "id": "cu_abc", "email": "jane@example.com", ... } } }`}My bookings
/api/me/bookingsList every booking for the current customer. Default scope: all. Supports ?scope=upcoming|past|cancelled|all (default all). Sorted: upcoming ASC by start_datetime, others DESC. Hard cap: 100.
scopeenumFilter by scope: upcoming, past, cancelled, all. Default all.
{
"data": [
{
"id": "bk_abc",
"tenantId": "tn_xxx",
"startDatetime": "2026-08-15T16:00:00Z",
"endDatetime": "2026-08-15T16:45:00Z",
"durationMinutes": 45,
"timezone": "America/Vancouver",
"subtotal": 6500,
"totalAmount": 6500,
"amountPaid": 0,
"currency": "CAD",
"status": "confirmed",
"paymentStatus": "unpaid",
"cancellationReason": null,
"cancelledAt": null,
"notes": null,
"source": "online",
"confirmationCode": "BF-A4F2X9",
"createdAt": "2026-08-14T09:11:00Z",
"service": { "id": "sv_abc", "name": "Haircut" },
"staff": { "id": "sp_alice", "name": "Alice" },
"business": {
"id": "tn_xxx",
"name": "Demo Salon",
"slug": "demo-salon",
"logoUrl": null,
"widgetColor": "#6366F1"
}
}
]
}Reschedule
/api/me/bookings/:id/rescheduleReschedule a booking. Returns 409 with code slot_taken if the new slot is gone, or 409 with code not_reschedulable if the booking is in a terminal state.
newStartDatetimeISO-8601requiredNew start time in UTC.
staffProfileIdstringNew staff member. Defaults to the current staff.
Cancel
/api/me/bookings/:id/cancelCancel a booking. Returns 409 with code not_cancellable if the booking is completed or no_show.
reasonstringOptional reason (max 1000 chars).
Stats
/api/me/statsGet aggregate counts for the current customer.
{
"data": {
"totalBookings": 23,
"totalSpent": 145000,
"upcomingCount": 2,
"pastCount": 20,
"cancelledCount": 1,
"favoriteSalon": {
"tenantId": "tn_xxx",
"name": "Demo Salon",
"slug": "demo-salon",
"bookings": 14
}
}
}Common errors
400 invalid_request— body validation failed (Zod). details.fieldErrors has field-level messages.401 unauthorized— token missing, invalid, or expired (24h lifetime).404 not_found— booking doesn't exist or doesn't belong to this customer.409 not_reschedulable— booking is cancelled, completed, or no_show.409 not_cancellable— booking is completed or no_show.409 slot_taken— reschedule target is already booked.429 rate_limited— too many requests; check Retry-After.
Email hello@netwit.ca or call +1-604-206-8169. NetWit responds in 1 business day.