Skip to main content

Appointment Integration

Overview​

Orchestra's Calendar API lets you push appointments into Orchestra from another system, build a customer-facing booking portal, or do both at once. The API has two parallel surfaces that differ in audience, authentication, and what they expose:

  • Internal API (/calendar-backend/api/v1/...) — full CRUD over appointments, customers, branches, services, resources, and capacity. Authenticated with an Orchestra user. Used by third-party systems that own the master record and push their changes into Orchestra.
  • Public API (/calendar-backend/public/api/v1/... and /v2/...) — narrower, customer-safe surface designed for public booking journeys. Branches and appointments are addressed by publicId (an opaque 64-char string), not by internal numeric IDs. Typically fronted by the API Gateway.

The general pattern: the third party holds the master record, Orchestra holds the queue/operational view, and the API keeps them in sync. Appointments from a contact centre, an online portal, and walk-in customers all land in the same Orchestra branch and get blended into one queue.

Use cases​

Third-party booking system → Orchestra A hospital, bank, or government office books appointments in their existing scheduling platform (e.g. an EMR, contact-centre booker, partner portal). Each new booking, reschedule, or cancel is pushed to Orchestra so kiosks, screens, and staff terminals see one unified queue.

Public booking portal You build a self-service booking website or mobile app. The customer picks a branch and service, sees real available time slots, and books. Behind the scenes, calls flow through the public API (and usually the API Gateway).

Mixed flow Both at once — partner systems push appointments, your own portal also lets customers self-book against the same calendar. Orchestra is the source of truth for what's actually scheduled.

Daily sync / migration Bulk-importing from a legacy scheduling system. Use the sync endpoint to move large appointment sets in a single transaction.

When not to use this integration​

Walk-in-only deployments — Orchestra's queue and ticketing endpoints (Common API) are simpler and don't require Calendar setup.

Internal Orchestra-to-Orchestra communication — talk to Central directly rather than through Calendar.

Architecture​

Third-party booking Internal API
system / partner ─────────────────────────► ┌──────────────────────────┐
│ Orchestra Calendar │
│ Backend │
Public booking Public API + API Gateway │ • appointments │
website / mobile app ─────────────────────────► │ • customers │
│ • branches / services │
│ • resources / capacity │
Webhook callbacks ◄───────────────────────── │ • settings │
(status updates) └──────────┬───────────────┘
│
▼
┌──────────────────────────┐
│ Orchestra Central │
│ (visits, queues, │
│ workstations) │
└──────────────────────────┘

Components​

  • Calendar backend — separate Orchestra service that owns appointments, customers, branches, services, and capacity. Hosted at /calendar-backend/....
  • Internal endpoints — addressed by numeric id (e.g. branch.id = 7). Authenticated. Full read/write.
  • Public endpoints — addressed by publicId (e.g. branchPublicId, servicePublicId, publicAppointmentId). Designed for end-customer flows; some endpoints are anonymous, others require a token.
  • Callback endpoint — Orchestra can call back to your system when an appointment changes status. See Receiving updates.

Prerequisites​

  • Orchestra version: 6.1+ (calendar-backend module enabled).
  • Branch and service configured in Orchestra's Business Configuration application, with Mobile enabled ticked if the appointment will surface on Mobile Ticket.
  • For internal API: an Orchestra user with the Calendar connector role enabled.
  • For public API: an API Gateway in front, with an API token. See API Gateway guide.
  • Time zones: every branch in Orchestra is configured with a TZ. Send timestamps in ISO-8601 with a TZ-aware offset where you can.

Authentication​

Internal API — Basic Auth​

Every internal call uses HTTP Basic Auth with an Orchestra user. The user must have the Calendar connector enabled in its role.

GET /calendar-backend/api/v1/appointments
Host: orchestra.example.com
Authorization: Basic <base64(user:password)>

In curl:

curl -u user:password "https://orchestra.example.com/calendar-backend/api/v1/appointments"

Public API — through the gateway​

Public endpoints are typically exposed behind the API Gateway. Calls carry an auth-token header (a UUID issued by api-token-generator). The Gateway authenticates the request, applies rate limiting and caching, and forwards to the Orchestra calendar backend.

POST /rest/calendar-backend/public/api/v1/branches/{branchPublicId}/services/{servicePublicId}/dates/{date}/times/{time}/book
Host: api.example.com
auth-token: <gateway-token>
Content-Type: application/json

Booking flows​

Flow A — Third-party pushes appointments (internal API)​

This is the classic "third-party system holds the master record" pattern. The third party creates appointments in its own UI, then mirrors each change into Orchestra.

1. Create an appointment

POST /calendar-backend/api/v1/appointments
Authorization: Basic <creds>
Content-Type: application/json
{
"start": "2026-05-01T09:00:00",
"end": "2026-05-01T09:30:00",
"branch": {"id": 1},
"services": [{"id": 12}],
"customers": [{
"firstName": "Charles",
"lastName": "Ryder",
"email": "charles.ryder@example.com",
"dateOfBirth": "1903-09-21",
"externalId": "EXT-44721"
}],
"title": "Annual review",
"notes": "First-time visitor"
}

