Skip to content

Booking Details API

Authenticated endpoint for retrieving comprehensive booking details including offer, product, and passenger information.

The Booking Details API provides an admin-only endpoint for accessing complete booking information scoped to specific markets and languages.

Authentication: Requires active admin session (web middleware + auth) and admin role.

Base URL Pattern: /api/{market}/{lang}/bookings/{bookingReference}

GET /api/{market}/{lang}/bookings/{bookingReference}

Section titled “GET /api/{market}/{lang}/bookings/{bookingReference}”

Get complete booking details by reference code.

Parameters:

Name In Type Required Description
market path string Yes Market code (case-insensitive)
lang path string Yes Language code (e.g., “en”, “es”, “ca”)
bookingReference path string Yes Booking reference code (e.g., “BK-ABC12345”)

Response: 200 OK

{
"data": {
"booking_reference": "BK-ABC12345",
"status": "in_progress",
"status_label": "In Progress",
"total_amount": "2500.00",
"deposit_amount": "980.00",
"currency": "EUR",
"currency_symbol": "",
"booked_at": "2024-12-15T10:30:00+00:00",
"number_of_travelers": 2,
"market_code": "es",
"customer_service_phone": "+34 919 49 45 52",
"footer_copyright": "© BY VOLARE SL. Todos los derechos reservados",
"offer": {
"id": 123,
"sku": "ES-5CMB10-CA1-2024-01-15",
"departure_date": "2024-01-15",
"return_date": "2024-01-26",
"trip_days": 12,
"final_price": "1300.00",
"marketing_price_per_pax": "650.00",
"room_type": "double",
"room_type_label": "Double Room",
"departure_airport": {
"iata_code": "BCN",
"name": "Barcelona-El Prat Airport",
"city": "Barcelona"
}
},
"product": {
"id": 10,
"sku": "ES-5CMB10-CA1",
"title": "Tour de Sri Lanka",
"subtitle": "Descobreix l'illa maragda",
"short_description": "Una aventura increible...",
"highlights": ["Sigiriya", "Kandy", "Yala"],
"itinerary": [
{
"day": 1,
"date": "2024-01-15",
"title": "Arrival in Colombo",
"details": "Arrive, meet your host, private transfer to the hotel.",
"image_url": "https://cdn.example.com/itineraries/day1.jpg",
"is_flight_day": false
}
],
"hero_image": "https://cdn.example.com/images/sri-lanka.jpg",
"trip_duration_days": 10
},
"passengers": [
{
"id": 1,
"first_name": "John",
"last_name": "Doe",
"full_name": "John Doe",
"age_category": "adult",
"date_of_birth": "1985-03-15",
"is_lead_passenger": true,
"email": "john@example.com",
"phone": "+34612345678"
}
],
"hotels": [
{
"name": "Cinnamon Lodge Habarana",
"city": "Habarana",
"nights": 2,
"check_in": "2024-01-15",
"check_out": "2024-01-17",
"image_urls": ["https://cdn.example.com/hotels/cinnamon.jpg"],
"is_upgrade": false
}
],
"flights": {
"cabin_class": "ECONOMY",
"outbound": {
"date": "2024-01-15",
"departure_time": "10:30",
"arrival_time": "23:45",
"departure_airport": { "iata_code": "BCN", "city": "Barcelona" },
"arrival_airport": { "iata_code": "CMB", "city": "Colombo" },
"segments": [
{
"flight_number": "EK186",
"airline_code": "EK",
"airline_name": "Emirates",
"departure_airport": { "iata_code": "BCN", "city": "Barcelona" },
"arrival_airport": { "iata_code": "DXB", "city": "Dubai" },
"departure_time": "10:30",
"arrival_time": "20:15"
}
],
"stopovers": [
{ "airport": { "iata_code": "DXB", "city": "Dubai" }, "layover_minutes": 120 }
],
"total_duration_minutes": 795
},
"inbound": {
"date": "2024-01-25",
"departure_time": "01:30",
"arrival_time": "12:15",
"departure_airport": { "iata_code": "CMB", "city": "Colombo" },
"arrival_airport": { "iata_code": "BCN", "city": "Barcelona" },
"segments": [],
"stopovers": [],
"total_duration_minutes": 780
}
},
"experiences": [
{
"name": "Visita guiada a la roca de Sigiriya",
"city": "Sigiriya",
"date": "2024-01-16",
"image_url": "https://cdn.example.com/activities/sigiriya.jpg",
"tier": "included"
},
{
"name": "Safari en jeep por el Parque Nacional de Yala",
"city": "Yala",
"date": "2024-01-17",
"image_url": "https://cdn.example.com/activities/yala-safari.jpg",
"tier": "extra"
}
],
"optional_services": [
{
"name": "Traslado privado aeropuerto - hotel",
"description": "Vehículo privado con conductor desde el aeropuerto de Colombo hasta el hotel."
}
],
"section_prices": {
"hotels": 0.0,
"experiences": 120.0,
"services": 60.0
}
}
}
{
"message": "Unauthenticated."
}
{
"message": "Access denied. Admin role required."
}

