Developers

Errors

DevelopersErrors

BookFlow uses two error envelope shapes depending on which layer produced the error. Both are documented here. Successful responses are always wrapped in { data: ... }.

Shape 1: simple string (rate limiter, middleware)

{ "error": "rate_limited" }

Used by the rate limiter, CSRF middleware, and a few low-level handlers.

Shape 2: object (handler errors, validation)

{
  "error": {
    "code": "invalid_credentials",
    "message": "Wrong email or password",
    "details": { "fieldErrors": { "email": ["Invalid email"] } }
  }
}

Used by handlers and Zod validation. The code is a stable machine-readable string; message is human-readable; details is only present on zod validation failures.

It's the result of zodError.flatten() and matches: formErrors: string[], fieldErrors: Record<string, string[]>.

HTTP status codes

CodeMeaningWhen
200OKSuccessful GET, PATCH, or POST that doesn't create a new resource
201CreatedSuccessful POST that creates a new resource
400Bad RequestBody validation failed (Zod). Shape 2 with code: invalid_request
401UnauthorizedMissing or invalid token, or wrong audience (Shape 1: { error: 'unauthorized' })
403ForbiddenToken valid but lacks the required role or scope (Shape 1: { error: 'tenant required' | 'tenant not found' })
404Not FoundResource doesn't exist or isn't visible to the caller (Shape 2: { code: 'not_found' })
409ConflictResource state conflict (slot taken, duplicate, slug_taken, invalid_transition, etc.)
429Too Many RequestsRate limit exceeded (Shape 1: { error: 'rate_limited' }, with Retry-After header)
500Server ErrorUnhandled error. NetWit gets an alert
503Service UnavailableAdmin /api/admin/system/health returns 503 when D1 or KV probes fail

Common error codes

CodeHTTPMeaning
invalid_request400Zod validation failed. details.fieldErrors has per-field messages.
unauthorized (string)401Token missing, invalid, or wrong audience. Body is { error: 'unauthorized' }.
invalid_credentials401Login: wrong email or password.
tenant required / tenant not found403Authed but no valid tenant context (check JWT tenantId).
not_found404Resource not found.
slot_taken409Slot was just booked by another customer. Re-fetch availability.
email_taken409Register: email already exists.
slug_taken409Register or POST /api/businesses: business slug already exists.
invalid_transition409Tried to change a booking's status to one not allowed from its current status (e.g. cancelled → completed).
not_reschedulable409Booking is in a terminal state (cancelled, completed, no_show) and cannot be rescheduled.
not_cancellable409Booking is completed or no_show and cannot be cancelled.
guest_disabled403Public booking attempted but tenant does not allow guest checkout.
stripe_not_connected400Tried to create a payment intent before the owner connected Stripe.
rate_limited (string)429Rate limit hit. Wait the number of seconds in Retry-After.

Idempotency

BookFlow does not currently implement an Idempotency-Key header on POST endpoints. To safely retry on network errors, use a unique idempotency_key column on the client side (e.g. a UUID per booking attempt) and check for 409 slot_taken to detect a duplicate that beat you to it.

Request IDs

BookFlow does not currently emit an X-Request-Id response header. When you contact support, include the request URL, method, full response body, and approximate timestamp so the team can correlate against worker logs.

Need a human?

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