Skip to main content
@voyant-travel/charters is the charters vertical for OTA, tour-operator, and DMC deployments. It models small-scale and specialized charter inventory that does not fit the cruise schema: per-suite flat pricing, whole-yacht charter under a MYBA contract, and APA (Advance Provisioning Allowance) as a first-class concern. Charters exist because the cruises module deliberately carves out yacht-style products. The luxury yacht brands in scope (Aman, Four Seasons, Ritz-Carlton, SeaDream, Abercrombie & Kent, Orient Express) position themselves as luxury hospitality, not cruise lines. The booking unit is a named suite at a flat price, not a cabin grade times an occupancy variant. Multi-currency pricing is published natively per suite, and whole-yacht charter exists as a parallel mode where one party books the entire vessel under a negotiated contract. Charters may be sourced supplier inventory or operator and DMC-owned operations. They are an inventory and operations capability inside the target scenarios, not a separate charter-operator implementation. The package is opt-in, and it does not depend on or share schema with cruises.

Key concepts

These terms come from the shared glossary and the charter domain.
  • Charter product. The brand-and-yacht offering a customer thinks of as the product (for example “Aman at Sea, Amangati”). Brands with multiple ships have multiple products. Each brand is a Supplier row linked from the product.
  • Charter voyage. A dated voyage of a charter product on a specific yacht, with embark and disembark ports, departure and return dates, the booking modes it offers, and (for whole-yacht mode) the multi-currency charter price.
  • Booking modes. Two modes coexist on a voyage, discriminated at booking time rather than at product-definition time. Per-suite books one named suite at a flat, multi-currency price. Whole-yacht books the entire vessel for a date range at a single charter rate under a MYBA contract.
  • Charter suite. The per-suite pricing row on a voyage: suite code and name, category tier, multi-currency flat price (USD, EUR, GBP, AUD stored as first-class columns), optional per-currency port fees, and availability. There are no fare codes and no occupancy variants; a suite costs the same whether two or three guests sleep in it.
  • MYBA contract. Whole-yacht charters in the luxury segment universally operate under the MYBA Charter Agreement (or equivalent). It specifies embarkation times and locations, the charter area, the base fee plus APA, force majeure, the captain’s authority, insurance, and dispute resolution. The module generates the contract from a seed template at booking time.
  • APA (Advance Provisioning Allowance). A deposit a charterer pays on top of the charter fee (typically 25 to 30 percent) that the captain spends on fuel, port fees, food, and shoreside transport during the charter. Unspent funds are refunded; overspend is invoiced at the end. It is an expense pass-through, not a markup, so it gets its own line item and lifecycle.
  • Broker mediation. Most charter inquiries are conversational, not click-to-pay. Voyages and suites can carry an appointmentOnly flag that routes the flow through request, broker review, quote, and confirm instead of self-serve checkout. The flag can be set per voyage, per suite, or per booking mode.

