Skip to content

Booth API

Everything the booth does goes through the routes on this page, each called with your API key. The photos themselves never pass through the API: you upload them straight to storage on signed URLs it gives you.

A session reaches the booth one of two ways. Usually guests register at our kiosk and the session waits in the set’s queue for you to pick up. At an event without a kiosk, the booth takes the guests’ details itself and creates the session. From there the flow is the same: upload, then complete.

  1. Find your set. List the events running now and pick the set (station) this booth is working.

    Terminal window
    curl -sS https://api.theloungebooth.com/events \
    -H "Authorization: Bearer $TLB_API_KEY"
    {
    "events": [
    {
    "id": "7Kq2mZ0aBx91Tc",
    "slug": "city-launch",
    "name": "City Launch",
    "startAt": 1758499200000,
    "endAt": null,
    "venueMode": false,
    "sets": [
    { "id": "Rd8vN3pLw0Ymqe", "slug": "set-1", "name": "Set 1" },
    { "id": "Jf4bQ7tHs2Xzvi", "slug": "set-2", "name": "Set 2" }
    ]
    }
    ]
    }

    Keep the set’s id. Every later call starts from it.

  2. Get a session. Either take the oldest group waiting in the queue:

    Terminal window
    curl -sS https://api.theloungebooth.com/sets/Rd8vN3pLw0Ymqe/sessions \
    -H "Authorization: Bearer $TLB_API_KEY"
    {
    "sessions": [
    {
    "id": "Mv6xC1kRp8Wdzn",
    "label": "Alex and Sam",
    "createdAt": 1758501330000,
    "guests": [
    { "firstName": "Alex", "lastName": "Reyes" },
    { "firstName": "Sam", "lastName": "Okafor" }
    ]
    }
    ]
    }

    Or, with no kiosk, create one from the details the booth collected:

    Terminal window
    curl -sS -X POST \
    https://api.theloungebooth.com/sets/Rd8vN3pLw0Ymqe/sessions \
    -H "Authorization: Bearer $TLB_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{ "guests": [{ "firstName": "Alex", "lastName": "Reyes", "email": "alex@example.com" }] }'
    { "id": "Mv6xC1kRp8Wdzn", "label": "Alex", "status": "active" }

    Either way, the session’s id is what the next three steps take.

  3. Get upload URLs. One per photo. Ask for the whole session at once with count:

    Terminal window
    curl -sS -X POST \
    "https://api.theloungebooth.com/sessions/Mv6xC1kRp8Wdzn/uploads?count=2" \
    -H "Authorization: Bearer $TLB_API_KEY"
    {
    "uploads": [
    {
    "key": "7Kq2mZ0aBx91Tc/Mv6xC1kRp8Wdzn/Zp3nT8wQ1eLk52.jpg",
    "url": "https://<account>.r2.cloudflarestorage.com/tlb-photos/7Kq2mZ0aBx91Tc/…&X-Amz-Expires=600"
    },
    {
    "key": "7Kq2mZ0aBx91Tc/Mv6xC1kRp8Wdzn/Hb9dK4sVu7Rm03.jpg",
    "url": "https://<account>.r2.cloudflarestorage.com/tlb-photos/7Kq2mZ0aBx91Tc/…&X-Amz-Expires=600"
    }
    ],
    "contentType": "image/jpeg",
    "expiresIn": 600
    }
  4. Upload each photo to its url, with the contentType you were given and no API key (this goes to storage, not to us):

    Terminal window
    curl -sS -X PUT "$url" \
    -H "Content-Type: image/jpeg" \
    --data-binary @frame-01.jpg
  5. Complete the session with the keys you uploaded, in the order the gallery should show them. This publishes the gallery and sends each guest their link.

    Terminal window
    curl -sS -X POST \
    https://api.theloungebooth.com/sessions/Mv6xC1kRp8Wdzn/complete \
    -H "Authorization: Bearer $TLB_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
    "photos": [
    { "key": "7Kq2mZ0aBx91Tc/Mv6xC1kRp8Wdzn/Zp3nT8wQ1eLk52.jpg", "width": 2048, "height": 2560 },
    { "key": "7Kq2mZ0aBx91Tc/Mv6xC1kRp8Wdzn/Hb9dK4sVu7Rm03.jpg", "width": 2048, "height": 2560 }
    ]
    }'
    { "id": "Mv6xC1kRp8Wdzn", "status": "completed", "completedAt": 1758501902000, "photos": 2 }

If the group leaves without photos, cancel the session instead of completing it.

Send Authorization: Bearer $TLB_API_KEY on every route. A wrong key, or one since replaced or revoked, gets 401. Request and response bodies are JSON. Ids are 14-character strings such as Mv6xC1kRp8Wdzn, and case matters. Times are whole milliseconds since January 1, 1970 UTC, the same number JavaScript’s Date.now() gives. Error bodies and the validation shape are on Errors.

The events a booth can work now, each with its sets. No parameters.