Returned when booking does not exist, or does not belong to requested market/locale.

{
"success": false,
"error": "booking_not_found",
"message": "Booking with reference 'XYZ' not found."
}
{
"success": false,
"error": "language_not_supported",
"message": "Language 'de' is not supported by market 'ES'. Supported languages: es, ca"
}

The flights field is populated from multiple sources in priority order:

Priority Source Cabin Class When Used
1 booking_upsells.flight_search_params BUSINESS Customer upgraded to business class
2 booking.flight_selection ECONOMY Customer selected economy flight during checkout
3 dynamic_flight_cache (itinerary_index=1) ECONOMY Legacy fallback for older bookings

The resource handles both enriched format (with nested airport objects containing iata_code and city) and legacy format (plain IATA code strings) for backward compatibility.

Each segment carries both airline_code (IATA code, e.g. EK) and airline_name (full carrier name, e.g. Emirates). For stored economy selections and business-upgrade legs, the airline is resolved per segment from the segments[*] entries (one per flight number), falling back to the leg’s distinct airlines/airline_names arrays by index and finally to the flight-number prefix. The per-segment source matters on multi-segment same-airline legs, where indexing the distinct arrays would mislabel later segments (e.g. flight 907 shown as 90). Cached economy legs resolve the name from the dynamic_flight_cache_segments.airline_name column, falling back to the operating carrier code.

offer.return_date (YYYY-MM-DD) and offer.trip_days (inclusive day count) are derived from the bound international flight via Offer::getTravelDates() — door-to-door, outbound departure day to return arrival-home day (same source as the booking emails). Both are null when no flight is bound (land-only / pre-flight); the frontend then falls back to the product.trip_duration_days land-tour estimate. product.trip_duration_days (itinerary nights + 1) remains unchanged and can under-report long-haul trips whose return leg spans an extra travel day.

In addition to the booking core fields, the response includes market-aware metadata used by the frontend:

Field Type Description
market_code string Lowercased market code for frontend routing (defaults to es)
customer_service_phone string | null Market customer-service phone number
footer_copyright string | null Footer copyright text, resolved from CmsFooter via the market’s default locale

The trip page doubles as the quotation page sent to clients. For pre-payment bookings it renders a price + deposit block, so the resource exposes a customer-facing status and a resolved deposit alongside the total.

Field Type Description
status string Customer-facing status (ClientBookingStatus value) — see below
status_label string Human label for the status (ClientBookingStatus::getLabel())
total_amount string Stored decimal (decimal:2) — the booking’s total price
deposit_amount string | null Resolved deposit (decimal:2 string) — persisted, derived, or null (see below)
section_prices object Per-section extra totals: { hotels, experiences, services } (floats)

status is a projection of the operational Booking::$status, computed via ClientBookingStatus::fromBookingStatus(). The raw operational status (the flight/land fulfillment pipeline) is never exposed. The client-facing values:

Value status_label Operational statuses projected onto it
in_progress In Progress Draft, Checkout
quote Quotation QuotationRequested, QuotationConfirmed
pending_payment Pending Payment PendingPayment, PaymentProcessing
confirmed Confirmed PendingFlightBooking, FlightBookingInProgress, FlightBookingFailed, FlightsConfirmed, PendingLandConfirmation, Confirmed, FullyPaid
balance_due Balance Due AwaitingBalance
completed Completed Completed
cancelled Cancelled Cancelled, Expired

Source: backend/app/Enums/ClientBookingStatus.php.