What it owns

  • Five core tables: charter products, charter yachts, charter voyages, charter suites (per-suite pricing rows), and charter schedule days (a flat per-voyage itinerary). Notably fewer than cruises, because charter pricing is flat and the schema has no cabin categories, decks, or pricing grid.
  • Native multi-currency storage. The four primary currencies (USD, EUR, GBP, AUD) are first-class columns on voyages and suites, populated at sync time; tertiary currencies are handled by display-time FX.
  • chartersService for product, voyage, yacht, suite, and schedule reads and writes, and pricingService for per-suite and whole-yacht quotes plus APA math and currency resolution.
  • A MYBA-contract generation service that wraps the legal/contracts module.
  • Admin routes (/v1/admin/charters/*) and inquiry-shaped public routes (/v1/public/charters/*).
  • A booking extension (booking_charter_details) with separate per-suite and whole-yacht creation helpers and APA reconciliation endpoints.
  • A swappable CharterAdapter contract, registry, memoizing decorator, and MockCharterAdapter for external charter inventory.
What it does not own: brand-specific connectors (those live in Connect or an operator’s own adapter), sync scheduling and polling, provider authentication, and real-time hold or live-book orchestration beyond the adapter commit boundary. Bareboat, day charters, and mid-market brokerage are out of scope entirely.

Working with it

Create a voyage and its suites

import { chartersService } from "@voyant-travel/charters"

const voyage = await chartersService.upsertVoyage(db, {
  productId,
  yachtId,
  voyageCode: "RC-EVR-2026-007",
  departureDate: "2026-06-14",
  returnDate: "2026-06-21",
  nights: 7,
  bookingModes: ["per_suite", "whole_yacht"],
  wholeYachtPriceEUR: "980000.00",
})

await chartersService.replaceVoyageSuites(db, voyage.id, [
  { suiteCode: "OWNERS", suiteName: "Owners Suite", suiteCategory: "owners", priceEUR: "62000.00", maxGuests: 2 },
  { suiteCode: "DECK2-PORT", suiteName: "Deck 2 Stateroom", suiteCategory: "deluxe", priceEUR: "28000.00", maxGuests: 2 },
])

Quote a suite or a whole-yacht charter

Quotes return native currency. Display-time conversion to other currencies is a future FX module’s job, the same convention cruises uses.
import { pricingService } from "@voyant-travel/charters/service"

const suiteQuote = await pricingService.quotePerSuite(db, { suiteId, currency: "EUR" })
// { suiteId, suiteName, currency, suitePrice, portFee, total }

const charterQuote = await pricingService.quoteWholeYacht(db, { voyageId: voyage.id, currency: "EUR" })
// { voyageId, currency, charterFee, apaPercent, apaAmount, total }

Book a suite

A charter booking is a booking. The two modes are different enough that there are two distinct entry points rather than one polymorphic helper. Each does its work in one transaction: resolve the quote, create the booking, insert travelers, and upsert the charter detail snapshot.
import { chartersBookingService } from "@voyant-travel/charters/booking-extension"

const booking = await chartersBookingService.createPerSuiteBooking(db, {
  voyageId: voyage.id,
  suiteId,
  currency: "EUR",
  passengers: [{ firstName: "Ada", lastName: "Byron", travelerCategory: "adult", isPrimary: true }],
  contact: { personId },
  mode: "reserve",
})

Book a whole-yacht charter

Whole-yacht bookings also generate the MYBA contract. Contract generation must succeed for the booking to land: the contract is the agreement, and without it the charter cannot legally proceed, so there is no “book now, contract later” path.
const charter = await chartersBookingService.createWholeYachtCharterBooking(db, {
  voyageId: voyage.id,
  currency: "EUR",
  charterer: { organizationId },
  embarkationLocationOverride: "Athens",
  generateMybaContract: true, // wraps legal.contractsService, threads contractId into the snapshot
})
APA is collected and reconciled through dedicated endpoints (.../charter-details/apa/payment and .../charter-details/apa/reconcile), with the finance module recording the deposit, the post-charter refund as a credit note, and any overspend as an invoice line.

Register an external adapter

External charters reach the module through a registered adapter, the same pattern cruises uses. The adapter is swappable; the framework never imports a concrete adapter.
import { createApp } from "@voyant-travel/hono"
import { chartersHonoModule, registerCharterAdapter } from "@voyant-travel/charters"
import { createConnectCharterAdapter } from "@voyant-travel/charters-adapter-connect"

registerCharterAdapter(createConnectCharterAdapter({ apiKey: env.VOYANT_CONNECT_API_KEY }))

export const app = createApp({ modules: [chartersHonoModule] })
Admin list and detail routes then interleave local products with external ones. Booking creation against an external voyage commits upstream through adapter.createPerSuiteBooking() or adapter.createWholeYachtBooking() first, then snapshots the result.
  • bookings. The booking_charter_details extension and the per-suite and whole-yacht helpers wrap the bookings module’s create path, state machine, and PII handling.
  • legal/contracts. MYBA generation wraps legal.contractsService.createContract; the MYBA template ships as a seed contract template at module install, forkable per brand. Whole-yacht bookings get a contractId on the charter detail row.
  • finance. APA is a finance concern: quoteWholeYacht returns the breakdown, and finance records the deposit, refund, and topup as line items. Charters does not reinvent any of it.
  • suppliers and Places. Each yacht brand is a supplier row linked from the product; ports are facility (Place) rows referenced by soft foreign key.
  • CRM. Charterers are referenced by personId or organizationId; charter-specific contact fields live in the bookings traveler-detail extension, not in charter tables.
  • notifications. Pre-charter reminders (final payment, MYBA signature pending, preference form) flow through the notifications module on a schedule.
  • Connect. Voyant Connect is the default adapter Voyant’s templates wire up for external charter inventory; an agency that prefers its own connectivity implements the same CharterAdapter contract. See the Connect SDK.
Charters does not depend on cruises and shares no schema with it. What they share are conceptual primitives that already live in shared modules: ports, booking infrastructure, contract templates, person and organization references, pricing catalogs, and storage.

React package

@voyant-travel/charters-react is the charters client tier: headless data hooks and a fetch client at the root, ./hooks, and ./client (no styling peers), plus styled UI under ./ui and ./components/* (which add @voyant-travel/ui). It carries hooks for products, voyages, suites, schedule, per-suite and whole-yacht quoting, bookings, MYBA actions, and external adapter actions, and registry components such as a charter-product card, voyage detail, suite list, whole-yacht quote display, inquiry form, and external badge. Components render English by default. Wrap them in ChartersUiMessagesProvider and import only the locales you support to localize:
import { ChartersUiMessagesProvider } from "@voyant-travel/charters-react/ui"
import { chartersUiEn } from "@voyant-travel/charters-react/i18n/en"
import { chartersUiRo } from "@voyant-travel/charters-react/i18n/ro"

Next steps

Cruises

The sister module for cabin-grid cruise inventory.

Connect SDK

Sourcing external charter inventory through Connect.

Modules overview

How verticals compose into a Voyant app.

Glossary

The charter vocabulary in the shared domain language.