Tour Director API
The on-tour surface: a real-time sales dashboard per trip, assisted booking on behalf of guests, cash and card recording, refunds, availability management, goodwill vouchers, and the Tour Director's own settlements — plus the trip-operations endpoints that give the Tour Director their whole operational day (schedule, guest manifest, suppliers, documents, expenses).
All endpoints are under /api/optionals/v1 and require a Tour Director bearer token (see Authentication). Ownership is enforced everywhere: trip-scoped endpoints verify the trip is assigned to the session's Tour Director, and order/transaction endpoints verify the order sits on one of their trips — anything else returns 403 not_your_trip. All amounts are integer minor units.
Authentication
Tour Directors authenticate through Keycloak using the OAuth 2.0 Authorization Code flow with PKCE and MFA, federated to your corporate identity provider (for example Microsoft Entra ID) so they sign in with their existing SSO accounts. The API validates Keycloak-issued access tokens and never accepts staff credentials directly — see Authentication.
On sandbox tenants (provider: mock) only, the evaluation login is:
POST /auth/td
{ "tenantId": "meridian", "code": "TD-4471", "pin": "4471" }
Returns { token, td } (the Tour Director record, without the PIN). The bearer-token transport is identical in both modes.
Trips and the dashboard
My trips
GET /td/trips?tenantId=meridian
Trips assigned to the signed-in Tour Director, each with its resolved brand.
Trip dashboard
GET /td/trips/{tripId}/dashboard?tenantId=meridian
The real-time operational view, in one response:
{
"trip": { "...": "..." },
"brand": { "...": "..." },
"guests": [
{
"guest": { "id": "g_014", "firstName": "Elena", "lastName": "Carter", "party": { "size": 2, "...": "..." } },
"orders": [ { "order": { "...": "..." }, "transactions": [ "..." ], "paymentLabel": "Partially paid", "supplierLabel": "Supplier place pending", "hasPendingOnRequest": true } ],
"totalSpent": 13000,
"outstanding": 13000
}
],
"catalog": [ { "item": { "product": { "...": "..." }, "status": "available", "remaining": 9, "...": "..." }, "sold": 15, "participants": ["Elena Carter", "James Carter"] } ],
"totals": { "grossSales": 412000, "refunds": 8500, "cashCollected": 96000, "cardCollected": 301000, "voucherRedeemed": 15000 },
"gratuityTotal": 54000,
"currency": "GBP",
"dayPlans": [ "..." ]
}
guests— every guest party with their orders, payment state labels, and outstanding balances.catalog— per-product sold counts and confirmed participant names, exactly what the Tour Director reads out when making supplier reservations.totals— running trip totals by payment method (cash collected is net of cash refunds).
Selling
Assisted booking
POST /td/trips/{tripId}/orders
{
"tenantId": "meridian",
"guestId": "g_014",
"items": [ { "type": "optional", "productId": "opt_venice_gondola", "quantity": 2, "participants": ["Elena Carter", "James Carter"] } ]
}
Same item shape as the guest order, created on the guest's behalf with channel: td_assisted. When a product is sold out under an on_request policy, an assisted sale may still be taken — the item is flagged onRequestStatus: pending until operations confirms it with the supplier. Returns 201 with the order.
Record a cash payment
POST /td/orders/{orderId}/payments/cash
{ "tenantId": "meridian", "amount": 13000, "payerName": "James Carter" }
Cash is a first-class transaction: it feeds the dashboard totals, settlement, and reconciliation like any card payment. amount optional (defaults to the outstanding balance); payerName records who handed over the cash for split payments.
Take a card payment
POST /td/orders/{orderId}/payments/card
Same body as the guest card payment — used when the Tour Director takes the guest's card on tour. The transaction records the Tour Director as the recorder.
Apply a voucher
POST /td/orders/{orderId}/payments/voucher
{ "tenantId": "meridian", "code": "MER-GV-7Q2K9" }
Accepts either a code or — for codeless goodwill vouchers the Tour Director has just issued — a voucherId.
Pre-check a voucher
POST /td/vouchers/check
{ "tenantId": "meridian", "code": "MER-GV-7Q2K9", "currency": "GBP" }
Same response shape as the guest pre-check; the Tour Director supplies the currency since the sell flow may span trips.
Issue a goodwill voucher
POST /td/vouchers
{
"tenantId": "meridian",
"amount": 5000,
"currency": "GBP",
"expiryDate": "2026-12-31",
"linkedBookingRef": "MT-882401",
"withCode": false,
"notes": "Missed excursion on day 3 - goodwill"
}
An ad-hoc goodwill voucher issued in the field — with withCode: false it is codeless and applied immediately by voucherId; with withCode: true a code is generated for later use. Returns 201 with the voucher.
Corrections
Refund
POST /td/refunds
{ "tenantId": "meridian", "transactionId": "txn_9912", "amount": 8500, "reason": "Guest illness - day 4 excursion" }
Refunds a completed transaction (partial with amount, full without). The refund is linked to the original transaction, restores allotment where applicable, and requires a reason. Tour Directors can only refund transactions on their own trips.
Remove a participant
POST /td/trips/{tripId}/orders/{orderId}/remove-participant
{
"tenantId": "meridian",
"itemId": "item_1",
"participantName": "James Carter",
"reason": "Guest unwell",
"changeRequestId": "evt_2231"
}
Removes one named participant from an optional item and refunds that unit price in one operation. Passing the optional changeRequestId also resolves the guest's change request, clearing it from the overview action list.
Availability
PATCH /td/trips/{tripId}/products/{productId}/availability
{ "tenantId": "meridian", "status": "sold_out", "capacityOverride": 20, "whenSoldOut": "on_request", "note": "Supplier reduced the allotment for this date" }
Sets the per-trip availability override: status (available | sold_out | unavailable), an optional capacityOverride (null clears it), the whenSoldOut policy (block | on_request — preserved from the current setting when omitted), and a note. Returns the stored override. Guests see the effect immediately in the catalog.
Visibility
Sales export (CSV)
GET /td/trips/{tripId}/sales.csv?tenantId=meridian
The Tour Director's own completed sales for the trip as a CSV download (timestamp, order reference, type, method, amount, payer) — their tax and reconciliation record. Scoped strictly to their own transactions; no card data, voucher codes, or finance actions appear.
Trip activity
GET /td/trips/{tripId}/activity?tenantId=meridian
A sanitized, bounded activity feed for the trip — human-readable summaries ("Elena Carter recorded a card payment", "Marco Bellini updated product availability"). Finance settlement actions and other Tour Directors' actions are filtered out.
My settlements
GET /td/settlements?tenantId=meridian
The Tour Director's own settlements — full transparency into what the operator owes them per period and currency, including the administration-fee deduction and status (draft → exported → released). Read-only; settlement actions belong to finance.
Trip operations
Beyond selling, these endpoints give the Tour Director their operational day in the same app. They follow the same ownership rules.
| Endpoint | What it returns / does |
|---|---|
GET /td/trips/{tripId}/overview-extras | The "today" banner, quick-access counts, actionable alerts (including guest change requests), and the crew list |
GET /td/trips/{tripId}/schedule | Day-by-day itinerary services with the Tour Director's own status updates applied |
PATCH /td/trips/{tripId}/services/{serviceId}/status | Update a service's status: pending | in_progress | complete | delayed | cancelled. delayed/cancelled require a reason; reversing a completed or cancelled service requires confirm: true (409 confirmation_required otherwise) |
GET /td/trips/{tripId}/manifest | Guest duty-of-care manifest: rooms, dietary/medical/accessibility flags, form completion, emergency contacts |
POST /td/trips/{tripId}/manifest/{guestId}/remind | Log a form-completion reminder for follow-up |
POST /td/trips/{tripId}/change-requests/{eventId}/resolve | Mark a guest change request handled (body: orderId, optional resolution) |
GET /td/trips/{tripId}/suppliers | Supplier directory for the trip: contacts, booking references, service days |
GET /td/trips/{tripId}/documents | Crew documents: rooming list, passenger list, voucher pack, briefing notes, emergency plan |
GET /td/trips/{tripId}/expenses | Cash float, on-ground supplier payment lines, and the Tour Director's own out-of-pocket expenses |
POST /td/trips/{tripId}/expenses/payments/{paymentLineId}/settle | Mark an on-ground supplier payment line settled/unsettled (body: { "settled": true }) |
POST /td/trips/{tripId}/expenses | Capture an out-of-pocket expense: category (Meals, Transport, Tips & Gratuities, Activities, Supplies, Other), description, dayNumber, amount, hasReceipt |
Every state change here is audited like the sales endpoints.