Response: 201 Created with the full appointment object including the new id (internal) and publicId (opaque). Store both — id for future internal API calls, publicId for any URL you'd embed in an email or SMS.

Reference: POST /calendar-backend/api/v1/appointments under AppointmentEndpoint.

2. Update the appointment

PUT /calendar-backend/api/v1/appointments/{id}

Send the full appointment body — fields you don't include get cleared. To reschedule, change start and end. To change the assigned service or resource, update those nested objects.

3. Cancel the appointment

DELETE /calendar-backend/api/v1/appointments/{id}

Returns 204 No Content on success.

4. Bulk daily sync

If you need to push hundreds of appointments at once (initial migration or daily batch), use the sync endpoint:

POST /calendar-backend/api/v1/appointments/sync

The request body carries a list of appointments. Orchestra processes them transactionally and returns per-record status.

5. Audit trail

Every change to an appointment is recorded. Read it via:

GET /calendar-backend/api/v1/appointments/{id}/history
GET /calendar-backend/api/v1/appointments/history?start=2026-05-01&end=2026-05-31&branch=1

Flow B — Customer self-books (public API)​

This is the booking-website pattern: walk the customer through branch → service → date → time → confirm.

1. List branches

GET /calendar-backend/public/api/v1/branches
auth-token: <gateway-token>

Response is a list of branch summaries with publicId, name, address, lat/lon. Use this to populate a branch picker.

2. Show services available at the chosen branch

GET /calendar-backend/public/api/v1/branches/{branchPublicId}/services

3. Show available dates

GET /calendar-backend/public/api/v1/branches/{branchPublicId}/services/{servicePublicId}/dates

Returns dates within the next booking window where capacity exists.

4. Show available time slots for a chosen date

GET /calendar-backend/public/api/v1/branches/{branchPublicId}/services/{servicePublicId}/dates/{date}/times

The response is a list of time strings (e.g. 09:00, 09:30, ...) representing slots that have at least one resource free.

5. Reserve and confirm (two-step)

For flows where you collect customer details after picking a slot — recommended because it stops two customers grabbing the same slot:

POST /calendar-backend/public/api/v1/branches/{branchPublicId}/services/{servicePublicId}/dates/{date}/times/{time}/reserve

The slot is now held under a temporary appointment in status 10 (reservation). The response includes a publicAppointmentId. Show the customer the booking form and on submit, confirm:

POST /calendar-backend/public/api/v1/branches/appointments/{publicAppointmentId}/confirm
Content-Type: application/json
{
"customer": {
"firstName": "Charles",
"lastName": "Ryder",
"email": "charles.ryder@example.com",
"phone": "+44 20 7946 0958"
},
"title": "Annual review",
"notes": ""
}

If the customer abandons the form, the reservation expires (default ~5 minutes) and the slot becomes bookable again.

6. Or book directly (single-step)

If you have all customer data already, skip the reserve step:

POST /calendar-backend/public/api/v1/branches/{branchPublicId}/services/{servicePublicId}/dates/{date}/times/{time}/book
Content-Type: application/json
{
"title": "Annual review",
"notes": "",
"customer": {
"firstName": "Charles",
"lastName": "Ryder",
"email": "charles.ryder@example.com",
"phone": "+44 20 7946 0958",
"dateOfBirth": "1903-09-21"
}
}

Response: 201 Created with the booked appointment, including its publicId for use in confirmation emails.

Flow C — Multi-customer / multi-service (public API v2)​

For appointments where multiple customers share a slot, or one slot covers several services, use the v2 endpoints:

POST /calendar-backend/public/api/v2/branches/{branchPublicId}/dates/{date}/times/{time}/book

Body carries services: [...] and customers: [...] arrays. Useful for family appointments, multi-service consultations, or any scenario where the slot's not 1:1.

The v2 surface also has helpful helpers:

  • GET /branches/available — branches that can serve a given set of services.
  • GET /branches/nextAvailableSlot — first free slot across a list of branches.
  • POST /branches/{branchPublicId}/reserveFirstAvailable — reserve the next available slot without picking a date manually.
  • POST /branches/{branchPublicId}/reserveRandomAvailable — reserve one of the first 10 available, useful for load-spreading.
  • PUT /branches/appointments/{publicAppointmentId}/extend — extend an active reservation by N minutes.

Customer management​

Customers are first-class objects in the calendar backend. The internal endpoints (/api/v1/customers) let third parties manage customer records the same way they manage appointments.

POST /calendar-backend/api/v1/customers create
GET /calendar-backend/api/v1/customers list
GET /calendar-backend/api/v1/customers/{id} get by internal id
PUT /calendar-backend/api/v1/customers/{id} update
DELETE /calendar-backend/api/v1/customers/{id} delete
GET /calendar-backend/api/v1/customers/search search by criteria
GET /calendar-backend/api/v1/customers/searchcustomer free-text search

