Stave 2.0 · admin.stave.works

Stave 2.0 Guide

How to run a business site on Stave: every screen in the business admin, the tools Stave staff use, each connected service and the API the client sites and partners call.

Admin admin.stave.works Written October 26, 2026 Screens shown Golden Road Vineyards, with people's names and contact details swapped for made-up ones

Getting in

Everyone signs in at admin.stave.works. There is no public sign-up. A Stave admin adds a business and its first owner, and owners invite the rest of their team.

Sign in with a password, or ask for a link by email.
Forgot your password sends a reset link.

Signing in

  • Password. Enter your email and password and press Sign in.
  • Email link. Press Email me a link instead. Stave emails a one-time sign-in link to addresses that already have an account. Unknown addresses get nothing.
  • Forgot your password. Enter your email and Stave sends a reset link that works for one hour. This also works for someone who has never set a password, for example an owner who came across from Stave 1.0.
  • Invitations. An invite email opens /invite/<token>. Someone new enters their name and Stave emails them a sign-in link. Someone with an account already signs in and accepts. Either can add a password later with Forgot your password.

Who can do what

RoleCan do
OwnerEverything for their business, including business info, connected services, the team, site health and text messages.
MemberPages, posts, wines, beers, events, photos and the day-to-day lists. Members can't change business info, connected services or the team.
Stave adminEvery business, plus the staff area at /stave. Admins must set up two-factor sign-in before they can do anything, and they can view the admin as any person (see View as).

After signing in, people land on their business at /b/<slug>, for example /b/grv. Someone who belongs to more than one switches with the business name at the top of the sidebar.

The business admin

The sidebar only shows what a business uses. A Stave admin switches sections (Blog, Wines, Events and so on) and plugins on per business, and screens for anything switched off disappear. Screens marked Owner are hidden from members.

Sidebar groupScreensShown when
TopHome, MessagesAlways
SitePages, Site settings, Redirects, BoardAlways
Review notesReview notes plugin
Site health OwnerSite health plugin
ContentPostsBlog section
Wines · BeersWines section · Beers section
Events, Ticket salesEvents section
ShopShop section
MediaAlways
CustomersContactsAlways
Wine clubClub section
ReservationsVisit or Tasting room section
Text messages OwnerText messages plugin
StayUnits, BookingsStay section
GuidesStay section and Stay guide plugin
BusinessBusiness info, Connected services, Team OwnerAlways, owners only

Home

/b/grv

The first screen after signing in. It shows when the site was last published and how many pages have changes waiting, shortcuts to write a post, add an event, add a wine or upload photos, a Needs your attention list (unpublished pages, new club requests, open review notes, connectors that stopped working), what's coming up and recent activity.

Home. Each item under Needs your attention links straight to the fix.

The whole admin works on a phone. The sidebar folds into the menu button at the top left.

Home at phone width.

Messages

/b/grv/messages

Everything sent through forms on the site. Stave also emails each message to the business. Form settings sets which address gets new messages and what the automatic reply to the sender says.

Messages with nothing waiting.

Pages

/b/grv/pages

Every page on the site with its address, when it was last edited and whether it has unpublished changes. Click a page to edit it. Use New page to add one, and the ⋯ menu on a row to duplicate or delete one.

Pages. Unpublished changes means visitors still see the old version.

Editing a page

/b/grv/pages/<id>

  1. The left column lists the sections on the page (hero, hours, featured products and so on). Drag to reorder, or use Add section. The site header and footer are shared by every page and edited under Site settings.
  2. Click any section in the preview to edit it. Its text becomes editable where it sits.
  3. The right column holds the page settings: name, web address, the title and description search engines show, and a preview of how the page looks on Google.
  4. Changes save as a draft automatically. Use the desktop, tablet and phone buttons at the top to check sizes, and undo or redo from the same bar.
  5. Press Publish when it's ready. Nothing changes on the live site until then.
The page editor. The live preview loads the client's own site, so in this local copy it shows a placeholder.

Site settings

/b/grv/site

The header menu, footer and other settings shared by every page. Reorder menu items with the arrows, open one to change its label or link, then Save draft or Publish.