deposit_amount is resolved by BookingDetailsResource::resolveDepositAmount():

  1. Persisted deposit — if the booking has a stored deposit_amount column (set when payment is initiated), it is emitted verbatim.

  2. Derived deposit — otherwise, for pre-payment bookings without a persisted deposit (quote and in_progress), it is derived from the checkout snapshot via PaymentCalculatorService::depositFromCheckout():

    deposit = round(raw_flight_cost + margin% × total_price, 2)
    deposit = min(deposit, total_price)

    raw_flight_cost comes from flight_selection.fare_total_price (business, all legs pax-scaled) or the snapshot flight_base_price (economy).

  3. Null — any other status with a null persisted deposit stays null.

pending_payment is deliberately excluded from the derivation set: it always carries a persisted deposit (set when the booking is promoted to payment), so it emits the stored value from step 1, never a derived one.

Gotcha: the derived deposit only becomes usable once the checkout snapshot carries flight pricing. The initial draft snapshot (checkout step flights, before advancing) only holds step_timestamps, so the derivation yields 0.00 until the booking advances past the flights step.

Source: BookingDetailsResource::resolveDepositAmount() / deriveDepositFromCheckout(); PaymentCalculatorService::depositFromCheckout().

section_prices aggregates the per-section extra totals shown in the price/quote view, built by BookingDetailsResource::buildSectionPrices() from Booking::checkoutExtrasBreakdown(). It works for both quote and finalized bookings (the breakdown reconciles with the snapshot extras_price). Upsell types map to the three trip-page sections:

Key Upsell types (BookingUpsellType)
hotels Hotel
experiences Activity
services Transfer, FlightUpgrade, Insurance

Source: BookingDetailsResource::buildSectionPrices().

Each hotel stay groups consecutive nights at the same hotel.

Field Type Description
name string Hotel name
city string | null POI city name
nights int Number of nights
check_in string Check-in date (YYYY-MM-DD)
check_out string Check-out date (YYYY-MM-DD)
image_urls string[] Full URLs for all hotel images (empty array when none)
is_upgrade bool Whether the stay is a selected hotel upgrade (luxury or grand-luxury tier). The highest purchased tier wins: grand-luxury takes precedence over luxury, which takes precedence over the base selection hotel

product.itinerary is a flat, per-day list — one entry per calendar day, sequential from the destination-arrival date. Each item: { day, date, title, details, image_url, is_flight_day }. The tour’s stops (which can span multiple nights) are flattened into individual days: per-day text (title/details) comes from the localized translation, and image_url is resolved from the tour template’s per-day image (day_image), or null when the tour has no image for that day. is_flight_day is false for these content days.

The list may also contain virtual flight-day entries (is_flight_day: true) prepended at the start and/or appended at the end — one per overnight flight night, carrying hardcoded Spanish “en vuelo” copy and an endpoint image. They are dated to the real booked flight (outbound entries to the home-departure date, return entries landing on the home-arrival date). The rendered count per direction is the tour’s authored flight_days (see Product Templates) clamped to the booked flight’s actual span, so an overnight day is never shown for a leg that departs and lands the same calendar day. On a 2-night flight the standard copy is always the first night (Mientras duermes… / El final de un gran viaje, primary image) and the second night is a fixed-title, per-market editable slot (A una película de distancia / Próximamente en los mejores destinos, day-2 image, falling back to the primary when unset) — see Flight-day copy and ordering. When no flight day is rendered, the endpoint’s description/image merges onto the adjacent content day instead.

Source: BookingDetailsResource::buildItineraryData().

These arrays surface the activities and services for the booking:

  • experiences — the tour’s included activities plus the extra activity upsells the customer added at checkout. Each item: { name, city, date, image_url, tier } where tier is included or extra. Included activities are derived from the tour itinerary; extras come from activity upsells. The date is derived from the booking’s departure date plus the activity’s tour day (null when no day is recorded). Entries are ordered chronologically. (A future substitution tier is not yet emitted.)
  • optional_services — transfer upsells only (buildServicesData filters to BookingUpsellType::Transfer). Each item: { name, description }. Travel insurance upsells are intentionally not surfaced here; see Travel Insurance (Intermundial).

Activity and transfer names/descriptions are localized to the market’s default locale.

Component File
Controller backend/app/Http/Controllers/Api/BookingController.php
Booking Resource backend/app/Http/Resources/BookingDetailsResource.php
Offer Resource backend/app/Http/Resources/OfferSummaryResource.php
Passenger Resource backend/app/Http/Resources/PassengerSummaryResource.php
Route backend/routes/api.php (market-scoped routes)