Developers

Customer API

DevelopersCustomer 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 account
  • POST /api/customer-auth/login — sign in, returns token
  • POST /api/customer-auth/logout — invalidate session
  • GET /api/customer-auth/me — current customer
  • PATCH /api/customer-auth/me — update profile
  • POST /api/customer-auth/change-password — change password
  • POST /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 tenants
  • GET /api/me/bookings/:id — single booking detail
  • POST /api/me/bookings/:id/reschedule — move to a new time
  • POST /api/me/bookings/:id/cancel — cancel a booking
  • GET /api/me/stats — counts (total bookings, completed, no-shows, favorite salon, etc.)

Register

POST/api/customer-auth/register

Create a new customer account. Rate limited to 5 / 60s per IP.

emailstringrequired

The customer's email (1-200 chars).

passwordstringrequired

Plain-text, 8-200 chars.

firstNamestringrequired

First name (1-100 chars).

lastNamestring

Last name (1-100 chars).

phonestring

Phone (max 40 chars).

marketingOptInboolean

Default false.

timezonestring

IANA 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

POST/api/customer-auth/login

Sign in an existing customer. Rate limited to 10 / 60s per IP.

emailstringrequired
passwordstringrequired
{`{ "data": { "token": "eyJ...", "sessionId": "...", "customer": { "id": "cu_abc", "email": "jane@example.com", ... } } }`}

My bookings

GET/api/me/bookings

List 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.

scopeenum

Filter 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

POST/api/me/bookings/:id/reschedule

Reschedule 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-8601required

New start time in UTC.

staffProfileIdstring

New staff member. Defaults to the current staff.

Cancel

POST/api/me/bookings/:id/cancel

Cancel a booking. Returns 409 with code not_cancellable if the booking is completed or no_show.

reasonstring

Optional reason (max 1000 chars).

Stats

GET/api/me/stats

Get 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.
Need a human?

Email hello@netwit.ca or call +1-604-206-8169. NetWit responds in 1 business day.