Return a date to the recurring week (admin, or manage_org_settings)
/api/organization/special-hoursRemoves the row, so the date follows the ordinary weekly hours again. Emphatically not the same as saving it with no windows, which is how a business says it is shut. Deleting a date that was never set is not an error: the caller asked for this date to follow the ordinary week, and it now does.
Authorization
sessionCookie 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
Query Parameters
YYYY-MM-DD.
dateResponse Body
application/json
application/json
application/json
application/json
curl -X DELETE "https://example.com/api/organization/special-hours?date=2019-08-24"{ "ok": true}Set what happens on one date (admin, or manage_org_settings) POST
An upsert keyed on `(company_id, special_date)`, so sending the same body twice is the same as sending it once. **An empty `windows` array means CLOSED, and it is the most common write this route takes.** That is the opposite of what an empty `book_company_hours` means, and the two live inches apart: zero rows there means UNCLAMPED (every org is in that state until it saves hours), while here the row EXISTS, so `[]` is somebody deliberately saying "nothing". Closing a date is therefore NOT the same as `DELETE`ing it, which returns the date to the ordinary week. An `endTime` earlier than `startTime` runs past midnight (migration 0042) and is accepted: an 18:00 to 02:00 New Year's Eve is the single most likely row anyone writes here. Only a zero-length window is refused. No replace-the-set function stands behind this, unlike the hours and service-period routes: one date is one row, so an upsert is already one statement and therefore one transaction. Gated to admin, or a staff login granted `manage_org_settings` (migration 0082). **Conflicts** against existing, non-cancelled bookings on the date are checked live, unconditionally, the same `findConflictingAppointments` predicate `POST /api/providers/{id}/availability-overrides` already uses: does the booking fall inside one of the proposed windows. Reads `book_reservations` for a hospitality company and `book_appointments` for an appointments one, company-wide rather than per-provider, since this table has no provider column. A conflict refuses with 409 and nothing is written; resend with `conflictAcknowledged: true` to proceed anyway. Unlike the override route this never hard-blocks by role: every caller who can reach this route already holds `manage_org_settings`, so acknowledgment alone is the gate. The route never touches the conflicting bookings themselves: no auto-move, no auto-cancel, no auto-refund. Resolving each one is a separate, deliberate action from Bookings.
List this company's tiered no-show fees GET
Every row of `book_no_show_fee_tiers` (migration 0137) this company has configured, in `threshold_hours` order: what DepositsCard's tier editor renders and what `GET /api/payments/no-show-fee` resolves a suggestion from. Readable by any member; only the writes below are gated.