02_HOTELS_TENANT_ENGINE

Graphical reference view with linked API documentation.

API — Hotels & availability (tenant-scoped engine)

This is the same search + book path as the public site: the engine uses TenantVendorConfigService to restrict which distribution vendors (Hotelbeds, Bonotel, Amadeus, etc.) are queried. If a tenant is configured in admin with only bonotel, availability search will only use Bonotel—identical to the web UI.

Always send tenant context on stateless clients:

  • tenant_id (query or JSON), or
  • tenant (slug, numeric id, or tenants.domain), or
  • header X-Tenant (same as tenant), or
  • rely on the Host if it matches tenants.domain for a single branded domain.

Accept: application/json on all requests.

Base URL (v1): https://{your-host}/api/v1
Legacy (same handler): https://{your-host}/api


1. Capabilities (discovery)

Method GET
Path /api/v1/tenant/capabilities
Auth None
Query tenant_id or tenant or X-Tenant; optional market=US (ISO-3166 alpha-2) to evaluate market allow/restrict on configs

Response (200): data.hotel.vendors_effective lists vendor codes the availability layer will use for that tenant, plus data.enabled_products (UI tabs) and optional data.hotel.vendor_config_rows.

Example

GET /api/v1/tenant/capabilities?tenant_id=2&market=US

2. Availability search (primary — use for rate shopping)

Method POST
Path /api/v1/availability/search
Auth None (or Bearer if you also pass user context)
Content-Type application/json

2.1 Search criteria (first request)

Field Required Description
checkIn, checkOut Yes* Y-m-d. *Not required when loading another page via searchId only.
tenant_id or X-Tenant / tenant Recommended Resolve tenant; otherwise host/default
One of: hotelCanonicalCode / hotelCode — or latitude+longitude — or destinationCode — or cityName+countryName Yes** **At least one search mode on the first request (omit when using searchId alone).

Optional (shopping): adults, children, childrenAges, currency, language, roomQuantity / rooms, deduplicate, applyPricing, Amadeus filters (chainCodes, amenities, starRating, budgetMin, budgetMax, hotelSource), etc.

2.2 Response mode (full vs paginated)

Use this to control payload size for web (full) vs mobile (paginated).

Field Default Description
responseMode full full = all hotels in one JSON array. paginated = one page + session id.
paginated Boolean alias; true is the same as responseMode: "paginated".
page 1 Page index (1-based) when paginated.
perPage 25 Hotels per page. Server cap: AVAILABILITY_SEARCH_MAX_PER_PAGE (default 100).
searchId UUID from meta.searchId on a prior paginated search. Only searchId, page, perPage, compact, and tenant keys are needed on follow-up requests.
compact false When true, each hotel omits roomRates (use for list screens; load rates on detail/check-rate).

Server configuration (.env, optional)

Variable Default Purpose
AVAILABILITY_SEARCH_DEFAULT_PER_PAGE 25 Default perPage when omitted
AVAILABILITY_SEARCH_MAX_PER_PAGE 100 Maximum perPage
AVAILABILITY_SEARCH_SESSION_TTL_MINUTES 15 How long searchId stays valid

2.3 Full response (default)

Omit responseMode or set "responseMode": "full". Behaviour is unchanged from legacy integrations: every matching hotel is returned in hotels.

Request example

POST /api/v1/availability/search
{
  "tenant_id": 2,
  "checkIn": "2026-06-01",
  "checkOut": "2026-06-05",
  "destinationCode": "PAR",
  "adults": 2,
  "roomQuantity": 1,
  "currency": "USD",
  "responseMode": "full"
}

Response shape

{
  "meta": {
    "responseMode": "full",
    "totalHotels": 142,
    "checkIn": "2026-06-01",
    "checkOut": "2026-06-05",
    "stayNights": 4,
    "roomQuantity": 1,
    "adults": 2,
    "children": 0,
    "pricing": { "minPriceSemantics": "...", "multiRoom": "..." }
  },
  "hotels": [ { "hotelCode": "hb_123", "hotelName": "...", "minPrice": 450, "roomRates": [ ] } ],
  "errors": { "amadeus": "optional vendor error string" },
  "message": null
}

2.4 Paginated response (mobile / large result sets)

