Save a page's draft content (admin, or manage_org_settings)
/api/site-pages/{type}Writes draft_content ONLY; the live site (published_content) never moves until POST .../publish. book_site_pages (migration 0112), upserted rather than updated because a company that has never had this page edited before has no row yet. Gated the same way /api/organization/hours gates general org settings: admin, or a staff member holding manage_org_settings (0082). What a page SAYS is business-profile-shaped work, not a daily-operations action every staff login should get by default.
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
Which of the seven site pages (SITE_PAGE_TYPES). Anything else is a 404.
Value in
- "home"
- "services"
- "about"
- "team"
- "gallery"
- "reviews"
- "contact"
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 PATCH "https://example.com/api/site-pages/home" \ -H "Content-Type: application/json" \ -d '{ "content": { "visible": true, "heading": "string", "intro": "string", "body": [ { "heading": "string", "paragraphs": [ "string" ] } ], "order": 0, "showOnHome": true, "textMotion": "string", "metaTitle": "string", "metaDescription": "string", "noIndex": true } }'{ "ok": true}Replace the venue opening hours (admin, or manage_org_settings) PUT
PUT, not PATCH: the editor holds the whole week and a window has no stable identity in it, so "these are the hours" is the only honest request shape. **An empty array means NO VENUE RESTRICTION, not "closed all week"**: zero rows has to keep meaning unclamped, because that is the state every org is in until it saves hours here, and any other reading would take every existing booking page to zero availability. Delete-then-insert over PostgREST is two transactions; if the insert fails the org is left with no rows, which fails permissive (staff hours alone apply) and is reported as such. Gated to admin, or a staff login granted `manage_org_settings` (migration 0082).
Publish a page's draft content live (admin) POST
Copies `draft_content` over `published_content` and stamps `published_at`. Unconditionally admin-only, no `manage_org_settings` escape hatch (unlike the save route above): migration 0112 grants UPDATE on `draft_content`/`updated_at` only, deliberately never on `published_content`, so this is the ONLY place "publish" is actually enforced. A company that never touched this page's draft gets a clean 200 no-op: the live page already renders its generated default and there is nothing to copy forward.