Skip to main content

Journeys API reference

Every endpoint currently exposed by the Edge Journeys API. All routes are versioned under:

/api/journeys/v1

The same contract is available machine-readable: OpenAPI 3.1 specification — import it into Postman, generate a client, or hand it to an AI coding agent.

Examples preserve the Meridian Voyages cruise reference implementation. The same API also supports group-touring catalogs; neutral route aliases and touring-specific semantics are called out below.

Conventions

Tenant scoping. Every request requires a tenantId — as a query parameter for GET/DELETE, or a body field for POST/PUT/PATCH. Requests without one receive 400. Unknown tenants receive 404.

Response envelope. Successful responses wrap payloads in a data key:

{ "data": { } }

Basket lifecycle. A basket is a short-lived commerce session, not a persistent cart:

PropertyBehaviour
ExpiryBaskets expire 15 minutes after creation. Expired baskets return 404; the UI should recreate the basket and replay selections if a guest returns late.
Statusopenpriced (after a pricing calculation) → booked (after checkout). Status only moves forward.
Inventory holdsSelecting an inventory-backed room or cabin places a temporary inventory hold with its own expiry, carried through checkout. The exact hold granularity follows the configured catalog model.
One booking per basketA basket can be booked exactly once. A second POST /bookings on the same basket returns 409 — double-submission cannot create a duplicate itinerary.

Idempotency and retries. GET requests are always safe to retry. PUT and PATCH requests are idempotent by design (they replace state). The two money-adjacent operations have explicit protection: booking creation is guarded by basket status (409 on replay, as above), and POST /payment-requests refreshes the existing open payment request for the basket rather than creating a duplicate. An explicit Idempotency-Key request header (as used by Edge Webforms) is being extended to the Journeys booking and payment endpoints during private beta.

Currency and localization. Every pricing operation is explicit about currency (currency_code on the basket, currency on catalog reads) — multi-currency is first-class. Catalog content (trip names, room or cabin categories, activities) is served in the language of the tenant's product catalog. Multi-language catalog delivery — a language parameter resolving localized content per request — is scoped per tenant during onboarding for markets that require it; raise it with your implementation team as part of private beta configuration.

Neutral catalog routes. New integrations should use /catalog/trips and /catalog/rooms, which speak a product-neutral dialect: tripCode, tripName, and roomCode, with vessel and deck fields present only when the product actually has them. The original /catalog/voyages and /catalog/cabins routes are unchanged and keep the established voyageCode, voyageName, and cabinNumber wire fields for existing integrations.

Error contract. Errors return a JSON body with an error message and a meaningful status:

StatusWhen
400Missing/invalid parameters (tenantId, required fields)
403Basket belongs to a different tenant; disabled feature
404Tenant not configured; basket or payment request not found
409Basket already booked; payment not completed before booking
422Booking attempted on a basket with no component selections
429Rate limit exceeded (quote endpoint)
502An upstream platform service (KTAPI, Basket Service) returned an error
503Tenant's live configuration is incomplete; feature not configured

Catalog

List trips

GET /api/journeys/v1/catalog/trips?tenantId=meridian
GET /api/journeys/v1/catalog/voyages?tenantId=meridian # legacy cruise dialect

Returns every trip (Kaptio package departure) in the tenant's configured catalog, with departure dates and from-prices per currency. In live mode the source depends on live.catalog_model: cruise uses cruise details and cabin inventory; touring prices required room options from the package. Results are cached.

{
"data": [
{
"tripCode": "ANT-2728",
"tripName": "Antarctic Horizons",
"region": "Antarctica",
"durationDays": 15,
"vesselName": "Meridian Explorer",
"departureDates": [
{ "label": "Nov 2027", "date": "2027-11-18" },
{ "label": "Dec 2027", "date": "2027-12-14" }
],
"fromPrice": 13995,
"currency": "USD",
"fromPriceUSD": 13995,
"fromPriceAUD": 21295,
"fromPriceGBP": 11095,
"fromPriceEUR": 12895,
"fromPriceNZD": 22995,
"fromPriceCAD": 18995,
"sellingStatus": "Open",
"season": "2027-2028"
}
]
}