Site settings.

Redirects

/b/grv/redirects

Send an old address to its new home, so old links and search results keep working. Add one when a page moves or a new site replaces an old one. Choose permanent (308) for moves that stick and temporary (307) for anything short-lived. Stave applies them on the business's own domains, and client sites hosted elsewhere look them up through the redirects API.

Redirects.

Review notes

/b/grv/notes

Comments pinned to a spot on a page, shared between the business and Stave while a site is being reviewed. Open the site while signed in and use the Review button to drop a note on any element. Notes have a kind (design, copy, bug, question, idea), a priority and a status (open, in progress, resolved). Visitors never see the button or the pins.

Review notes, filtered by status.

Site health Owner

/b/grv/health

Scores for how the site does with AI assistants, search engines and speed, each with the findings behind it: what needs a fix and what's already working. A Stave admin sets how often the checks run.

Site health.

Board

/b/grv/board · TV address /t/grv/board

The menu for the TV in the tasting room. Open the TV address in the TV's browser and leave it. It updates itself and never needs a login.

  • What shows: everything that's pouring, or only the drinks you pick.
  • Tastings and flights: board-only items that aren't wines or beers.
  • Banner: one line that scrolls across the top. Leave it empty for no banner.
  • Prices and sizes: select several drinks and give them the same sizes and prices in one step.
Board settings with a live preview of the TV.

Posts, wines and beers

Posts

/b/grv/posts

The blog. Tabs split drafts, scheduled and published posts. Write a post opens the editor.

Posts.
  • Set a cover photo, a title and the body. The toolbar handles headings, bold, italic, quotes, lists and links, and can drop in a wine, an event or a photo.
  • The side panel holds the web address, categories, a short summary (160 characters for search results), the byline and a Google preview.
  • Publish now, or pick a time from the publish menu. Scheduled posts go live at the top of the next hour after their time.
The post editor.

Wines

/b/grv/wines

The wine list. Changes show on the site right away, with no publish step. Prices and stock also feed the shop. Use the arrows to set the order wines appear in, and the status to mark a wine on the menu or sold out. When Vinoshipper is connected, its catalog sync keeps wines in step.

Wines.

Beers

/b/grv/beers

Guest beers poured in the tasting room, grouped by style. They show on the TV board and the tasting room menu and aren't sold online. The toggle on each row puts it on or takes it off the board. Breweries using Untappd can sync their menu instead of typing it.

Beers.

Events and ticket sales

Events

/b/grv/events

Upcoming and past events. With Facebook Events connected, events from the Facebook Page come in every 8 hours, including changes and cancellations. Check Facebook now pulls them immediately. Add an event creates one by hand.

Events. From Facebook marks imported ones.

Ticket sales

/b/grv/tickets

Tickets sold, ticket money, events on sale and orders waiting for payment, then every upcoming event with its sales. Paid tickets need Stripe or Square connected. Without one, guests can only get free tickets.

Ticket sales. The banner offers to connect Stripe.
  1. Press Set up tickets on an event.
  2. Add a ticket type with a name, a price (zero for free RSVPs), a description and how many are available.
  3. The Sales card shows whether guests can buy. Turn sales off stops new orders without hiding the event.
  4. The Guests tab lists ticket holders, and Orders lists orders with refunds.
  5. On the night, staff open /scan on a phone, signed in, and scan each ticket's QR code to check guests in.
One event's tickets.

Guests buy on /t/grv/tickets/<event> and get an order page, a ticket page per ticket with a QR code, and an Apple Wallet pass when Wallet is configured.

Shop and media

Shop

/b/grv/shop

What the site's shop sells and where orders go. For a winery on Vinoshipper, guests pick wines on the Shop page and check out in Vinoshipper's cart. Vinoshipper takes the payment, checks age and ships, and Stave doesn't get a copy of those orders. Sync now pulls the catalog. Event tickets are separate and live under Ticket sales.

Shop.

Media

/b/grv/media

Upload once and use anywhere on the site. Drop JPG, PNG or WebP files up to 4.5 MB on the page or press Upload. Stave resizes them for fast loading. Filter by photos or bottles, and use Needs a description to find images missing alt text.

