Skip to main content

Public queue self-booking

Every queue you publish gets a hosted booking page at a public URL — no account, no sign-in:
A caller opens the link, sees the slots the queue published, picks one, and types the phone number you should call back. The booking stores as a scheduled callback with that caller’s number and slot, and the queue’s callback dispatcher dials them at the appointed time. The booking is the caller’s consent to be called back at that slot, and every booking writes a consent record you can review in the audit log. Two public endpoints back the whole surface, which is why you don’t have to host anything — embed the URL, and the branded booking page, the slot picker, and the confirmation are already done.

Tenant setup — expose a queue as bookable

The surface is off until you opt in per organization. Flip it on with a settings write:
cURL
The same settings block carries your page chrome as optional fields (hostname-safe: the API accepts only http(s) logo URLs, #rrggbb colours, and caps the copy lengths):
cURL
Omit both and the platform renders its default (no branding changes, no copy changes). Compose the public URL from the building blocks you already have: https://orbit.devotel.io/book/{ tenantId from GET /settings/general }/{ the queue id }. Turn it back off by writing "enabled": false — the booking page renders the 404 and the booking POST answers 422 with the same shape an unknown tenant gets, so nothing picks apart your inventory (an attacker can’t tell a queue that never existed from a queue you closed).

Publish the queue’s bookable window

The page publishes slots only for queues with a schedule. Two levels:
  • Per-queue hoursbusinessHours on the queue itself. Set or override it via PUT /api/v1/voice/queues/{id} with the businessHours field (IANA timezone + day windows). A queue without a businessHours falls back to the org default, which you set via PUT /api/v1/organization/business-hours.
  • A queue with neither has no published schedule, the availability endpoint reports reason: "no_schedule_configured" with an empty slot list, and the booking POST returns 422 NO_BOOKABLE_SCHEDULE.
cURL
Bookable slots respect slot_minutes (5–240, default 30) and lead_minutes (minimum notice before a slot, 0–20160, default 60) — the page and API accept both as query overrides, so a booking link can ask for 45-minute slots with 2-hour notice without changing the queue configuration.

Booking lifecycle

Every booking runs the same four steps:
  1. Availability. GET /api/v1/public/queues/{tenantId}/{queueId}/availability (or the booking page itself, which calls this) enumerates the queue’s business-hours window into concrete slots and subtracts the slots already holding a booking, so two customers can’t take the same slot.
  2. Book. POST /api/v1/public/queues/{tenantId}/{queueId}/appointments writes one scheduled callback row with the caller’s phone and chosen slot_start.
  3. Dispatch. At the slot start, the queue’s callback dispatcher dials the caller. The dial respects the queue’s callback retry/attempt settings, and the row the booking wrote is the same shape as a callback your agents queue from the dashboard.
  4. Cancel or reschedule. The caller receives a tracking link (GET /api/v1/public/callbacks/<token>) in the “your callback is on its way” message — a signed HMAC envelope that carries the callback id, never a tenant id or queue id the caller could tamper with. Status, one-tap cancel, and reschedule all run that same token (see Callbacks and scheduling). The booking’s consent record stays in your audit log either way.
Self-booking via curl, once you know the queue is bookable:
cURL
A slot someone just took returns 409 SLOT_ALREADY_BOOKED (the caller picks another); a queue that published no schedule answers 422 NO_BOOKABLE_SCHEDULE; anything that can’t identify the tenant or that hasn’t opted in answers 422 INVALID_TENANT.
  • SMS deep link. Send the booking URL in the message. Append ?phone=<E.164> and the phone field arrives pre-filled, so the caller taps a slot with nothing to type: https://orbit.devotel.io/book/<tenantId>/<queueId>?phone=+15551234567.
  • Email footer / ticket link. Drop the URL into signature blocks or the “can’t wait?” line of a support reply; the page renders the branding and copy you configured.
  • QR code (for in-store / printed collateral). Point the QR at the URL; any phone camera can open it.
  • IVR hand-off — pair the booking cue with the existing press-1 virtual callback flow: when the queue offers a callback in-queue, play the booking URL instead and send it by SMS to the caller, so the caller who wants a scheduled slot picks it off the page rather than waiting to be dialled.
Because the surface is tokenless (the URL identifies only tenant + queue, and exists only when you publish the queue), the booking POST is the only call your integration needs to guard. Ready-made booking page means you can use the plain URL from any channel — SMS, WhatsApp, email, chat, or a link from your own web support page.

Ops

  • No existence signal. A queue that is closed or unknown returns { "enabled": false } on the availability GET and 422 INVALID_TENANT on the booking POST — the same body an unknown tenant gets either way. Never verify “is this queue enabled?” from a public reader; verify inside your own systems.
  • Rate limiting. Every public call to the surface sits under the per-IP public surface limit. If you embed booking into your own agent or routing flow, pick the slot client-side first and then forward the booking POST so a half-finished journey never burns the public budget.
  • SLA and audit inherit the queue. The queue’s SLA target, occupancy, and health metrics read the booking rows the dispatcher dialled, so the booking surface and the manual callback queue share the wallboard you already run. The booking audit row keeps the caller’s phone masked (the raw number lives on the callback row for dispatch only), and the booking remains the caller’s consent to a callback at the chosen slot.