fromPrice is null (never 0) when no valid price could be resolved for a trip. vesselName appears only for products operated on a vessel — a group-touring trip carries no vessel fields at all. The legacy /catalog/voyages route returns the same rows with the original voyageCode, voyageName, and shipName field names.

List room categories

GET /api/journeys/v1/catalog/categories?tenantId=meridian

Returns room or cabin categories available across the catalog, grouped into the tier structure defined in journey.yaml (catalog.category_tiers). Cruise rows describe cabin grades. Touring rows describe required package room options, commonly shared and single occupancy.

{
"data": [
{
"kaptioName": "Balcony Stateroom C",
"label": "Balcony Stateroom",
"tier": "Balcony",
"tierGroup": "Staterooms",
"isSolo": false,
"isPopular": true,
"sortOrder": 3
}
]
}

List rooms

GET /api/journeys/v1/catalog/rooms?tenantId=meridian&tripCode=ANT-2728&categoryKaptioName=Balcony%20Stateroom%20C&currency=USD
GET /api/journeys/v1/catalog/cabins?tenantId=meridian&voyageCode=ANT-2728&categoryKaptioName=Balcony%20Stateroom%20C&currency=USD # legacy cruise dialect

Returns bookable room options for a trip and category, with live availability and per-person pricing. In cruise mode, a row identifies a physical cabin with its deck and location. In touring mode, a row represents a room configuration such as Twin or Single, and no deck field is returned.

Query parameterRequiredDescription
tenantIdYesTenant slug
tripCodeYesTrip reference from the catalog response (the legacy route uses voyageCode)
categoryKaptioNameYesCategory kaptioName from the categories response
currencyYesISO 4217 currency code
{
"data": [
{
"id": 512,
"roomCode": "512",
"deck": 5,
"location": "Midship",
"priceAmount": 14495,
"priceCurrency": "USD",
"priceBasis": "per person",
"berthsAvailable": 2,
"componentId": "a1B0000000000AAAA",
"optionId": 3,
"itemOptionId": "a1C0000000000AAAA",
"cabinTypeId": "a1D0000000000AAAA"
}
]
}

The selection-context fields are opaque — pass the values returned for that row back verbatim via Update components. Cruise rows can include cabinTypeId; touring rows are selected through their package component, option, and item-option identifiers.

List activities

GET /api/journeys/v1/catalog/activities?tenantId=meridian&voyageCode=ANT-2728&currency=USD

Returns optional package activities for a trip (e.g. a guided excursion or workshop) with pricing and remaining availability. Externally quoted ancillaries are separate; see Extending journeys with third-party services.

{
"data": [
{
"activityName": "Sea Kayaking Programme",
"priceAmount": 1250,
"priceCurrency": "USD",
"unitsAvailable": 8
}
]
}

Configuration

Get journey config

GET /api/journeys/v1/config?tenantId=meridian

Returns the tenant's validated journey.yaml — steps, channels, branding, behavior, and catalog metadata. The hosted tenant UI calls this on load; a custom frontend can use it to drive step order and branding from the same configuration. See the journey.yaml reference for the shape.


Advisors (B2B)

Look up advisor

POST /api/journeys/v1/advisors/lookup

Identifies a travel advisor before a B2B booking flow begins (channels with requires_advisor_id: true).

{
"tenantId": "meridian",
"iata_number": "91234567",
"email": "advisor@agency.example"
}

Response is a discriminated union on status:

{
"data": {
"status": "matched",
"account": {
"account_id": "0010000000000AAAA",
"agency_name": "Polar Travel Co",
"iata_number": "91234567",
"email": "advisor@agency.example",
"country": "US"
}
}
}

status is one of matched, not_found, or ambiguous (multiple accounts share the IATA number). For not_found and ambiguous, account is absent.


Baskets

Create basket

POST /api/journeys/v1/baskets

Starts a commerce session for a package departure. Returns 201.

