Appointments engine

Remove a time-away entry (admin, or manage_staff)

delete/api/providers/{id}/blocked-periods

The provider's ordinary hours (and any approved date-specific override) apply again over that range immediately. Scoped to both id and this provider: a bare id match would let a caller delete a whole-venue closure or another provider's entry by reusing a uuid from the same table.

Authorization

sessionCookie
sb-qxrvgfkjyvbngipqvslu-auth-token<token>

The dashboard's Supabase Auth session cookie, set at sign-in. Large sessions are split across numbered chunks (…auth-token.0, .1), so treat this as a cookie family rather than one name.

Every request re-validates it against the Auth server (getUser()), never by decoding the cookie locally: a JWT nothing has checked is not a credential. Tenancy is then read from the verified app_metadata.company_id claim and enforced by row-level security; it is never read from request input, on any route, ever.

role (admin / staff) is deliberately not in RLS. It gates specific actions in route code, the operations marked admin below, so hiding a button in the UI is cosmetic only, and a route's own check is the enforcement.

In: cookie

Path Parameters

id*string

Provider id.

Query Parameters

id*string

The blocked-period row id.

Formatuuid

Response Body

application/json

application/json

application/json

application/json

application/json

curl -X DELETE "https://example.com/api/providers/string/blocked-periods?id=497f6eca-6276-4993-bfeb-53cbbbba6f08"
{  "ok": true}

Mark a staff member away (admin, or manage_staff) POST

Writes a book_blocked_periods row (migration 0001) with this provider's id, covering `firstDay` 00:00 to `lastDay` 23:59:59 as one instant range: the same "wall clock wearing a +00" convention every other date-scoped write in this app uses. No engine change was needed to make this take effect. book_blocked_periods with a `provider_id` was already read on every public path that decides whether a provider is bookable (GET /api/book/{slug}/availability, POST /api/book/{slug}/appointments, the guest reschedule inside /api/book/{slug}/manage/{token}) before this route existed to write one. A `provider_id IS NULL` row is a DIFFERENT, older feature (a whole-venue closure, set from Organization > Special hours); this route never writes one. **Conflicts** against existing, non-cancelled appointments anywhere in the range are checked live, the same `findConflictingAppointments` pattern `POST /api/providers/{id}/availability-overrides` and `POST /api/organization/special-hours` already prove: refused with 409 unless `conflictAcknowledged: true`. Every caller who can reach this route already holds `manage_staff`, so unlike the override route there is no staff-vs-admin branch; acknowledgment alone is the gate, same as special-hours' own reasoning. Self-service (a staff member marking their own leave) is deliberately out of scope: this is the same admin/manage_staff boundary as the rest of provider CRUD, not the tiered self-request flow `book_availability_overrides` uses.

List availability-override requests GET

Staff sees only their own linked provider's rows (server-forced, ignoring any client filter; there is none to send); admin sees every row across every status, so this one endpoint powers both a "needs attention" view (filter client-side on `status: pending`, or `approved` with `conflictAcknowledged` and `conflictCount > 0`) and a full history view. `conflictCount` is recomputed live on every call for an admin caller (always 0 for staff); migration 0052 deliberately does not persist which appointments conflicted, so a later cancellation or reassignment through the ordinary calendar screen makes a row stop needing attention with no write required.