Request beta access (public)
/api/beta/request-accessThe public entry point for the beta-access gate (book_beta_access_requests/book_beta_codes, migration 0188): POST /api/onboarding/complete refuses to create a brand new tenant without a valid, unredeemed code, and this is how a prospect asks for one. Rate-limited (beta-request: bucket, 5/min-window/IP via the same consumeBucket() the guest booking surface uses) and bot-checked (guestBotCheck(), registered in bot-paths.ts). Always answers {ok:true} regardless of outcome, whether fresh, a resubmission of an already-pending request (23505 on the partial-unique index, swallowed), or auto-approved, so the response can never be used to enumerate which emails have already asked or which auto-approve rules exist.
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
application/json
application/json
application/json
application/json
curl -X POST "https://example.com/api/beta/request-access" \ -H "Content-Type: application/json" \ -d '{ "email": "user@example.com", "name": "string", "vertical": "appointments", "industry": "string" }'{ "ok": true}Create an invited account (public) POST
The "create account and join" half of the `/invite/{token}` form, no session required, because the visitor is about to establish their first one. Creates the account with its email already confirmed, then the browser signs in with the same password and goes on to POST `/api/invites/accept`. Skipping the confirmation email is the reason this route exists rather than a shortcut taken around one: holding the token means having opened the message sent to the invite's address, so it is already proven, on the same bearer-capability reasoning that lets `/invite/{token}` be viewed with no auth and lets resend be honoured on a token alone. A second confirmation round-trip proves nothing the token did not. The address is read off the invite row and never off the body; a token is authority over exactly one address, and honouring a caller-supplied one would turn a leaked invite into a create-an-account-anywhere primitive. Replaced a browser-side `supabase.auth.signUp()`, which could not work here: with confirmations enabled GoTrue answers an already-registered address with a session-less 200 and no error at all (enumeration protection) and sends no mail, so the form's only remaining branch was to strand a returning invitee on "check your email" for a message that was never coming. The admin API does not obfuscate, which is what makes the 409 below possible.
Sign up for Gaplessly product updates (public) POST
The marketing footer's newsletter sign-up, double opt-in with all state in Resend (lib/marketing/newsletter.ts): a new address becomes a Resend contact with `unsubscribed: true` and is mailed a confirmation link carrying the contact id; nothing is subscribed until `POST /api/newsletter/confirm`. A still-pending address gets the link again (Resend's own idempotency key limits that to once per 24h); a confirmed subscriber is sent nothing. Rate-limited (`newsletter:` bucket, 5/min-window/IP) and bot-checked (`guestBotCheck()`, registered in `bot-paths.ts`), since it mails whatever address it is given. Answers `{ok:true}` for new, pending and confirmed addresses alike, so it cannot be used to learn who is on the list. A form-encoded body (the footer form before hydration) gets a 303 to `/newsletter/confirm?status=sent` instead of JSON.