Developers

Owner API

DevelopersOwner API

The owner API is everything the dashboard does: read and mutate the tenant, services, staff, customers, bookings, and payments. All routes require an aud: owner JWT from POST /api/auth/login. The token is tenant-scoped: every call must match the tenantId encoded in the token.

Business

GET/api/businesses/me

Read the current tenant.

PATCH/api/businesses/me

Update business fields. Slug is NOT patchable here (set at registration).

businessNamestring

Business name (max 200 chars).

businessTypestring

Business type (max 80 chars).

addressLine1string

Street address (max 200 chars).

addressLine2string

Address line 2 (max 200 chars).

citystring

City (max 120 chars).

provinceStatestring

Province/state (max 120 chars).

postalCodestring

Postal code (max 40 chars).

countrystring

Country (max 120 chars).

phonestring

Display phone on the public page (max 40 chars).

emailstring

Contact email (max 200 chars).

websitestring

Website URL (max 300 chars).

descriptionstring

Public description (max 2000 chars).

logoUrlstring

Logo URL (max 500 chars).

coverImageUrlstring

Cover image URL (max 500 chars).

timezonestring

IANA timezone (max 80 chars).

currencystring

Currency code (max 8 chars, e.g. CAD).

countryCodestring

ISO country code (max 8 chars).

bookingAdvanceDaysinteger

How far ahead customers can book (1-365).

minNoticeHoursinteger

Minimum notice in hours (0-168).

bufferMinutesinteger

Buffer between bookings (0-120).

maxBookingsPerSlotinteger

Concurrent bookings per slot (1-50).

allowGuestBooking0 | 1

0 = account required, 1 = guest checkout allowed.

requirePayment0 | 1

0 = no deposit, 1 = deposit required at booking.

depositPercentinteger

0-100 percent of price charged as deposit.

cancellationHoursinteger

Free-cancellation window in hours (0-720).

cancellationPolicystring

Free-text policy (max 4000 chars).

customDomainstring

Custom domain CNAME target (max 200 chars).

templatestring

Template slug. The Zod schema accepts any 1-64 char string; the AI site-content mock currently only knows atelier / salon / spa / clinic. Other slugs are stored as-is and rendered by the dashboard.

templateAccent#rrggbb | null

Hex color for template accent. Use null to clear.

Services

GET/api/services

List services for the current tenant (includes inactive).

POST/api/services

Create a service.

namestringrequired

Service name (1-200 chars).

descriptionstring

One or two sentences (max 2000 chars).

durationMinutesintegerrequired

5-480.

priceinteger

Price in minor units (cents). 0-100000.

depositPercentinteger

Optional 0-100 percent.

maxAdvanceDaysinteger

1-365, optional.

minNoticeHoursinteger

0-168, optional.

bufferAfterMinutesinteger

0-120. Default 0.

maxBookingsPerSlotinteger

1-50. Default 1.

imageUrlstring

Optional image URL.

isActive0 | 1

Default 1. 0 hides from booking page.

isOnlineBookable0 | 1

Default 1. 0 means in-person only.

orderIndexinteger

Display order. Lower = earlier.

customFormFieldsstring

JSON-encoded custom form schema (max 8000 chars).

PATCH/api/services/:id

Update a service. Any subset of the create fields.

DELETE/api/services/:id

Soft-delete. Flips isActive to 0; historical bookings are preserved.

POST/api/services/reorder

Bulk-reorder. Body: { order: ["sv_a", "sv_b", ...] }. Sets order_index to position in array.

Staff

GET/api/staff

List all staff profiles for the current tenant.

POST/api/staff

Create a staff profile. Optionally links to an existing users row by email.

displayNamestringrequired

Display name (1-120 chars).

userEmailstring

If present and the email exists, the staff profile is linked to that user. Otherwise a new users row is created.

titlestring

Job title (max 120 chars).

biostring

1-2 sentences. Shown on the public page (max 2000 chars).

avatarUrlstring

Optional photo URL (max 500 chars).

color#rrggbb

