Pagination
List endpoints return up to 25 items per page, newest first. Passlimit to ask for 1 to 100 items. GET /services, GET /staff, GET /rules and GET /agents return everything in one page, in their own order.
status, starts_after, starts_before and contact_id on GET /appointments, combine with each other and with pagination. To get the next page, send the same request, filters included, with cursor set to next_cursor. On the last page, next_cursor is null and has_more is false. Treat the cursor as an opaque string; its format can change.
GET /services, GET /staff, GET /availability and GET /webhooks return every item on one page.
Filter lists
Some list endpoints take filters as query parameters. Filters combine with each other and withcursor.
To list yesterday’s calls for a business in Toronto:
+ in an offset such as +01:00 as %2B01:00.
Retry safely with Idempotency-Key
Network errors can leave you unsure whether a request worked. Send anIdempotency-Key header on requests that create things, and retry with the same key:
POST /contactsPOST /appointmentsPOST /knowledgePOST /servicesPOST /rulesPOST /billing/checkoutPOST /widget-keysPOST /phone-numbers/claimsandPOST /phone-numbers/{phone_number_id}/replace, which require itPOST /team/invitations, which requires it
POST /webhooks ignores the header, so LobbyStack never stores a copy of the signing secret. If a create request fails with a network error, check GET /webhooks before you retry.
POST /contacts/passcodes and POST /contacts/passcodes/generate ignore it too, so LobbyStack never stores a copy of a keypad PIN. Sending the same PINs to POST /contacts/passcodes again just sets them again. If POST /contacts/passcodes/generate fails with a network error, look for contacts whose passcode.set_at is from that request, and give them new PINs with contact_ids.
POST /team/claim-link ignores it too, so LobbyStack never stores a copy of the link that hands over the business. Each request sends a new link and revokes the one before, so after a network error, look in GET /team for a pending invitation with transfers_ownership before you retry.
book_appointment, create_contact and add_knowledge count as POST /appointments, POST /contacts and POST /knowledge.
- A retry with the same body gets the first response again, with the header
Idempotent-Replayed: true. The API doesn’t create a second appointment. LobbyStack compares the JSON values, so key order and spacing don’t matter. - The same key with a different body returns
422with the codeidempotency_key_reused. - A retry that arrives while the first request is still running waits for it, then gets its response.
- LobbyStack saves the key and the change together. If the first request failed with an error, nothing was saved, so your retry runs again.
Errors
Errors use one shape:details appears on validation errors, and on the 409 from POST /onboarding/complete, where each path names a missing step. Match on code; the message wording can change.
Booking through the API
The API follows the same rules as the AI agent:POST /appointmentsandPOST /appointments/{appointment_id}/reschedulework only when the business’sbooking_modeisinstant. Read it fromGET /business.- The requested time must fall within opening hours, avoid closures and other appointments, and be free on a connected Google Calendar. When LobbyStack can’t confirm the calendar is up to date, it treats the time as busy.
- A day can have several opening windows, such as 09:00 to 12:00 and 13:00 to 17:00. Send each one as its own entry in
hours. A business with no opening hours has no bookable time. - New bookings sync to the connected calendar and queue the usual confirmation and reminder texts. LobbyStack sends them only to contacts who agreed to texts. On a self-hosted instance, pass
"sms_consent": truewhen the customer agreed while booking. On LobbyStack Cloud, texts come from LobbyStack’s shared number, and only the agent can collect that consent, on a call, sosms_consenthas no effect. - Changing a contact’s
phonewithPATCH /contacts/{contact_id}clears their agreement to texts, since they gave it for the old number. A contact who opted out stays opted out. - To book with a specific person, pass
staff_id. Without it, LobbyStack picks a free staff member who takes the service. - Cancelling works in any booking mode. Cancelling an appointment that is already cancelled returns it unchanged. When you cancel an upcoming appointment and the customer agreed to texts, LobbyStack texts them that it’s cancelled.
Staff
GET /staff lists everyone who takes appointments, with active and the service_ids each person can be booked for. A service with no staff assigned, in the dashboard or with staff_ids on POST /services and PATCH /services/{service_id}, is open to every active staff member.
A business that never set up staff still has one active staff member, named after the business. Bookings go to that person, so GET /staff returns it like any other staff member, and you can pass its id as staff_id.
Pass staff_id to:
GET /availability, to list only the times that person is free.POST /appointments, to book with that person.POST /appointments/{appointment_id}/reschedule, to move the appointment to that person. Without it, the appointment keeps its staff member.
staff_id that isn’t an active staff member of the business, or who doesn’t take the service, returns 400 with the code invalid_request. Moving an appointment to another staff member returns 409 conflict when either person has their own connected calendar; cancel it and book a new one instead.