Shared

What the notification bell shows

get/api/notifications

The next few bookings and the newest unread guest messages, in one read. Polled by NotificationDataProvider.tsx every 60 seconds: there is no Supabase Realtime wiring in this product (no supabase.channel() calls, no publication on the booking tables), and standing that up for one bell would be a great deal of infrastructure for what is, per tenant, a handful of rows a minute.

Shared, and genuinely so rather than merely ungated. It calls requireMember() with no vertical and then branches on business_type itself; upcoming appointments for one engine, upcoming reservations for the other; because one bell serves either product. Naming a vertical here would be wrong, not missing.

Both lists are capped at 5 and are a display feed, not a paged resource: there is no cursor, and asking for more is what the Bookings and Inbox screens are for. An account with no company answers 200 with two empty arrays, not a 403; the bell renders on every dashboard page and must not be the thing that breaks a half-provisioned session.

seenAt (migration 0208) is the caller's own "last opened the bell" cursor: bookings/messages/overrides created at or before it render as seen (greyed, not removed) in the dropdown, and only unseen ones count toward its badge. null means never opened. Set via PATCH on this same path.

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

Response Body

application/json

application/json

application/json

curl -X GET "https://example.com/api/notifications"
{  "bookings": [    {      "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",      "startsAt": "2019-08-24T14:15:22Z",      "customerName": "string",      "title": "string",      "color": "string",      "href": "string",      "createdAt": "2019-08-24T14:15:22Z"    }  ],  "messages": [    {      "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",      "customerName": "string",      "preview": "string",      "createdAt": "2019-08-24T14:15:22Z",      "href": "string"    }  ],  "seenAt": "2019-08-24T14:15:22Z"}