Document data reference
Every document type has a JSON Schema (draft-07) that defines its data contract. The schema is the boundary between data transformation and templates: the transformation must produce data that validates against the schema, and templates may only bind fields the schema declares.
Two rules apply to all schemas:
- Optionals are nullable by design. Required blocks are kept to a minimum; everything else is
["type", "null"]or an optional property. Templates collapse sections whose backing data is absent, so a sparse dataset renders a shorter document, not a broken one. - Every dataset must validate. The QA harness validates every sample dataset against its schema on every change, and generation validates org-derived data the same way. Schema validation failing is a feature — it surfaces data bugs before a guest sees them.
Schemas are versioned (option-1.0.json); blueprint actions pin the schema_version and template_version they use, so you can evolve contracts without breaking in-flight documents.
The shared meta block
Every document (except the flat regulatory certificate) carries the same meta envelope:
{
"meta": {
"locale": "en-GB",
"currency": "GBP",
"brand": { "code": "meridian-uk", "name": "Meridian Travel UK" },
"channel": { "name": "UK Direct", "isTrade": false },
"revision": 1,
"supersedesDocumentId": null
}
}
| Field | Meaning |
|---|---|
locale | BCP 47 tag. Drives date, number, and currency formatting at render time. Required. |
currency | ISO 4217 code for all monetary values in the document. Required. |
brand | Identity only: {code, name}. Presentation (colours, fonts, logos) resolves from tenant configuration by brand code — hex values never travel in document data. Required. |
channel | The sales channel and whether it is a trade channel (isTrade). |
revision | Monotonic revision number of this document within its itinerary. |
supersedesDocumentId | ID of the document this revision replaces; null for the first revision. Drives the "a newer version exists" viewer banner. |
The option schema adds meta.itineraryMode (see below).
option (Proposal)
Required blocks: meta, tour, contact, pricing, footer. Everything else — travelers, agent, highlights, itinerary, services, flights, accommodations, stay units, inclusions, terms, personal message — is optional and collapses when absent.
tour.departureType — required, never defaulted
{
"tour": {
"title": "Highlands & Islands of Scotland",
"code": "MER-SCO-10",
"departureType": "Fixed",
"startDate": "2026-09-08",
"endDate": "2026-09-17",
"durationDays": 10
}
}
departureType is an enum — Fixed | Anyday | Seasonal — and is required with no default. It determines the content source at transformation time (departure-level content for Fixed; item-level content for Anyday/FIT) and the layout the template selects. Defaulting it would hide data bugs: the reference transformation deliberately emits a placeholder that fails validation when the value cannot be resolved, so the problem surfaces loudly instead of silently rendering the wrong layout.
itinerary.days vs services[] — both optional
A proposal can be day-driven (escorted touring, cruising: itinerary.days[], each with day number, title, body, meals, an open dayType classification, and optional cruise port {name, code, arrive, depart, tenderRequired} metadata) or service-driven (FIT: services[] entries with serviceType, dates, nights, and vesselName/route for named conveyances, plus serviceDates[] summary rows for a journey overview).
Neither array is required. The explicit switch is meta.itineraryMode (days | services | both): templates switch on it rather than inferring from array lengths, and fall back to data presence when it is absent. Cruise documents commonly carry both — port-call days and the cruise service entry.
stayUnits[] — assigned and guaranteed allocations
Stay units cover cruise cabins, rail sleeper berths, and similar allocated inventory:
{
"stayUnits": [
{
"category": { "code": "BX", "name": "Balcony Stateroom" },
"deck": "Deck 7",
"unitNumber": "7042",
"occupancy": 2,
"fareCode": "FJORD27"
},
{
"category": { "code": "OV", "name": "Ocean View Stateroom" },
"unitNumber": null,
"occupancy": 2,
"fareCode": "FJORD27-GTY"
}
]
}
unitNumber: null has defined meaning: a guaranteed (GTY) allocation — the category is sold, the specific unit is assigned later. Templates render a "Guarantee" treatment instead of an empty cell. deck is optional for the same reason.
pricing — numeric, typed, and firewalled
All monetary values in pricing are numbers in meta.currency; display formatting happens at render time from meta.locale. The block carries summary figures (pricePerGuest, singleSupplement, grandTotal), package-level rows (pricingPackages), group-size tiers, discounts as records, and two structures worth detail:
paymentSchedule[] is N labelled installments, not a deposit/balance binary. deposit and balance exist as summary conveniences, but the schedule itself is an open list — cruise and hybrid programmes commonly carry three or more installments:
{
"paymentSchedule": [
{ "label": "Deposit", "amount": 600, "dueDate": "2026-06-30" },
{ "label": "Final balance", "amount": 5790, "dueDate": "2026-07-10" }
]
}
priceLines[] is typed line-level pricing. Each line carries a required role that drives template treatment:
| Role | Meaning |
|---|---|
fare | Base price lines |
tax_fee | Port taxes, government fees |
gratuity | Service charges |
credit | Value in the guest's favour (e.g. onboard credit) — negative totals |
insurance | Travel protection |
addon | Optional extras priced separately |
discount | Promotional reductions — negative totals |
{
"priceLines": [
{ "role": "fare", "description": "Cruise fare, Balcony Stateroom 7042 (2 guests)", "quantity": 2, "unitPrice": 2899, "total": 5798, "displayPrice": true },
{ "role": "tax_fee", "description": "Port taxes and government fees", "quantity": 4, "unitPrice": 95, "total": 380, "displayPrice": true },
{ "role": "credit", "description": "Onboard spending credit, £75 per guest", "quantity": 4, "unitPrice": -75, "total": -300, "displayPrice": true }
]
}
displayPrice: false renders a line without its monetary value (folded into a package price). Price lines are subject to the data-level firewall: they are emitted only for actions that request them, and they never carry commission fields — commission vocabulary exists only in the commission family.
Travelers
travelers[] holds named passenger records (firstName, lastName, type: adult|child|infant). The displayed traveler count is always derived: the count of travelers[] when passenger records exist, otherwise a group-size field resolved into pricing.travelers at transformation time. It is never hand-entered.
booking_confirmation
Required: meta, booking, tour, contact, footer. This is its own contract — confirmation-first — not a proposal subset. Its distinctive blocks: booking {reference, confirmedDate, status}, whatHappensNext[] (numbered next steps), financialSummary, howToPay {body, paymentUrl}, and serviceConfirmations[] (supplier references). An optional condensed itinerary summary is allowed; full day content is not required.
{
"booking": { "reference": "MER-2026-0412", "confirmedDate": "2026-05-18", "status": "Confirmed" },
"financialSummary": {
"grandTotal": 6013,
"amountPaid": 750,
"balance": 5263,
"currency": "GBP",
"paymentSchedule": [
{ "type": "Deposit", "amount": 750, "dueDate": "2026-06-30", "amountPaid": 750, "balanceDue": 0 },
{ "type": "Final balance", "amount": 2663, "dueDate": "2027-03-16", "amountPaid": 0, "balanceDue": 2663 }
]
}
}
Note the payment-schedule shape difference: guest proposals use {label, amount, dueDate}; confirmation and finance documents use {type, amount, dueDate, amountPaid, balanceDue} — both shapes exist by design.
invoice
Required: meta, booking, billTo, invoice, lineItems, totals, footer. The universal billing document, deliberately ungated (offered on every channel). Optional: travelers, credits, discounts, paymentSchedule.
{
"lineItems": [
{ "description": "Canadian Rockies by Rail - SilverScenic Service, Twin Share", "category": "Tour", "quantity": 2, "unitPrice": 2750, "total": 5500 }
],
"totals": { "subtotal": 6013, "credits": 0, "discounts": 0, "grandTotal": 6013, "amountPaid": 750, "balanceDue": 5263, "currency": "GBP" }
}
There are no commission fields anywhere in this schema — trade bookings get this same invoice, and commission detail goes on the commission invoice instead.
commission_invoice
Required: meta, booking, billTo, lineItems, agent. The agent invoice, trade-gated. Its structure converged independently across two established multi-brand operators — the baseline preserves those field names exactly. This is the only place (together with commission_statement) where commission vocabulary exists:
agentis required:{name, abn, address, reference, contactName, email, phone}.- Each line item extends the invoice line with
commissionPercent,commissionAmount, andcommissionGroup. commissionSummaryaggregates by group:tourCommission,accommodationCommission,airfareCommission,nonCommissionable,totalCommission,commissionGST,totalPayable.
{
"agent": { "name": "Compass Rose Travel Partners", "reference": "CRT-88412", "contactName": "Daniel Reyes" },
"lineItems": [
{ "description": "Canadian Rockies by Rail - SilverScenic Service, Twin Share", "quantity": 2, "unitPrice": 3450, "total": 6900, "commissionPercent": 12, "commissionAmount": 828, "commissionGroup": "Tour" }
],
"commissionSummary": { "totalCommission": 871, "commissionGST": 0, "totalPayable": 871 }
}
Other document types
The remaining schemas follow the same conventions (shared meta, numeric pricing, nullable optionals):
| Schema | Core blocks | Notable optional blocks |
|---|---|---|
cancel_invoice-1.0 | meta, booking, billTo, cancellation, refund, footer | cancellationCharges (banded charges), paymentSchedule |
commission_statement-1.0 | booking, agent (required), estimatedCommission, summary | optionExpiry, depositInfo, disclaimer |
financial_summary-1.0 | meta, booking, totals, footer | lineItems, payments[] {date, method, amount, reference}, paymentSchedule |
final_travel_docs-1.0 | meta, tour, contact, footer | travelers, itinerary with timings, operational accommodations, flights, serviceConfirmations, emergencyContacts {name, phone, hours}, importantInfo {title, body}, packingNotes, tourLeader |
supporting_document_example-1.0 | Flat: booking_reference, passengers[] {name}, total_cost, currency, issue_date, certificate_number | Replace wholesale per regulatory scheme |
Related pages
- How these fields are populated from Salesforce: Data transformation
- How templates bind and guard these fields: Templates
- How datasets are validated on every change: QA test harness