Media.

Contacts and wine club

Contacts

/b/grv/contacts

Everyone who signs up on the site lands here, and Add contact adds someone by hand. Search by name, email or phone and filter by where they signed up or email status. The Texts column shows who opted in to text messages. Text opt-in needs a phone number and a date of birth showing the person is 21 or older.

Contacts.

Wine club

/b/grv/club

Three tabs: Requests from people asking for a place on the list, Members and Tiers. Changing a request's status (new, invited, declined) sends nothing, so write to the person yourself. Members join a tier through the site and get a private page at /club/manage/<token> to pause, skip a shipment, switch between pickup and shipping, update their address or cancel.

Wine club requests.

Reservations

/b/grv/reservations · settings /b/grv/reservations/settings

Tasting room and visit reservations made from the site, with party size and notes.

Reservations.

Open Reservation settings to take bookings:

  • Take reservations on your site turns the booking page on. When it's off, the page tells people reservations aren't open.
  • Confirm right away confirms and emails the guest while the time has room. Turn it off to review each one as a request.
  • Largest party the form accepts. Bigger groups are told to get in touch.
  • Days and times sets which slots can be booked and how many seats each holds. Nobody can reserve until at least one exists.
Reservation settings.

Guests get a confirmation email with a link to add the visit to their calendar, and a reminder email on the morning of the visit.

Text messages Owner

/b/grv/sms

Marketing texts sent from the business's own Telnyx account and number, only to people who opted in. A Stave admin turns on the Text messages plugin first. That adds this screen and unlocks the Telnyx connector.

Before Telnyx is connected, the screen points to Connected services.
  1. Connect Telnyx (see Setting up Telnyx).
  2. Press New text. Give it a name, write the message and check the preview.
  3. Under Who gets it, pick contact tags or everyone. Only people who opted in are counted.
  4. Leave Schedule empty to send now, or pick a time. Press Schedule, or Save draft to finish later.
  5. Stave sends due texts every five minutes and keeps per-campaign delivery counts. STOP and HELP replies are handled for you.

With the plugin off, saving the Telnyx connector and sending campaigns are both refused, but STOP and HELP replies are still honored.

Stays and guest guides

Units

/b/grv/units

The places guests can book, with photos, sleeping details, rates and rules for the stay page. Each unit has a booking link. If it's set, the site's Book button sends guests there (Airbnb, VRBO). Clear it and guests use Stave's own request form.

Units.

Bookings

/b/grv/bookings

A month calendar per unit showing stays booked through Stave, requests that need a reply and stays pulled in from outside calendars. Stave reads each unit's Airbnb or VRBO calendar every two hours, and publishes its own feed at /api/stays/ical/<token> so those sites can block the same dates. Add booking enters one by hand.

Bookings.

Guides

/b/grv/guides

What guests read once they've booked: door code, wifi, house manual and local favorites. Each guide has a private link (/g/<token>) that never changes, so a printed QR code keeps working. Only people with the link can see it.

Guides.

Business info and team

Business info Owner

/b/grv/business

Hours, address, phone, email and social links. These show on every page of the site, in the business's Google listing data and in what AI assistants say about it. Hours lines can be days, date ranges or words like "By appointment". Press Save when done.

Business info.

Team Owner

/b/grv/team

People who can sign in to this business. Invite someone emails an invite as owner or member. Pending invites can be resent or revoked, and existing people can be changed between roles or removed. A business always keeps at least one owner.

Team.

Connected services Owner

/b/grv/connectors

Link the outside services a business already uses. Only owners and Stave staff can open this screen. Keys and tokens are stored encrypted and can never be read back. Leaving a key field empty keeps the saved one.

Connected services, grouped by what each one does.

Services fall into three groups:

  • Pulls your information in: Facebook Events, Untappd, Vinoshipper, Telnyx and Square. Each has a sync that runs on a schedule and a button to run it now.
  • Works on your site: Stripe and Google Analytics, used directly by pages and checkout.
  • Saves your keys only: Google Business Profile, Mailchimp, Commerce7, WineDirect, Toast, Arryved and Ollie. You can save keys and mark these on, but Stave doesn't talk to them yet.

