Kiosk, gallery and tracking
No credential, rate limited instead.
Registration
Section titled “Registration”GET /events/:eventSlug/sets/:setSlug
Section titled “GET /events/:eventSlug/sets/:setSlug”What a kiosk loads from the two slugs in its URL.
200 { set: { id, slug, name }, event: { id, slug, name } }
404 { "error": "no such set" }
POST /sets/:setId/sessions
Section titled “POST /sets/:setId/sessions”Creates the active session the booth picks up.
{ "guests": [ { "name": "Alex Reyes", "email": "alex@example.com" }, { "name": "Sam", "phone": "0412 345 678" } ]}1–12 guests. One name each, stored as first word and rest. At least one of
email or phone, since that is how the gallery link reaches them; phones are
normalised to E.164, defaulting to a US country code. A guest who has been to an
event before links to the same customer on email, then phone, so a placeholder
address merges two people.
201 { id, label, status: "active" }. The gallery is GALLERY_URL/<id> once live.
400 { "error": "This doesn't look like a phone number: 04123" } or { "error": "Sam needs an email or a phone number" } — both written to show the guest as they are.
404 { "error": "no such set" }
Galleries
Section titled “Galleries”A gallery is a completed session with photos. Until then these 404.
GET /gallery/events/:slug
Section titled “GET /gallery/events/:slug”The event page: its sets, each with its latest 12 live sessions, newest first. No paging.
{ "id": "7Kq2mZ0aBx91Tc", "slug": "city-launch", "name": "City Launch", "startAt": 1758499200000, "sets": [ { "id": "Rd8vN3pLw0Ymqe", "slug": "set-1", "name": "Set 1", "sessions": [ { "id": "Mv6xC1kRp8Wdzn", "label": "Alex and Sam", "createdAt": 1758501330000, "photos": [ { "id": "Yn2fB6jWc4Qaol", "url": "https://cdn.theloungebooth.com/sessions/Mv6xC1kRp8Wdzn/Zp3nT8wQ1eLk52.jpg", "width": 2048, "height": 2560 } ] } ] } ]}404 { "error": "no such event" }, also for a private event. Private hides the
index, not the session links.
GET /gallery/:sessionId
Section titled “GET /gallery/:sessionId”200 { id, label, event: { id, slug, name, startAt }, photos: [{ id, url, width, height }] } in gallery order.
404 { "error": "no such gallery" } while active, cancelled, or without photos.
Photo urls are on cdn.theloungebooth.com, the bucket’s own domain, cached at
the edge. Rendering one is not counted as a download.
GET /gallery/:sessionId/photos/:photoId/download
Section titled “GET /gallery/:sessionId/photos/:photoId/download”200 the bytes, Content-Disposition: attachment, filename from the label
(Alex and Sam 1.jpg). Counted as a download.
404 plain text.
GET /gallery/:sessionId/zip
Section titled “GET /gallery/:sessionId/zip”200 every photo as one stored zip, streamed as it is built. Counted as a zip.
404 plain text. 429 over 60/min.
Tracking
Section titled “Tracking”POST /track
Section titled “POST /track”Views and share clicks. Downloads and zips count themselves in the routes above.
text/plain with a JSON body, so sendBeacon posts without a preflight and
survives the page closing.
navigator.sendBeacon( "https://api.theloungebooth.com/track", JSON.stringify({ kind: "view", sessionId: "Mv6xC1kRp8Wdzn" }),){ "kind": "view", "sessionId": "…" }or{ "kind": "view", "eventId": "…" }{ "kind": "share", "sessionId": "…", "channel": "facebook" }—facebook,instagram,tiktok,copyornative
204 counted, no body.
400 { "error": "bad body" } or { "error": "no such session or event" }.
Rows carry kind, channel, event, session and a timestamp. Nothing about the visitor: no IP, no user agent, no cookie.
Contact form
Section titled “Contact form”POST /contact
Section titled “POST /contact”Labels are checked against the published Sanity contact page, so this is not a general mailer.
{ "fields": { "Name": "Alex Reyes", "Email": "alex@example.com", "Message": "…" } }24 fields, 2,000 characters each, 16 KB total.
200 { "ok": true }
400 { "error": "Unknown fields: Company" } or { "error": "Invalid submission" }
413 { "error": "Message too long" }
502 / 503 Resend refused, or the keys are unset.
Photo bytes in local dev
Section titled “Photo bytes in local dev”GET /photos/* reads from the simulated bucket and PUT /photos/* is what a
presigned URL points at with no R2 credentials to sign against. A deployed
Worker 404s the PUT. In production, photo URLs come back on the gallery
responses, on cdn.theloungebooth.com.