{
"tenantId": "meridian",
"package_id": "a0P0000000000AAAA",
"departure_id": "a0Q0000000000AAAA",
"occupancy": [{ "adults": 2, "children": 0 }],
"channel_id": "b2c-direct",
"currency_code": "USD"
}
FieldTypeRequiredDescription
package_idstringYesKTAPI package ID (from the trip catalog)
departure_idstringYesDeparture ID for the selected date
occupancyarrayYesOne entry per room or cabin: { adults, children }
channel_idstringYesJourney channel ID (mapped to a Salesforce channel via live.channel_map)
currency_codestringYesISO 4217 currency
account_idstringNoSalesforce account (defaults to live.default_account_id)
tax_profile_idstringNoOverrides live.tax_profile_id
service_level_idstringNoOverrides live.service_level_id
{
"data": {
"id": "bkt_01HZXX0000000000000000",
"tenant_id": "meridian",
"channel_id": "b2c-direct",
"package_id": "a0P0000000000AAAA",
"departure_id": "a0Q0000000000AAAA",
"currency_code": "USD",
"occupancy": [{ "adults": 2, "children": 0 }],
"components": [
{ "component_id": "a1B0000000000AAAA", "selections": [] }
],
"travellers": [],
"promotions": [],
"status": "open",
"created_at": "2027-06-01T10:00:00Z"
}
}

Get basket

GET /api/journeys/v1/baskets/:basketId?tenantId=meridian

Returns the current basket state — components, selections, travellers, promotions, status.

Delete basket

DELETE /api/journeys/v1/baskets/:basketId?tenantId=meridian

Abandons the session. Returns 204 with no body.

Update component selections

PATCH /api/journeys/v1/baskets/:basketId/components

Sets option selections on basket components — this is how an inventory-backed room, cabin, or other package option is added. The selection context comes verbatim from the rooms response.

{
"tenantId": "meridian",
"components": [
{
"component_id": "a1B0000000000AAAA",
"selections": [
{
"option_id": "3",
"item_option_id": "a1C0000000000AAAA",
"cabin_id": "512",
"travellers": [1, 2]
}
]
}
]
}

Returns the updated components array.

Update travellers

PUT /api/journeys/v1/baskets/:basketId/travellers

Replaces the traveller list. One traveller should carry contact: true as the lead passenger.

{
"tenantId": "meridian",
"travellers": [
{
"id": "1",
"first_name": "Jane",
"last_name": "Smith",
"email": "jane@example.com",
"phone": "+1 555 0100",
"date_of_birth": "1980-04-12",
"contact": true
},
{
"id": "2",
"first_name": "Alex",
"last_name": "Smith",
"email": "alex@example.com"
}
]
}

Returns the updated travellers array.


Promotions

Apply promotion

POST /api/journeys/v1/baskets/:basketId/promotions
{ "tenantId": "meridian", "code": "EARLYBIRD15" }

Validates the code against the platform promotion engine and attaches it to the basket. Returns the updated basket. Invalid or non-combinable codes return an upstream validation error.

Remove promotion

DELETE /api/journeys/v1/baskets/:basketId/promotions/:code?tenantId=meridian

Removes the code and returns the updated basket.


Pricing and payments

Get pricing

GET /api/journeys/v1/baskets/:basketId/prices?tenantId=meridian

Calculates the full price for the current basket state — selections, occupancy, and applied promotions — via the platform pricing engine.

{
"data": {
"subtotal": 28990,
"taxes": 2029,
"total": 31019,
"currency": "USD",
"deposit_amount": 6204,
"deposit_percentage": 20,
"balance_due": 24815,
"balance_due_date": "2027-08-20",
"line_items": [
{ "label": "Balcony Stateroom C — Cabin 512 x 2", "amount": 28990, "currency": "USD" }
]
}
}

Commission fields are optional. An eligible trade basket can additionally return:

{
"commission_amount": 3101.9,
"commission_lines": [
{ "label": "Agency commission", "amount": 3101.9, "currency": "USD" }
]
}