Setting up any service

  1. Press Set up on the service. A panel opens on the right.
  2. Fill in the fields. Each one says where to find the value in the other service.
  3. Tick the capabilities you want, where the service has them.
  4. Turn it On and press Save.
  5. Press Check credentials to test the keys, then Sync now for services that sync. The row shows when it last synced and why, if it failed.

Turning a service off stops it syncing and being used, but keeps the saved details. Disconnect deletes them.

Vinoshipper's panel.
Stripe's panel.

Setting up Telnyx

Telnyx sends the business's marketing texts from its own account, so Stave never pays for or owns the number.

  1. Stave turns on Text messages. A Stave admin opens the business under Businesses, Sections and plugins, and switches on the Text messages plugin. Telnyx then appears under Connected services.
  2. API key. Telnyx portal, Account, API keys. Paste it into API key.
  3. Messaging profile. Telnyx portal, Messaging, Messaging profiles. Copy the ID of the profile the number belongs to.
  4. Send from. With the key saved, press Load list and pick the number, or type it as +1XXXXXXXXXX.
  5. Webhook public key. Telnyx portal, Account, Keys and credentials, Public key. Without it Stave refuses STOP replies, because anyone who found the webhook address could otherwise opt contacts in and out.
  6. 10DLC campaign ID. The registered campaign the number sends under. US carriers quietly filter unregistered marketing texts, so a send can look successful and never arrive. Registration happens in Telnyx.
  7. Webhook address. In the Telnyx messaging profile, set the inbound webhook to https://admin.stave.works/api/connectors/telnyx/webhook/<business id>. Golden Road's business id is 4.
  8. Tick Marketing campaigns, turn it On, Save, then press Check connection. It warns about a missing 10DLC campaign ID, a number that isn't active or a missing messaging profile.
Telnyx's panel.

Outside production, Stave only texts numbers listed in SMS_ALLOWLIST, so tests on staging can't reach real customers.

Every service

Facebook Events Syncs every 8 hours

Upcoming events on the business's Facebook Page import automatically and stay in step, cancellations included. Press Connect with Facebook and approve access. Stave stores the Page token.

FieldWhere to find it
Page IDFilled in when you connect. If the Facebook account manages several Pages, set the ID of the one to import from.

Untappd Syncs every 8 hours

Menus sync into Wines, Beers, Ciders and Meads and stay up to date.

FieldWhere to find it
Account emailThe Untappd for Business login the API token belongs to.
API token sealedUntappd for Business, Account settings, API access. A read-only token works.
Menu IDOptional. Sync only this menu. Leave empty to sync every menu on the account.

Vinoshipper Syncs every 8 hours

Compliant wine shipping. The Account ID powers the Vinoshipper block on pages, and an API key adds the catalog sync.

FieldWhere to find it
Account IDVinoshipper, Account, Integrations, Vinoshipper Injector. It's the number in the init() line of the snippet. This alone powers the page block.
Tasting menu catalog IDOptional. Vinoshipper, Products, Catalogs. Wines in this catalog are marked as poured in the tasting room.
API key, API secret sealedOptional. Only some Vinoshipper plans offer them.

Capabilities: Product catalog (copies the wine list into products every 8 hours). Inventory, orders, customers, clubs and shipping compliance checks are listed as coming later.

Telnyx Check on demand Needs Text messages plugin

Marketing texts from the business's own Telnyx account and number. Powers campaigns under Text messages and passes STOP and HELP replies back to the contact. Setup steps are above.

FieldWhere to find it
API key sealedTelnyx portal, Account, API keys.
Messaging profileTelnyx portal, Messaging, Messaging profiles.
Send fromA number on the account. Load the list or type +1XXXXXXXXXX.
Webhook public keyTelnyx portal, Account, Keys and credentials, Public key.
10DLC campaign IDThe registered campaign the number sends under.

Capabilities: Marketing campaigns. Order and booking texts are listed as coming later.

Square Syncs every 8 hours

Point of sale, payments and stock. Takes ticket payments and keeps product availability matched to the register. Products are matched to Square items by name on the first sync.