Flow

  1. First request — include full search criteria + "responseMode": "paginated" (and optional page, perPage, compact). Vendors are queried once; results are cached server-side.
  2. Responsehotels contains only the requested page; meta.searchId identifies the cached search; meta.pagination describes pages.
  3. Next pagesPOST with "searchId": "<uuid>", "page": 2 (same tenant_id if you use tenant scoping). No checkIn / checkOut / destination required.
  4. Stop when meta.pagination.hasMore is false, or start a new search if you get 410 search_session_expired.

Request — page 1

POST /api/v1/availability/search
{
  "tenant_id": 2,
  "checkIn": "2026-06-01",
  "checkOut": "2026-06-05",
  "destinationCode": "PAR",
  "adults": 2,
  "currency": "USD",
  "responseMode": "paginated",
  "page": 1,
  "perPage": 20,
  "compact": true
}

Request — page 2+

POST /api/v1/availability/search
{
  "tenant_id": 2,
  "searchId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "page": 2,
  "perPage": 20
}

Response shape (paginated)

{
  "meta": {
    "responseMode": "paginated",
    "searchId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "totalHotels": 142,
    "pagination": {
      "page": 1,
      "perPage": 20,
      "total": 142,
      "totalPages": 8,
      "hasMore": true
    },
    "checkIn": "2026-06-01",
    "checkOut": "2026-06-05",
    "stayNights": 4,
    "compact": true
  },
  "hotels": [ { "hotelCode": "hb_123", "hotelName": "...", "minPrice": 450 } ],
  "errors": {},
  "message": null
}

Errors (paginated session)

HTTP code Meaning
410 search_session_expired searchId unknown or TTL elapsed — run a new search from page 1.
422 Validation (e.g. missing search criteria on first request).

2.5 Hotel object (summary)

Each item in hotels includes (when available): hotelCode, hotelName, vendor, currency, geo, starRating, thumbnailUrl, images, amenities, minPrice / maxPrice, pricingDisplay, roomRates (unless compact: true), isRefundable, etc. Pricing fields describe total stay for one room unless documented otherwise in meta.pricing.

2.6 Behaviour notes

  • Vendors are filtered to the tenant’s allow list before parallel search (admin B2B vendor configuration).
  • Paginated mode does not re-query bedbanks on page 2+; it reads the cached result set for that searchId.
  • Single-hotel lookup (hotelCanonicalCode only) still returns at most one property; pagination is mainly for destination/geo searches with many hotels.

Legacy path (identical handler): POST /api/availability/search


3. Check rate

Method POST
Path /api/v1/bookings/check-rate
Auth As needed for your integration

Use after the user selects a specific rateKey from search results (especially when compact: true omitted roomRates on the list).

Legacy: POST /api/bookings/check-rate


4. Create booking

Method POST
Path /api/v1/bookings
Auth Varies; supports X-API-Key (tenant’s api_key when configured) plus tenant resolution (BookingController::getTenant)

Legacy: POST /api/bookings


5. Booking details & voucher

Method GET
Path /api/v1/bookings/{vendorBookingCode}
/api/v1/bookings/{vendorBookingCode}/voucher

6. Hotel catalog (static / content)

GET /api/v1/hotels, GET /api/v1/hotels/{code} — list/detail helpers; not the multi-vendor availability engine. For prices and inventory aligned with the tenant, use §2 above.


7. Reference data (v1 prefix)

GET /api/v1/refs/places, refs/destinations, refs/cities/search, refs/hotels/autocomplete, etc. (same as /api/refs/...).


Mobile integration checklist

  1. GET /api/v1/tenant/capabilities?tenant_id=… — confirm enabled vendors.
  2. POST /api/v1/availability/search with responseMode: "paginated", compact: true, sensible perPage (e.g. 20).
  3. Store meta.searchId; load more with searchId + page until hasMore is false.
  4. On hotel tap, use rateKey from detail flow or re-run single-hotel search / check-rate.
  5. Handle 410 by re-running step 2 (session expired).

Implementation: App\Http\Controllers\Api\AvailabilityController, App\Services\Hotel\AvailabilityService, App\Services\Hotel\AvailabilitySearchPresentationService, App\Services\B2B\TenantVendorConfigService.