It is present only when KTAPI returns positive price lines with applicable_to: reseller_commission for the basket's account and channel. Journeys aggregates those lines across passengers without adding them to or subtracting them from subtotal, taxes, total, or line_items.

commission_lines groups the same KTAPI commission lines by display label. It does not allocate commission to individual line_items; customer sales lines remain separate.

Commission terminology

TermMeaning in Journeys
Sales amountCustomer-facing price. KTAPI price lines with applicable_to: sales contribute to total and line_items.
Agent or reseller commissionCommercial entitlement produced by the matching reseller agreement and commission-group assignment. It is exposed as aggregate commission_amount when present.
NetA channel-specific commercial view often calculated as sales less agent commission. BasketPricing does not expose net_amount; do not treat supplier cost, payment settlement, or a sales line as the net value.

Commission is configured in Kaptio, not in journey.yaml. Commission groups classify commissionable packages, price categories, items, or fees. A reseller agreement assigns rates to those groups and is eligible only when its account and channel assignments match the basket. A B2B journey therefore needs the advisor/account resolution and live.channel_map context to match the intended agreement.

Quote and select an external service

External services are configured per tenant and remain outside Basket Service. Quote requests accept only basket traveller IDs and bounded scalar metadata; Journeys loads trip, departure, currency, and date-of-birth context from the basket.

POST /api/journeys/v1/baskets/:basketId/external-ancillaries/:providerId/quotes?tenantId=meridian
{
"traveller_ids": ["1", "2"],
"metadata": { "market": "AU" }
}

The response contains normalized items with a stable provider_ref and an opaque, basket-bound quote_token. Select one item:

POST /api/journeys/v1/baskets/:basketId/external-ancillaries/:providerId/selections?tenantId=meridian
{
"quote_token": "{opaque-token}",
"item_id": "standard-cover",
"quantity": 2,
"traveller_ids": ["1", "2"]
}

The response includes a normalized selection and selection_token. The signed selection fixes its amount, currency, source, provider reference, traveller applicability, and bounded metadata.

To include external selections in the review presentation, repeat externalAncillaryToken on the pricing request:

GET /api/journeys/v1/baskets/:basketId/prices?tenantId=meridian&externalAncillaryToken={token}

The response keeps total authoritative for the Kaptio basket and adds external_ancillary_lines, external_ancillary_total, and display-only presentation_total.

Get payment schedules

GET /api/journeys/v1/baskets/:basketId/payment-schedules?tenantId=meridian

Returns the deposit/balance options for the basket, driven by the tenant's payment schedule rules.

{
"data": [
{ "id": "ps_deposit", "label": "Deposit (20%)", "amount": 6204, "due_date": "2027-06-01", "type": "deposit" },
{ "id": "ps_balance", "label": "Balance", "amount": 24815, "due_date": "2027-08-20", "type": "balance" }
]
}

Create payment request

POST /api/journeys/v1/baskets/:basketId/payment-requests

Creates a payment request against a chosen schedule. Returns 201.

{
"tenantId": "meridian",
"pay_per_person": false,
"passenger_payments": [
{ "payment_schedule_rule_id": "ps_deposit" }
]
}
FieldTypeRequiredDescription
passenger_paymentsarrayYesOne entry per payment; passenger_id optional when paying per person
pay_per_personbooleanYesSplit the payment per passenger
{
"data": {
"id": "pr_01HZXX0000000000000000",
"basket_id": "bkt_01HZXX0000000000000000",
"amount": 6204,
"currency": "USD",
"status": "pending",
"payment_url": "https://pay.example.com/session/abc123"
}
}

Create embedded payment session

POST /api/journeys/v1/baskets/:basketId/payment-requests/:paymentRequestId/embed-session
{ "tenantId": "meridian", "payment_option": "deposit" }

Edge Pay creates the hosted session. When tenant payment methods carry a surcharge, the response includes method-specific previews calculated by Edge Pay from the current payment.yaml:

{
"data": {
"session_id": "session_123",
"embed_url": "https://pay.example.com/pay/session_123",
"expires_at": "2027-06-01T12:00:00Z",
"surcharge_options": [
{
"method_id": "card",
"method_label": "Credit card",
"percentage": 1.5,
"amount": 93.06,
"currency": "USD",
"source": "edge_pay",
"authoritative": false
}
]
}
}