FieldWhere to find it
Access token sealedSquare Developer dashboard, the app's credentials.
Application IDSame page. An ID starting sandbox- uses Square's test mode.
Location IDThe location whose stock counts drive availability on the site.

Capabilities: Card payments and Catalog and stock sync (reads from Square only). Outside production Stave refuses a production Square application.

Stripe Works on your site

Card payments for tickets and orders, paid straight into the business's own Stripe account. The usual route is the Connect Stripe button on Ticket sales, which runs Stripe's own onboarding and fills in the account ID.

FieldWhere to find it
Secret key sealedStripe dashboard, Developers, API keys. A test key uses Stripe's test mode for a fake payment end to end.
Publishable keySame page. Must pair with the secret key.
Connected account IDFilled in by Stripe onboarding. Only paste one when moving an account across from elsewhere.

Google Analytics Works on your site

Page views, traffic sources and conversions in the business's own Google Analytics property. The ID is passed to the client site through the site content API.

FieldWhere to find it
Measurement IDGoogle Analytics, Admin, Data streams, the web stream for the site. It starts with G-.

Saves keys only Not used yet

These store credentials so they're ready when Stave starts using them. Nothing syncs.

ServiceForFields
Google Business ProfileHours, reviews and mapPlace ID
MailchimpEmail and newsletter marketingAPI key, Audience ID
Commerce7Winery sales and customer recordsTenant ID, App ID, Secret key
WineDirectWinery online store and fulfillmentAPI key
ToastRestaurant and taproom point of saleClient ID, Client secret
ArryvedBrewery and taproom point of saleAPI key
OllieBrewery management and productionAPI key

Pages guests use

The client's own website (grvwines.com for Golden Road) shows the content. A few guest pages are served by Stave itself, and the client site links to them.

AddressWhat it is
/t/<slug>/reservationsBook a tasting or visit.
/t/<slug>/tickets/<event>Buy tickets for one event.
/t/<slug>/stay/bookRequest a stay.
/t/<slug>/boardThe tasting room TV menu.
/order/<number>An order and its tickets.
/ticket/<code>One ticket with its QR code and Wallet pass.
/club/manage/<token>A club member's own page.
/g/<token>A guest guide.
stave.works/reports/<slug>A published audit report, behind its access code. Add /present for the slideshow.
/scanStaff ticket check-in. Needs a signed-in team member.
Buying tickets.
Requesting a stay.
The reservations page while reservations are switched off.

Stave staff tools

Stave admins get a second sidebar at /stave: Overview, Businesses, Users, Change log, Audits and Leads. Two-factor sign-in is required before any of it opens.

Overview: live and paused sites, audits waiting, new leads and users.

Businesses

/stave/businesses

Every business with its domain, theme, sections and status. Open as owner jumps into its admin. Add a business asks for a name, slug, domain and theme.

Businesses.

Opening a business shows six tabs:

TabWhat it's for
Sections and pluginsWhich screens the business sees and which pages its site has. Changes apply as soon as you flip them.
DomainsThe domains the business owns. Redirects and domain lookups use these.
PeopleOwners and members, pending invites, and View as for each person. Owners can invite and remove their own people, so staff only step in when nobody at the business can sign in.
SettingsBusiness name, site status, the Vercel project ID and how often AI audits run (off, weekly, fortnightly, monthly or quarterly).
AI runsAudit runs for this business: queued, running, done, failed or cancelled.
ThemeMinimal, Contour or Golden Road.
Sections.
Plugins, including Text messages.
People.
Settings.
PluginAdds
Review notesPinned page notes while a site is being reviewed.
Site healthSpeed and AI visibility reports for the owner.
Stay guideGuest guides for stays.
Text messagesThe Text messages screen and the Telnyx connector.

Users and View as

/stave/users

Everyone who can sign in, the businesses they belong to, their Stave role, whether two-factor is on and when they last signed in.

Users.

View as opens the admin exactly as that person sees it, with their role and sidebar, so you can reproduce a problem they report. A banner stays at the top while you're viewing. Stop viewing returns you to your own account.

