Reply to a Google review from this app's own dashboard
/api/google-business/reviews/{reviewId}/replyAdmin-only. reviewId is this app's own book_google_reviews.id (0090), not Google's resource name: the client never needs to learn Google's naming scheme. Scoped by company_id explicitly in the lookup, not by id alone: that table has no SELECT policy for authenticated at all, so this IS the tenant boundary, same shape reviews/page.tsx's own book_feedback read already relies on.
Calls Google's reviews.updateReply (v4; reviews were never migrated off that surface when the rest of the My Business API was decomposed into v1 services; see docs/google-business-profile.md), which creates a reply if none exists or replaces one that does; there is no separate edit endpoint. On success, best-effort mirrors the reply into the cached row so the dashboard shows "already replied" without a live call. A failure there is only stale UI, since the reply already succeeded on Google's side, and the next sync overwrites it either way.
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
Path Parameters
book_google_reviews.id, not the Google resource name.
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
application/json
application/json
curl -X POST "https://example.com/api/google-business/reviews/string/reply" \ -H "Content-Type: application/json" \ -d '{ "comment": "string" }'{ "ok": true}Disconnect this company's Google Business Profile POST
Admin-only. Best-effort revokes the stored refresh token against Google's own `/revoke` endpoint BEFORE deleting the `book_google_business_connections` row (0090): a revoke that fails (already expired, a network blip) must not block the disconnect, or an owner trying to leave would be stuck because of the very connection they're trying to leave. Cached reviews in `book_google_reviews` are left as historical record, not deleted; they simply stop refreshing once the connection is gone.
Upload the booking-page background (admin, or manage_org_settings) POST
Same shape as the logo route, with a 5 MB ceiling instead of 2 MB; a full-bleed background is a bigger picture than a mark. Same gate too: admin, or a staff login granted `manage_org_settings` (migration 0082).