Skip to main content

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
}
}
FieldMeaning
localeBCP 47 tag. Drives date, number, and currency formatting at render time. Required.
currencyISO 4217 code for all monetary values in the document. Required.
brandIdentity only: {code, name}. Presentation (colours, fonts, logos) resolves from tenant configuration by brand code — hex values never travel in document data. Required.
channelThe sales channel and whether it is a trade channel (isTrade).
revisionMonotonic revision number of this document within its itinerary.
supersedesDocumentIdID 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:

RoleMeaning
fareBase price lines
tax_feePort taxes, government fees
gratuityService charges
creditValue in the guest's favour (e.g. onboard credit) — negative totals
insuranceTravel protection
addonOptional extras priced separately
discountPromotional 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:

  • agent is required: {name, abn, address, reference, contactName, email, phone}.
  • Each line item extends the invoice line with commissionPercent, commissionAmount, and commissionGroup.
  • commissionSummary aggregates 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):

SchemaCore blocksNotable optional blocks
cancel_invoice-1.0meta, booking, billTo, cancellation, refund, footercancellationCharges (banded charges), paymentSchedule
commission_statement-1.0booking, agent (required), estimatedCommission, summaryoptionExpiry, depositInfo, disclaimer
financial_summary-1.0meta, booking, totals, footerlineItems, payments[] {date, method, amount, reference}, paymentSchedule
final_travel_docs-1.0meta, tour, contact, footertravelers, itinerary with timings, operational accommodations, flights, serviceConfirmations, emergencyContacts {name, phone, hours}, importantInfo {title, body}, packingNotes, tourLeader
supporting_document_example-1.0Flat: booking_reference, passengers[] {name}, total_cost, currency, issue_date, certificate_numberReplace wholesale per regulatory scheme