Viewing as an owner. The banner names who and where.

Every change made while viewing as someone is recorded in the Change log (/stave/log) with who did it, who they were viewing as, the business, the change and a diff.

Change log.

Audits and leads

/stave/audits · /stave/leads

Audits lists AI visibility and site speed reports. Run an audit starts one for a business. Nothing goes to the business until you publish it. Published reports are served at stave.works/reports/<slug> behind an access code. Reports produced on another machine are uploaded through the audit import API.

Leads come from the free audit and the contact form on stave.works. Set each one's stage as it moves along.

Audits.
Leads.

API

All endpoints live on the admin host, https://admin.stave.works/api/…. Requests and responses are JSON unless noted. Errors come back as { "ok": false, "error": "…" } with a matching status.

How calls sign in

KindHowUsed by
PublicNo credentials. Form endpoints have per-IP rate limits and a bot check.Guest pages and client sites
Signed linkA token or signature in the URL is the credential.Tickets, iCal feeds, club pages
SessionThe signed-in admin cookie, plus the business slug as tenant. Many need the owner role.The admin itself
Site secretAuthorization: Bearer $SITE_CONTENT_SECRETClient sites, server side
Cron secretAuthorization: Bearer $CRON_SECRETVercel cron
Maintenance secretAuthorization: Bearer $MAINTENANCE_SECRETThe audit runner
Audit import tokenAuthorization: Bearer $AUDIT_IMPORT_TOKENAudit uploads
Webhook signatureStripe's stripe-signature, Telnyx's ed25519 headersStripe, Telnyx

For bearer endpoints, a wrong secret returns 401 and an unset one returns 404, so a missing environment variable doesn't look like a working endpoint.

Public forms use two bot fields. hp is a hidden honeypot that must stay empty. t is the time the form was shown, in milliseconds since the epoch, and a submit under 2.5 seconds later is treated as a bot. A caught request gets a fake success so bots learn nothing.

Site content

GET /api/site-content?tenant=<slug>[&draft=1]

The client site's whole content in one call: business details, site settings, pages, products, events, posts, categories and units. The client site fetches this server side and renders it in its own design.

Auth
Bearer SITE_CONTENT_SECRET (32 characters or more). The secret is shared by all businesses, and tenant picks one.
Query
tenant (required) and draft=1 for unpublished content, used by the client's preview route. There are no per-collection filters.
Limit
20 requests a minute per caller, checked before auth. A full crawl of a client site in development can trip it.
Returns
ok, version, draft, generatedAt, tenant, vinoshipperAccountId, googleAnalyticsId, site, pages, products, events, posts, postCategories, units, blockRelations, plus media (newest 1,000) when draft=1. Sent with no-store and noindex.
Errors
400 no tenant, 401 wrong secret, 404 unknown or inactive business, 429 over the limit.
curl -s "https://admin.stave.works/api/site-content?tenant=grv" \
  -H "Authorization: Bearer $SITE_CONTENT_SECRET"

A client site needs STAVE_CONTENT_URL, STAVE_CONTENT_SECRET, STAVE_TENANT_SLUG, STAVE_ADMIN_ORIGIN and NEXT_PUBLIC_MEDIA_ORIGIN.

GET /api/media/file/{path…}

Serves uploaded images and files. Public.

GET /api/redirects?host=&path=

The same redirect lookup Stave runs on every request, for callers that ask over HTTP. Public, cached for 60 seconds. Returns { found: true, to, permanent } or { found: false, hasRedirects }.

GET /api/health

Returns { ok: true }.

Forms and sign-ups

POST /api/forms/submit

Submits a form built in the admin. Saves it to Messages, emails the business and sends an auto-reply when a field held an email address. Public, 10 per 10 minutes per IP. t is required here.

Body
formId (number), values (object keyed f0, f1… by field position), t, hp
Returns
{ ok: true, message }, the form's thank-you text
POST /api/intake/newsletter POST /api/intake/club-request

Newsletter and club list sign-ups from a client site's own forms. Called server side with the site secret. Rate limited to 5 per 10 minutes per business and email.

