This website's traffic (self-hosted, cookieless)
/api/organization/website/analyticsThe "Your website" card's one data source: a self-hosted Umami instance (analytics.solvintia.com), one Umami "website" per company for clean per-tenant isolation (no cross-tenant prefix-summing to get wrong). Cookieless by design, see the Cookie Policy entry this shipped alongside for the exact claim. Lazily provisions the company's Umami website on first call if it has none yet (ensureUmamiWebsiteId, src/lib/booking/umami-website-admin.ts) rather than requiring a separate setup step. Included on Pro (src/lib/plan.ts, analytics), checked before any Umami call; readable by any member once past that gate, since this is a display of the business's own public traffic, not a setting.
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
The trailing window. Anything else silently falls back to 30, the same permissive-default posture holidays' own year param takes.
Value in
- 7
- 30
- 90
Response Body
application/json
application/json
application/json
application/json
application/json
application/json
curl -X GET "https://example.com/api/organization/website/analytics"{ "days": 0, "summary": { "views": 0, "visitors": 0, "visits": 0 }, "trend": [ { "date": "string", "views": 0, "visitors": 0 } ], "popularPages": [ { "label": "string", "count": 0 } ], "referrers": [ { "label": "string", "count": 0 } ], "devices": [ { "label": "string", "count": 0 } ], "countries": [ { "label": "string", "count": 0 } ]}Disconnect the connected custom domain (admin) DELETE
Removes the domain from this Vercel project, then the `book_custom_domains` row. Idempotent: 200 with no error if nothing was connected in the first place, the end state ("no domain connected") is what the caller wants either way.
Take the public site offline, or bring it back (admin) POST
Flips `book_companies.site_offline`. Unconditionally admin-only, no `manage_org_settings` escape hatch, same posture the publish route (`/api/site-pages/{type}/publish`) takes: this makes or stops making the tenant's public site unreachable for every visitor, a bigger call than the general settings a staff member with that grant can already make. Appointments only, same as every other route under the Website screen: the page itself already redirects a hospitality org away before this control could ever render. Revalidates the public site's cache tag on success, keyed off the company's slug (fetched via the same `.select('slug')` the update itself performs, not a separate read).