Complete payment

PUT /api/journeys/v1/baskets/:basketId/payment-requests/:paymentRequestId/complete

Marks the payment request complete after Journeys verifies the hosted session server-to-server with Edge Pay. The browser supplies only the session ID.

{ "tenantId": "meridian", "session_id": "session_123" }

Returns the payment request with status: "completed". If Edge Pay recorded a surcharge, surcharge_breakdown contains its authoritative amount and currency.

Payment-method surcharges

Payment-method surcharges are owned and applied by Edge Pay. Each enabled payment method in the tenant's payment.yaml can define surcharge_percent; Edge Pay uses the selected method and server-authoritative amount breakdown to show and collect the exact fee.

Journeys does not add a payment surcharge to BasketPricing, and consumers must not model the fee as a KTAPI basket price line. surcharge_options on the embed session are previews calculated by Edge Pay. The selected method and completed transaction remain authoritative; surcharge_breakdown.authoritative is true only after completion.

An itinerary-level KTAPI Fee line is different: it is part of itinerary pricing and can affect basket sales totals regardless of how the guest pays. A payment-method surcharge is selected and calculated at payment time by Edge Pay and is governed by payment.yaml.


Checkout

Create booking

POST /api/journeys/v1/baskets/:basketId/bookings

Converts the basket into a booking — a Kaptio itinerary in the tenant's Salesforce org. Returns 201.

{
"tenantId": "meridian",
"external_ancillary_selection_tokens": ["{selection-token}"]
}
{
"data": {
"itinerary_id": "a0R0000000000AAAA",
"booking_reference": "MER-104233",
"status": "Booked"
}
}

Guards:

StatusWhen
422The basket has no required component selections (for example, no room chosen)
409The basket is already booked, or a required payment is not completed
501External selections were supplied but the configured Edge Composite persistence boundary is unavailable
502Booking or external ancillary persistence failed upstream

When external selections are present, Journeys verifies them and checks persistence configuration before creating the booking. After the platform returns the itinerary ID, Journeys writes the normalized selections through Edge Composite. A write failure is returned explicitly; retry resumes the booking and repeats the idempotent ancillary write.

Booking lifecycle scope

POST /baskets/:basketId/bookings creates a new itinerary. The current public Journeys API does not expose execution routes to amend an existing itinerary, cancel a booking, or issue a refund.

The underlying platform includes pricing, cancellation-rule, supplier-fee, and orchestration capabilities that can support future lifecycle routes. Those capabilities are an extension surface, not callable Journeys execution endpoints. Do not build a client around an amendment, cancellation, or refund path until it appears in this reference and the OpenAPI contract.


AI quote letter

Generate quote letter (SSE)

POST /api/quote

Streams a personalised welcome/quote letter for the confirmation screen. Only available when the tenant has behavior.ai_quote_letter: true in journey.yaml. Note: this route is mounted at /api/quote, not under /api/journeys/v1.

{
"tenantId": "meridian",
"prompt": "Write a warm welcome letter for Jane Smith, booked on Antarctic Horizons departing 18 Nov 2027..."
}

The response is a raw server-sent-event stream (Content-Type: text/event-stream) of Anthropic Messages API events — the UI renders content_block_delta text as it arrives, and falls back to static copy on any failure.

Guardrails on this public endpoint:

GuardLimit
Prompt size6,000 characters
Response size500 tokens
Per-IP rate limit5 requests / minute
Per-tenant rate limit30 requests / minute
Feature gate403 unless enabled in journey.yaml

Health and operations

MethodPathPurpose
GET/healthService liveness
GET/readyReadiness (config repo reachable)
GET/api/journeys/v1/health-checkAPI-level health ({ "status": "ok", "api": "journeys-v1" })

Prometheus metrics (baskets created, bookings completed, quote outcomes — all labelled per tenant and channel) are exposed on a separate metrics port and are not part of the public surface.