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), ortenant(slug, numeric id, ortenants.domain), or- header
X-Tenant(same astenant), or - rely on the Host if it matches
tenants.domainfor 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
- First request — include full search criteria +
"responseMode": "paginated"(and optionalpage,perPage,compact). Vendors are queried once; results are cached server-side. - Response —
hotelscontains only the requested page;meta.searchIdidentifies the cached search;meta.paginationdescribes pages. - Next pages —
POSTwith"searchId": "<uuid>", "page": 2(sametenant_idif you use tenant scoping). NocheckIn/checkOut/ destination required. - Stop when
meta.pagination.hasMoreisfalse, or start a new search if you get 410search_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 (
hotelCanonicalCodeonly) 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
GET /api/v1/tenant/capabilities?tenant_id=…— confirm enabled vendors.POST /api/v1/availability/searchwithresponseMode: "paginated",compact: true, sensibleperPage(e.g. 20).- Store
meta.searchId; load more withsearchId+pageuntilhasMoreis false. - On hotel tap, use
rateKeyfrom detail flow or re-run single-hotel search / check-rate. - 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.