An event is listed from a day before it starts until a day after it ends, so you can set up early and upload late. An event with no end stays listed. Events come oldest start first; sets within an event by name. An event with no sets is left out.

Field Type What it is
events array The events, possibly empty.
events[].id id The event.
events[].slug string The event’s short name in our links, such as city-launch in kiosk.theloungebooth.com/city-launch/set-1. Also accepted by GET /events/:eventSlug/sets/:setSlug.
events[].name string The event’s name, for showing to your operator.
events[].startAt time When the event starts.
events[].endAt time or null When it ends. null means we set no end.
events[].venueMode boolean Whether the event runs on the venue server rather than in the cloud. The routes are the same either way; show it so your operator knows which they’re on.
events[].sets array, at least 1 The event’s sets. A set is one booth station; most events have one.
events[].sets[].id id The set. Every other route starts from it.
events[].sets[].slug string The set’s short name in the kiosk link, such as set-1. Also accepted by GET /events/:eventSlug/sets/:setSlug.
events[].sets[].name string The set’s name, such as Set 1 or Patio.

Finds one set from the two short names in its kiosk link, such as kiosk.theloungebooth.com/city-launch/set-1. Use it if your booth is set up from that link rather than from a list.

Path What it is
eventSlug The event’s short name, such as city-launch.
setSlug The set’s short name, such as set-1.
{
"set": { "id": "Rd8vN3pLw0Ymqe", "slug": "set-1", "name": "Set 1" },
"event": { "id": "7Kq2mZ0aBx91Tc", "slug": "city-launch", "name": "City Launch", "venueMode": false }
}
Field Type What it is
set.id id The set. The queue and create routes take it.
set.slug, set.name string As in GET /events.
event.id, event.slug, event.name, event.venueMode id, string, string, boolean The set’s event, as in GET /events.

Unlike GET /events, this finds the set whether or not its event is running.

Status Body When
404 { "error": "no such set" } No event has that short name, or it has no set by that name.

The set’s queue: sessions waiting for photos, oldest first. Take the first one unless your operator picks another.

Path What it is
setId events[].sets[].id from GET /events. An unknown id gives an empty queue, not an error.
Field Type What it is
sessions array The waiting sessions, possibly empty. Completed and canceled sessions never appear.
sessions[].id id The session. The upload, complete and cancel routes take it.
sessions[].label string The group’s first names as one line, such as Alex and Sam or Priya, Marcus, and Elena. It is the gallery’s heading; show it to your operator so they call the right group.
sessions[].createdAt time When the group registered.
sessions[].guests array, 1–12 Who is in the group, in the order they registered.
sessions[].guests[].firstName string As the guest typed it. Never empty.
sessions[].guests[].lastName string As the guest typed it. "" when they left it out.

Guests’ emails and phone numbers are never returned.

Registers a group on the set and puts it in the queue. Call it at events without our kiosk, with the details your booth collected. It only works while the event is running, the same window as GET /events: from a day before it starts until a day after it ends.

Path What it is
setId events[].sets[].id from GET /events.
{
"guests": [
{
"firstName": "Alex",
"lastName": "Reyes",
"email": "alex@example.com"
},
{
"firstName": "Sam",
"phone": "(415) 555-0100"
},
{
"firstName": "Jo",
"email": "jo@example.com",
"phone": "212-555-0199"
}
]
}
Field Type What it is
guests array, 1–12, required Everyone in the group, one entry each.
guests[].firstName string, up to 80, required Shown in the gallery heading. Spaces at either end are trimmed; blank is refused.
guests[].lastName string, up to 80, optional Kept for our records. Leave it out or send "" if you don’t ask for it.
guests[].email string, up to 254, this or phone Where we email the gallery link. Stored in lowercase.
guests[].phone string, up to 32, this or email Where we text the gallery link. Spaces, dashes and brackets are ignored. Without a leading + it must be a real 10-digit US or Canadian number; any other country needs + and its country code.

Give each guest an email, a phone number, or both. With both, the link goes both ways. A guest who has been to one of our events before is matched to their earlier visits by email, then by phone.

Response, 201 Created:

{ "id": "Mv6xC1kRp8Wdzn", "label": "Alex, Sam, and Jo", "status": "active" }
Field Type What it is
id id The new session. Take it straight to uploads. Its gallery will be gallery.theloungebooth.com/<id>.
label string The gallery heading, built from the first names.
status "active" Waiting for photos. It is also in the set’s queue now.

Nobody is sent anything yet. Each guest gets the link when you complete the session.

Status Body When
400 { "error": "Every guest needs a first name." } A guest’s firstName is missing or blank.
400 { "error": "Sam needs an email or a phone number" } A guest gave neither.
400 { "error": "This doesn’t look like a phone number: 555-01" } A phone number we can’t read.
400 { "error": "Add at least one guest." } An empty guests.
400 validation error More than 12 guests, a malformed email, or a value over its length.
404 { "error": "no such set" } No set has that setId.
409 { "error": "This event isn’t taking registrations right now." } The event starts more than a day from now, or ended more than a day ago.
409 { "error": "This event registers at the venue." } The event runs in venue mode and you reached the cloud. Point the kiosk or booth at the venue server.
429 { "error": "Too many requests. Try again in a minute." } More than 60 creates in a minute from your IP address.