Hex color used on the calendar (default #6366F1).

isBookable0 | 1

Default 1. 0 hides from public booking.

GET/api/staff/:id

Read a staff profile, including their workingHours array.

PATCH/api/staff/:id

Update a staff profile (no userEmail field). Any subset of displayName, title, bio, avatarUrl, color, isBookable.

DELETE/api/staff/:id

Soft-delete. Flips isBookable to 0; the staff member disappears from public booking.

PUT/api/staff/:id/hours

Replace the staff member's weekly schedule.

hoursarrayrequired

Array of { dayOfWeek: 0-6, isOpen: 0|1, openTime: HH:MM, closeTime: HH:MM }.

Bookings

GET/api/bookings

List bookings. Supports filters.

statusenum

One of: pending, confirmed, completed, cancelled, no_show.

startDateISO-8601

Filter start.

endDateISO-8601

Filter end.

staffIdstring
customerIdstring
serviceIdstring
limitinteger

Default 50, max 200.

offsetinteger

Default 0.

curl "https://booking-api.netwit.ca/api/bookings?status=confirmed&startDate=2026-08-01&limit=50" \
  -H "authorization: Bearer $OWNER_TOKEN"
GET/api/bookings/stats

Aggregate stats for the current tenant: today's bookings, upcoming (7d), no-show count, week revenue (all-time cents).

{
  "data": {
    "todayCount": 8,
    "upcomingCount": 47,
    "noShowCount": 6,
    "weekRevenue": 124000
  }
}
POST/api/bookings

Manual-create a booking from the dashboard. Either customerId or newCustomer is required.

serviceIdstringrequired
staffProfileIdstringrequired
startDatetimeISO-8601required
customerIdstring

Existing customer id (mutually exclusive with newCustomer).

newCustomerobject

Inline new customer: { firstName, lastName?, email, phone? }.

notesstring

Booking-specific notes (max 2000 chars).

statusenum

pending | confirmed | completed | cancelled | no_show. Defaults to confirmed.

GET/api/bookings/:id

Read one booking, with joined service, staff, and customer objects.

PATCH/api/bookings/:id

Update a booking. Any subset of notes, internalNotes, status.

POST/api/bookings/:id/confirm

Transition pending → confirmed.

POST/api/bookings/:id/cancel

Transition to cancelled (sets cancelled_at = now). Use /api/payments/:id/refund separately to refund the deposit.

POST/api/bookings/:id/complete

Transition confirmed → completed.

POST/api/bookings/:id/no-show

Transition confirmed → no_show (sets no_show_at = now).

POST/api/bookings/:id/notes

Append a timestamped internal note. Body: { note: string }.

POST/api/bookings/:id/reschedule

Move a booking to a new start time. Body: { newStartDatetime, staffProfileId? }.

Customers

GET/api/customers

List customers. Supports q (LIKE on first_name/last_name/email), tag, blacklisted=1, limit, offset.

GET/api/customers/export

Returns a CSV download. Headers: first_name, last_name, email, phone, total_bookings, total_spent, no_show_count, is_blacklisted.

POST/api/customers

Create a customer.

firstNamestringrequired

First name (1-100 chars).

lastNamestring

Last name (max 100 chars).

emailstring

Email (max 200 chars).

phonestring

Phone (max 40 chars).

notesstring

Internal notes (max 4000 chars).

tagsstring[]

Up to 50 tags (each max 50 chars).

GET/api/customers/:id

Read a customer (includes the 25 most recent bookings).

PATCH/api/customers/:id

Update a customer. Fields: firstName, lastName, email, phone, notes, tags, isBlacklisted, blacklistReason, birthday, referralSource.

DELETE/api/customers/:id

GDPR anonymize: blanks PII fields but keeps the row for audit.

GET/api/customers/:id/bookings

All bookings for this customer.

POST/api/customers/:id/notes

Append a timestamped note. Body: { note: string }.

Payments

POST/api/payments/businesses/me/stripe/connect

Start Stripe Connect onboarding. Returns a redirect URL (onboardingUrl) and accountId.

GET/api/payments/businesses/me/stripe

Read the current Stripe connection: connected, mode (test|live), chargesEnabled, payoutsEnabled, detailsSubmitted.

DELETE/api/payments/businesses/me/stripe

Disconnect Stripe. Clears stripe_account_id and stripe_charges_enabled.

POST/api/payments/intent

Create a Stripe PaymentIntent for a booking. Returns clientSecret for the Stripe.js front-end.

bookingIdstringrequired
paymentTypeenum

full or deposit. Defaults to deposit if the booking has a non-zero deposit_amount, otherwise full.

customerEmailstring

Optional. Forwarded to Stripe receipt.

customerNamestring

Optional. Forwarded to Stripe receipt (max 200 chars).

POST/api/payments/confirm

Read-after-write check. The webhook is the source of truth; this endpoint exists for synchronous UX. Body: { bookingId }. Returns the booking's status and payment_status.

GET/api/payments/reports

Aggregate payments by status. Returns rows of { status, c, total } where total is SUM(amount) in cents.

GET/api/payments/:id

Read a single payment.

POST/api/payments/:id/refund

Refund a payment. Body: { amount?, reason? }. amount is in MAJOR units (dollars); omit for a full refund. In test mode the database is updated directly; in live mode the Stripe refund is created.

amountnumber

Refund amount in major units (dollars). Omit for a full refund.

reasonstring

Free-text reason (max 500 chars).

Common errors

  • 400 invalid_request — body validation failed (Zod). Details in details.fieldErrors.
  • 401 unauthorized — token missing, invalid, or wrong audience.
  • 403 tenant required / tenant not found — authed but no valid tenant context (check JWT tenantId).
  • 404 not_found — id doesn't exist or is on a different tenant.
  • 409 invalid_transition — tried to change booking status to one not allowed from current status (e.g. cancelled → completed).
  • 409 slot_taken — slot was just booked by another customer.
  • 409 slug_taken — business slug already exists.
  • 409 duplicate — staff user already attached to a staff_profile in this tenant.
  • 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.