Customer body:

{
"id": 4521,
"publicId": "abc123…64chars",
"externalId": "EXT-44721",
"firstName": "Charles",
"lastName": "Ryder",
"name": "Charles Ryder",
"email": "charles.ryder@example.com",
"phone": "+44 20 7946 0958",
"dateOfBirth": "1903-09-21",
"identificationNumber": "AB123456C",
"addressLine1": "31 Marchmain House",
"addressLine2": "",
"addressCity": "London",
"addressZip": "SW1A 1AA",
"addressCountry": "GB",
"addressState": "",
"custom": "{\"loyaltyTier\":\"gold\"}",
"created": "2026-04-22T10:00:00.000Z",
"updated": "2026-04-22T10:00:00.000Z"
}

externalId is the field to use when mapping to your master record. publicId is what you embed in customer-facing URLs. custom is a free-form string — most customers JSON-encode it.

For the public API, customer endpoints are read-only and addressed by publicId:

GET /calendar-backend/public/api/v1/customers/identify?… lookup by personal details
GET /calendar-backend/public/api/v1/customers/{publicId} get
GET /calendar-backend/public/api/v1/customers/{publicId}/appointments upcoming appointments
GET /calendar-backend/public/api/v1/customers/externalId/{externalId}/appointments by mapping ID

Use customers/identify to locate a returning customer by partial details (name + DOB + last 4 of card etc.) so they don't have to retype everything.

Receiving updates (webhook callback)​

When an appointment changes status in Orchestra (booked, called, ended, cancelled), Orchestra can POST a callback to your system so the third-party master record stays current.

The callback endpoint shape is defined by the public API:

PUT /calendar-backend/public/api/v1/appointments/callback

Configure the callback URL and authentication in Orchestra's calendar settings. Each callback delivers a status-update payload — appointmentId, publicId, new status, timestamp.

To poll instead of receive callbacks, fetch by status:

GET /calendar-backend/api/v1/appointments/search?status=…&from=…&to=…

Reschedule and cancel from the public API​

Two patterns the customer-facing portal will need:

Reschedule — moves an appointment to a new slot, keeping the same publicId:

PUT /calendar-backend/public/api/v1/appointments/{publicId}/reschedule
Content-Type: application/json

{ "newDate": "2026-05-08", "newTime": "11:00" }

Cancel — deletes the appointment:

DELETE /calendar-backend/public/api/v1/appointments/{publicId}

Both endpoints accept anonymous calls if you've configured the calendar to allow customer self-service cancel/reschedule by publicId. If not, the customer needs to authenticate first.

Locking (concurrent edits)​

When two staff members might edit the same appointment, the API offers an optimistic lock:

PUT /calendar-backend/api/v1/appointments/{id}/lock acquire lock
DELETE /calendar-backend/api/v1/appointments/{id}/lock release lock

Acquire before editing, release after — or it auto-expires after a few minutes.

Troubleshooting​

SymptomLikely causeResolution
401 Unauthorized on internal endpointsWrong Basic Auth or user lacks Calendar roleCheck the user has the Calendar connector enabled in its role.
403 Forbidden on public endpointsAPI token is invalid or revoked at the GatewayRe-issue with api-token-generator; check the Gateway logs.
404 Not Found when bookingpublicId for branch or service is wrong, or the branch isn't Mobile enabledConfirm the IDs from GET /public/api/v1/branches and that the branch is mobile-enabled in Business Configuration.
409 Conflict on book or reserveTwo callers raced for the same slot, or the slot's been taken since you fetched the times listRefresh /times and retry. Use the reserve→confirm flow for high-contention slots.
Reservation expires before customer can confirmDefault 5-minute hold is too shortEither lengthen the reservation timeout in calendar settings, or use PUT /v2/branches/appointments/{id}/extend.
Times look wrong by an hourServer is interpreting wall-clock vs. UTC differently than your clientAlways send ISO-8601 with TZ offset; the branch has its own configured TZ in GET /api/v1/branches/timezones.
Customer info not flowing back to staff terminalCustom field mapping in Orchestra not configuredMap calendar customer fields to Orchestra Central customer fields in System Administration.

FAQ​

Which version of the API should I use — v1 or v2? For new public booking flows that need multi-service or multi-customer support, use v2. For everything else, v1 is fine and stable. The endpoints aren't deprecated — they coexist.

How do I attach a custom ID from my system to an Orchestra appointment? Use the customer's externalId field (most common) or the appointment's notes/custom-parameter fields. externalId is indexed and queryable via GET /public/api/v1/customers/externalId/{externalId}/appointments.

How do I block out a time slot without booking a real appointment? Use the blocking appointment search and create endpoints under /api/v1/appointments/blocking/....

Can I see what's in the calendar cache? Yes — GET /api/v1/cachestatistics and the related cache-key lookup endpoints expose internal cache state for debugging hot-path performance.