The guest errors are written for the guest to read, so you can show them as they are.

Signed upload URLs for a session’s photos, one per photo. No request body.

Path What it is
id The session, from the queue or from creating it.
Query Type What it is
count whole number, 1–200, optional How many URLs to get. Leave it out for one, in the single shape below.
type jpeg or png, optional The format of every photo in this call. Default jpeg. A session with both makes one call per type.

With count, the response lists the uploads:

{
"uploads": [
{
"key": "7Kq2mZ0aBx91Tc/Mv6xC1kRp8Wdzn/Zp3nT8wQ1eLk52.jpg",
"url": "https://<account>.r2.cloudflarestorage.com/tlb-photos/7Kq2mZ0aBx91Tc/…"
}
],
"contentType": "image/jpeg",
"expiresIn": 600
}

Without it, one upload, not in a list:

{
"key": "7Kq2mZ0aBx91Tc/Mv6xC1kRp8Wdzn/Zp3nT8wQ1eLk52.jpg",
"url": "https://<account>.r2.cloudflarestorage.com/tlb-photos/7Kq2mZ0aBx91Tc/…",
"contentType": "image/jpeg",
"expiresIn": 600
}
Field Type What it is
key string Where this photo will live: <event id>/<session id>/<photo id>.jpg (or .png). Keep it; complete takes it. Use it as given rather than building your own.
url string Where to upload the photo, with an HTTP PUT of its raw bytes. No API key: the signature in the URL is the permission.
contentType "image/jpeg" or "image/png" The exact Content-Type header to send with every PUT from this call. It is part of the signature, so anything else, even image/jpeg; charset=utf-8, gets 403 from storage.
expiresIn seconds, always 600 How long each url works. Start each upload within ten minutes. For a big session on a slow line, ask in batches as you go.

Every call gives new keys. A failed upload can be retried on the same url until it expires; after that, ask for a new one.

Status Body When
400 validation error count isn’t a whole number from 1 to 200, or type isn’t jpeg or png.
404 { "error": "no such session" } No session has that id.
409 { "error": "session is completed" } or "session is cancelled" The session no longer takes photos.

Records the uploaded photos, publishes the gallery and sends each guest their link. Call it once per session, after every upload has finished.

Path What it is
id The session.
{
"photos": [
{ "key": "7Kq2mZ0aBx91Tc/Mv6xC1kRp8Wdzn/Zp3nT8wQ1eLk52.jpg", "width": 2048, "height": 2560 }
]
}
Field Type What it is
photos array, 1–200, required The photos in gallery order.
photos[].key string, required A key from uploads for this session whose upload succeeded.
photos[].width whole number, above 0, required The photo’s width in pixels. The gallery lays out its grid from width and height before the image loads.
photos[].height whole number, above 0, required The photo’s height in pixels.

We check that every key belongs to this session and is really in storage before saving anything. Photos staff already added in admin stay first; yours follow.

Response, 200 OK:

{ "id": "Mv6xC1kRp8Wdzn", "status": "completed", "completedAt": 1758501902000, "photos": 1 }
Field Type What it is
id id The session.
status "completed" The gallery is live at gallery.theloungebooth.com/<id>.
completedAt time When it went live.
photos whole number How many photos this call added.

Links go out by email, text or both, whichever each guest gave. Guests already sent their link are not sent it again.

Status Body When
400 validation error No body, an empty photos, the same key twice, or a width or height that isn’t a whole number above 0.
404 { "error": "no such session" } No session has that id.
409 { "error": "session is completed" } or "session is cancelled" Already completed or canceled.
409 { "error": "session completed concurrently" } Two completes for the same session crossed; the other one won.
422 { "error": "objects not found", "missing": ["7Kq2…/Mv6x…/Zp3n….jpg"] } A key was never uploaded, its URL expired first, or it belongs to another session. missing lists them. Nothing was saved: upload those again and call again with the full list.

Takes a session out of the queue without photos, for a group that left. No request body. It can’t be undone, and only a waiting session can be canceled.

Path What it is
id The session.

Response, 200 OK:

{ "id": "Mv6xC1kRp8Wdzn", "status": "cancelled" }
Field Type What it is
id id The session.
status "cancelled" Out of the queue for good. The group registers again for a new session.
Status Body When
404 { "error": "no active session with that id" } No such session, or it is already completed or canceled.

Only this page’s create route is rate limited: 60 a minute from one IP address, answered with 429. Nothing is idempotent: two creates make two sessions, two uploads calls give two sets of keys, and a second complete gets 409. After a timeout, read the queue before creating again.