Auth
Bearer SITE_CONTENT_SECRET
Body
tenant, email (required), name (required for club requests), phone, smsConsent (needs phone and dob as YYYY-MM-DD, 21 or older), sourceDetail, smsConsentSource, smsConsentIp, note (club requests, up to 1,000 characters), hp, t
Returns
{ ok: true }. The contact is created or updated, and a club request is stored as well.
POST /api/club/join

Joins a club tier. Public, 5 per 10 minutes per IP.

Body
tierId, name, email (required), tenant, phone, fulfillment (pickup or ship), shipping {line1, line2, city, state, postalCode}, notes
Returns
{ ok: true, portalUrl } for a new member, or { ok: true, alreadyMember: true } with the link emailed instead
POST /api/club/manage

A member's own changes. The portal token in the body is the credential.

Body
token, action: pause (optional pausedUntil), resume, skip, unskip, fulfillment, address (shipping), notes, cancel
Returns
{ ok: true, member }; 409 when the membership is closed

Bookings and orders

MethodPathAuthWhat it does
GET/api/reservations/availability?tenant=<id>&date=YYYY-MM-DDPublicSlots for one day: { slots: [{ time, capacity, remaining }], maxParty }. tenant is the numeric business id.
GET/api/reservations/month?tenant=<id>&month=YYYY-MMPublicDates in the month that are closed or full.
POST/api/reservations/requestPublic, 5 per 10 minBody tenantId, date, time, partySize, name, email, optional phone, notes. Returns { confirmed, reservationId } and emails the guest.
GET/api/reservations/ics/{id}?sig=Signed linkCalendar file for one reservation.
GET/api/stays/availability?unit=<id>PublicBusy date ranges for a unit: { unit, busy: [{ from, to }] }.
POST/api/stays/bookPublic, 5 per 10 minBody unitId, checkIn, checkOut, name, email, optional partySize, notes. Returns { bookingId, nights }.
GET/api/stays/ical/{token}Signed linkA unit's calendar feed for Airbnb or VRBO to read.
POST/api/ordersPublic, 10 per 10 minTicket order. Body eventId, customer: { name, email }, lines: [{ ticketType, quantity }]. Free orders return { paid: true, orderNumber, orderUrl, tickets }; paid ones return { paid: false, orderNumber, provider, clientData } for the card form.
POST/api/tickets/check-inSessionBody code. Returns the ticket type, event and holder; 409 if void or already checked in.
GET/api/tickets/qr/{code}Signed linkThe ticket's QR code as PNG.
GET/api/tickets/wallet/apple/{code}Signed linkAn Apple Wallet pass. 503 when Wallet isn't configured.

Payments

