Appointments engine

Mark a staff member away (admin, or manage_staff)

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

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.

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.

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

curl -X POST "https://example.com/api/providers/string/blocked-periods" \  -H "Content-Type: application/json" \  -d '{    "firstDay": "2019-08-24",    "lastDay": "2019-08-24"  }'
{  "ok": true,  "blockedPeriod": {    "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",    "starts_at": "2019-08-24T14:15:22Z",    "ends_at": "2019-08-24T14:15:22Z",    "reason": "string"  }}

Request a date-specific schedule override POST

Submit a one-date replacement for this provider's recurring hours (migration 0052); more hours, fewer, or none at all (a day off). The approve-immediately-or-queue decision is made INSIDE `book_submit_availability_override`, from the caller's role, the provider's `availability_delegation` tier, and the org's `availability_approval_horizon_days`, all read from the database inside the function's own transaction, never trusted from this request body, so calling the underlying RPC directly cannot produce a different outcome than this route does. **Ownership**: an admin may submit for any provider in the org; any other role only for the provider row linked to their own login. An unlinked staff login gets 403; a deliberate fail-closed departure from this codebase's usual fail-open convention for an unlinked staff member, because that convention protects a front-desk person's own usability, not license to edit someone else's schedule with no approval. **Conflicts** against existing, non-cancelled appointments on the date are checked live, unconditionally. A staff caller with a conflict is always hard-blocked (409, nothing persisted); they already have the tool to fix it themselves (reschedule/cancel their own booking). An admin caller may proceed by sending `conflictAcknowledged: true`. On success, notifies the org's admins always, and the affected staff login too when the change applied without needing their own approval and they were not the one who submitted it (an admin acting on their behalf).

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

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.