MethodPathAuthWhat it does
POST/api/payments/stripe/confirmPublic, rate limitedBody orderNumber. Confirms a Stripe payment and returns { orderUrl }.
POST/api/payments/square/confirmPublic, rate limitedBody orderNumber, token (the card token from Square's form). Charges and returns { orderUrl }.
POST/api/payments/stripe/connectSession, ownerBody tenant. Starts Stripe onboarding and returns { accountId, url }.
GET/api/payments/stripe/connect?tenant=Session, ownerOnboarding status: configured, connected, accountId, chargesEnabled, payoutsEnabled, outstanding.
POST/api/payments/stripe/webhookStripe signatureStripe events. The address is saved in Stripe, so it must not move.

Confirm endpoints allow 30 calls per 10 minutes per IP and 10 per order.

Connectors

All of these except the webhooks and the Facebook callback need an owner's session, with the business slug as tenant in the body (or the query for GET).

MethodPathWhat it does
GET/api/connectors/facebook/oauth?tenant=Sends the owner to Facebook to approve access.
GET/api/connectors/facebook/callbackFacebook sends the owner back here. Saves the Page token and returns to Connected services.
POST/api/connectors/{facebook|square|untappd|vinoshipper}/syncSyncs one business now. The same paths answer GET for the cron.
POST/api/connectors/{square|untappd|vinoshipper|telnyx}/verifyTests saved credentials. Returns a location, a location count, a product count or a number count.
POST/api/connectors/telnyx/numbersLists the account's numbers for the Send from picker. Optional config.apiKey tests a key before saving.
POST/api/connectors/telnyx/webhook/{businessId}Inbound texts from Telnyx, verified with the saved webhook public key. Handles STOP and HELP.
GET/api/sms/audience?tenant=&tags=How many opted-in contacts a campaign would reach.
POST/api/sms/send-dueBody tenant, campaignId. Sends one campaign now. GET on the same path is the cron.

Admin and machine

MethodPathAuthWhat it does
POST/api/media/uploadSessionMultipart tenant, file, alt. Up to 4.5 MB.
GET POST/api/annotateSessionList or add review notes for a page. Business from ?tenant=. Body path, note, kind, priority, pageTitle, anchor, viewport.
PATCH DELETE/api/annotate/{id}SessionChange a note's status, kind, priority, note or add a reply, or delete it.
POST/api/monitoring/runStave adminBody tenant (numeric id). Starts an audit run and returns runId.
GET/api/monitoring/runs/{id}Stave adminOne run's status, modules, recent events and scores.
GET/api/monitoring/dueMaintenance secretBusinesses with an audit run requested, for the runner.
POST/api/audits/importAudit import tokenUploads a finished audit report bundle. Body bundle (or the older flat fields), tenantSlug, slug, accessCode, replace. Returns { slug, accessCode, url }.
GET POST/api/auth/{path…}variesSign-in, sign-out, password reset, email links and two-factor, handled by better-auth.

Scheduled jobs

Vercel cron calls these with CRON_SECRET. Times are UTC.

PathScheduleWhat it does
/api/sms/send-dueEvery 5 minutesSends text campaigns whose time has come.
/api/cron/publish-scheduledHourly, on the hourPublishes scheduled posts.
/api/stays/sync-icalEvery 2 hoursReads Airbnb and VRBO calendars into Bookings.
/api/connectors/facebook/syncEvery 8 hoursImports Facebook events.
/api/connectors/vinoshipper/syncEvery 8 hoursSyncs Vinoshipper catalogs.
/api/connectors/untappd/syncEvery 8 hoursSyncs Untappd menus.
/api/connectors/square/syncEvery 8 hoursSyncs Square stock.
/api/reservations/remindDaily, 13:00Emails reminders for the day's confirmed reservations (9 am Eastern in summer, 8 am in winter).

Each sync covers every active business with that connector turned on, and records the result on the connector's row in Connected services.

Running Stave

ProductionStaging
Addressadmin.stave.worksstave-git-stage-onenomad.vercel.app
Branchmainstage
Vercel projectstavestave, preview for the stage branch
STAVE_ENVproductionstaging
EmailSends normallyOnly to addresses in EMAIL_ALLOWLIST
TextsSends normallyOnly to numbers in SMS_ALLOWLIST
PaymentsLive keysStripe and Square test keys only. Live keys are refused.

Code flows development → stage → main through pull requests on OneNomad-LLC/stave-cms-2. Every other branch is skipped by Vercel. Database migrations run as part of each build.

Environment variables

Names only. Values live in Vercel and the password vault.

VariableFor
DATABASE_URL, DATABASE_URL_UNPOOLEDPostgres (Neon)
BETTER_AUTH_SECRET, BETTER_AUTH_URLSign-in sessions and the admin's own address
CONNECTOR_SECRET_KEYEncrypts saved connector keys. Changing it makes every saved key unreadable, and owners have to enter them again.
SITE_CONTENT_SECRETClient sites reading content and sending sign-ups
CRON_SECRET, MAINTENANCE_SECRET, AUDIT_IMPORT_TOKENCron, the audit runner and audit uploads
STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRETStripe Connect and its webhook
RESEND_API_KEYEmail
BLOB_READ_WRITE_TOKENUploaded media
FACEBOOK_*The Facebook app used for Events
REVALIDATE_*Telling client sites to refresh after a publish. Not set on staging.
AUTH_COOKIE_DOMAINShares the sign-in cookie across .stave.works
STAVE_ENV, EMAIL_ALLOWLIST, SMS_ALLOWLISTWhich environment this is, and who staging may contact