AI agents • booking assistants • integrators
HolaOlas Agent & Integrator Booking API
Public booking endpoints for agentic commerce in tourism: discover providers/experiences, check availability, validate live pricing, create bookings, and hand off payment on HolaOlas. JSON payloads, CORS enabled. The agent orchestrates; the human pays.
Core flow: provider → experiences → availability → pricing → booking → payment handoff to human on HolaOlas.
Provider lookup
Resolve a provider and list its experiences (with ids and slugs).
Example response
{
"provider": {
"slug": "adorablesailing"
},
"experiences": [
{
"id": "exp-uuid",
"slug": "el-clasico-excursion-privee-en-voilier-sur-la-lagune-de-bacalar-bacalar"
}
]
}Experience lookup
Get experience detail (name, slugs, pricing mode) to drive downstream calls.
Example response
{
"id": "exp-uuid",
"type": "excursion",
"booking_mode": "shared",
"slug": "el-clasico-excursion-privee-en-voilier-sur-la-lagune-de-bacalar-bacalar",
"timeSlots": [
{
"id": "morning-slot-1",
"startTime": "09:00",
"duration": 210
}
]
}Check availability
Check availability (dates + time slots) for an experience.
Payload (JSON)
{
"experienceId": "uuid",
"date": "2026-03-12",
"timeSlotId": "morning-slot-1",
"participantCount": 4
}Example response
{
"available": true,
"type": "excursion",
"reason": null
}Validate booking price
Compute and validate price. Returns pricingToken to lock price for create_booking.
Payload (JSON)
{
"experienceId": "uuid",
"date": "2026-03-12",
"timeSlotId": "morning-slot-1",
"adults": 2,
"children": 2,
"options": {
"snorkel-kit": 2
}
}Example response
{
"currency": "MXN",
"subtotal": 2400,
"discount": 0,
"fees": 0,
"deposit": 720,
"total": 2400,
"expiresAt": "2026-03-09T23:10:00Z",
"pricingToken": "eyJwIjoi...",
"unitBreakdown": [
{
"label": "Base",
"amount": 2400
}
]
}Create booking
Create a booking. With pricingToken: no price recalc, idempotent. Returns paymentUrl for human.
Payload (JSON)
{
"experience_id": "uuid",
"pricingToken": "from validate-booking-price",
"date": "2026-03-12",
"time_slot_id": "morning-slot-1",
"participant_data": {
"adults": 2,
"children": 0,
"firstName": "Jane",
"lastName": "Doe",
"email": "jane@example.com",
"phone": "+52..."
}
}Example response
{
"bookingId": "uuid",
"status": "pending",
"currency": "MXN",
"total": 2400,
"amountDueNow": 720,
"paymentUrl": "https://holaolas.app/en/booking/uuid?payment=true",
"expiresAt": "2026-03-09T23:25:00Z"
}Get booking
Fetch booking details. Add ?locale=en for paymentUrl locale.
Example response
{
"bookingId": "uuid",
"status": "pending",
"paymentUrl": "https://holaolas.app/en/booking/uuid?payment=true",
"amountDueNow": 720,
"amountPaid": 0,
"amountRemaining": 2400
}Pre-fill booking form (deep links)
Append query parameters to the public experience URL (path /…/p/providerSlug/e/experienceSlug). The page loads the experience, then applies one of two parsers: hotel if type === "hotel", otherwise excursion (excursion, activity, default). Same parameter names can mean different things (e.g. dateFrom/dateTo are multi-day on excursion, check-in/out on hotel).
Machine-readable spec — excursion / activity / default
{
"urlPattern": "https://holaolas.app/{locale}/p/{providerSlug}/e/{experienceSlug}",
"experienceTypes": [
"excursion",
"activity"
],
"experienceTypesNote": "Any loaded experience whose type is not hotel uses BookingFormExcursion and this parser (including activity and default branch).",
"parser": "parseExcursionPrefillFromSearchParams",
"sourceFile": "src/utils/booking-prefill-url.ts",
"internalDoc": "docs/booking-prefill-vs-formulaires.md section 3.A",
"dateFormat": "YYYY-MM-DD",
"precedence": [
"If categoryCounts (or pricingCategories) yields at least one valid row after filtering, it replaces adults/children/childrenAges prefill for that load.",
"payment is parsed before deposit; if both are set, payment wins when it maps to full or deposit.",
"options is parsed before selectedOptions; categoryCounts before pricingCategories.",
"specialRequests: first non-empty among specialRequests, notes, remarks (then trim, max 8000 chars)."
],
"paymentAndDepositSynonyms": {
"mapsToFull": [
"full",
"total",
"100"
],
"mapsToDeposit": [
"deposit",
"depot",
"partial"
],
"caseInsensitive": true,
"params": [
"payment",
"deposit"
]
},
"queryParams": {
"date": "Single-day YYYY-MM-DD (regex). Multi-day uses startDate+endDate when both valid and experience supportsMultiDay.",
"startDate": "Multi-day start. Alias: dateFrom.",
"endDate": "Multi-day end. Alias: dateTo.",
"slot": "Slot id or time 11:00 / 11h00. Alias: timeSlotId (slot read first).",
"categoryCounts": "URL-encoded JSON: array [{ categoryId or category_id, count }] or object { categoryId: count }. Alias param name: pricingCategories (categoryCounts read first). Counts 0–99; form keeps count>0 and known category ids + auto-adult + free-children.",
"adults": "Integer 1–99. Omitted if not in URL. Ignored when categoryCounts prefill applies.",
"children": "Integer 0–99. Omitted if not in URL. Ignored when categoryCounts prefill applies.",
"childrenAges": "Comma or semicolon separated ages 0–120. Alias: childAges.",
"options": "URL-encoded JSON object { optionId: quantity }; quantities 0–999. Alias: selectedOptions (options read first).",
"payment": "See paymentAndDepositSynonyms.",
"deposit": "Same parser as payment; lower precedence if payment also set.",
"specialRequests": "Aliases: notes, remarks. Max 8000 chars after trim.",
"firstName": "Alias: prenom.",
"lastName": "Alias: nom.",
"email": "string",
"phone": "Alias: tel.",
"phoneCountry": "e.g. FR, MX, CA"
},
"notParsedOnExcursionParser": [
"checkin",
"checkout"
]
}Machine-readable spec — hotel
{
"urlPattern": "https://holaolas.app/{locale}/p/{providerSlug}/e/{experienceSlug}",
"experienceTypes": [
"hotel"
],
"parser": "parseHotelPrefillFromSearchParams",
"sourceFile": "src/utils/booking-prefill-url.ts",
"internalDoc": "docs/booking-prefill-vs-formulaires.md section 3.B",
"dateFormat": "YYYY-MM-DD",
"routing": "The public experience page picks this parser only when the loaded experience type is hotel; otherwise the excursion spec applies.",
"precedence": [
"Valid unitId matching config.units[].id plus categoryCounts (after filter to that unit’s pricing categories and auto-adult/free-children) overrides adults/children/childrenAges for that load.",
"Valid unitId plus adults and/or children: merge into initializeCategoryCounts for that unit; unit-change category reset is skipped once (skipInitAfterPrefillRef).",
"Without valid unitId: adults, children, childrenAges, categoryCounts from URL are not applied; checkin/checkout, contact, options, payment, specialRequests, and extras booleans still apply when present.",
"payment before deposit; options before selectedOptions; categoryCounts before pricingCategories.",
"specialRequests: first non-empty among specialRequests, notes, remarks (trim, max 8000 chars)."
],
"paymentAndDepositSynonyms": {
"mapsToFull": [
"full",
"total",
"100"
],
"mapsToDeposit": [
"deposit",
"depot",
"partial"
],
"caseInsensitive": true,
"params": [
"payment",
"deposit"
]
},
"booleanQueryValues": {
"true": [
"1",
"true",
"yes",
"on"
],
"false": [
"0",
"false",
"no",
"off"
],
"note": "For extraBed, withPet, earlyCheckin, lateCheckout: true applies only if the matching hotelSettings flag is enabled; false always clears the checkbox."
},
"queryParams": {
"checkin": "YYYY-MM-DD. Aliases: checkIn, dateFrom (on hotel pages dateFrom means check-in, not excursion multi-day).",
"checkout": "YYYY-MM-DD. Aliases: checkOut, dateTo.",
"unitId": "Room/unit id from experience config. Aliases: unit, room (read in that order).",
"adults": "Integer 1–99. Ignored without valid unitId; ignored when categoryCounts prefill applies.",
"children": "Integer 0–99. Same constraints as adults.",
"childrenAges": "Comma or semicolon separated 0–120. Alias: childAges.",
"categoryCounts": "URL-encoded JSON array [{ categoryId or category_id, count }] or object. Alias: pricingCategories. Filtered to selected unit’s categories.",
"options": "URL-encoded JSON { optionId: quantity }. Alias: selectedOptions (options first).",
"payment": "See paymentAndDepositSynonyms.",
"deposit": "Same as payment; lower precedence if payment also set.",
"specialRequests": "Aliases: notes, remarks. Max 8000 chars after trim.",
"firstName": "Alias: prenom.",
"lastName": "Alias: nom.",
"email": "string",
"phone": "Alias: tel.",
"phoneCountry": "e.g. FR, MX, CA",
"extraBed": "boolean query; requires hotelSettings.extraBed.enabled for true to stick.",
"withPet": "boolean; requires hotelSettings.petFee.enabled for true.",
"earlyCheckin": "boolean. Alias: early_checkin. Requires hotelSettings.earlyCheckin.enabled for true.",
"lateCheckout": "boolean. Alias: late_checkout. Requires hotelSettings.lateCheckout.enabled for true."
},
"collisionNote": "dateFrom/dateTo mean checkin/checkout on hotel pages only; on excursion pages the excursion parser uses them as startDate/endDate aliases."
}Human-readable list — excursion / activity
- Core:
date,adults,children,slotortimeSlotId,firstName(orprenom),lastName(ornom),email,phone(ortel),phoneCountry - Multi-day (only if the experience has multi-day enabled):
startDate+endDate, ordateFrom+dateTo(bothYYYY-MM-DD) childrenAgesorchildAges— comma/semicolon-separated (e.g.5,8,12)categoryCountsorpricingCategories— URL-encoded JSON: array[{"categoryId":"…","count":n}]or object{"categoryId":n}(ids from experience config). Overridesadults/children/childrenAgeswhen present.optionsorselectedOptions— URL-encoded JSON{"optionId":quantity}paymentordeposit:fullordepositspecialRequests,notes, orremarks- Not read by the excursion parser:
checkin,checkout(use the hotel spec on hotel experiences).
Human-readable list — hotel
- Dates:
checkin+checkout(aliasescheckIn/checkOut, ordateFrom/dateTofor arrival/departure — not the excursion multi-day meaning). - Room:
unitId(aliasesunit,room) must matchconfig.units[].id. Without a valid id,adults,children,childrenAges, andcategoryCountsfrom the URL are ignored; other params still apply. - Same as excursion where shared:
childrenAges/childAges,categoryCounts/pricingCategories,options/selectedOptions,payment/deposit, contact fields,specialRequests/notes/remarks. - Extras (boolean:
1/true/yes/onvs0/false/no/off):extraBed,withPet,earlyCheckin(orearly_checkin),lateCheckout(orlate_checkout).trueonly sticks if enabled in hotel settings.
Example — excursion (minimal)
https://holaolas.app/en/p/adorablesailing/e/el-colectivo-shared-sailing-excursion-on-the-bacalar-lagoon-bacalar?date=2026-03-10&adults=3&children=0&slot=11:00&firstName=Jean&lastName=Dupont&email=jean@example.com&phone=0612345678
Example — excursion (ages + options JSON — encode the JSON for the URL)
In code: options=%7B%22snorkel-kit%22%3A2%7D
https://holaolas.app/fr/p/adorablesailing/e/example-slug?date=2026-04-13&adults=2&children=2&childrenAges=6,9&slot=11:00&firstName=Marie&lastName=Faubert&options=%7B%22snorkel-kit%22%3A1%7D&payment=deposit
Example — hotel (replace unit UUID and slugs)
https://holaolas.app/fr/p/myprovider/e/my-hotel-slug?checkin=2026-06-01&checkout=2026-06-05&unitId=YOUR-UNIT-UUID-FROM-CONFIG&adults=2&children=1&payment=deposit&extraBed=1
Recommended agent flow
- Resolve experienceId from URL (via /api/embed/provider if needed).
- POST /api/check-availability with date + timeSlotId.
- POST /api/validate-booking-price to get the total.
- POST /api/embed/booking/create then redirect the human to /{locale}/booking/{id}?payment=true.
- Or: generate a pre-fill URL (see above) for the user to open and pay directly.
Advanced parameters (private/shared excursions)
- bookingMode:
privateorshared(can also be defined per timeSlot). - adults, children[].age: enables age-based pricing.
- options: keys = option IDs, values = quantities.
- date and timeSlotId drive seasonal pricing and slot capacity.
The validate-booking-price and booking/create endpoints automatically apply seasonal rules, capacity, private/shared mode, and options/age pricing.
Common error responses
Slot full
{
"available": false,
"reason": "slot_full"
}Price mismatch
{
"error": "price_mismatch",
"message": "Submitted total does not match current pricing."
}Experience not found
{
"error": "Experience not found"
}Payment handoff
After booking/create, redirect the user to /{locale}/booking/{id}?payment=trueto complete payment on HolaOlas. The agent orchestrates; the human pays.
End-to-end example
{
"providerSlug": "adorablesailing",
"experienceId": "exp-uuid",
"steps": [
"GET /api/embed/provider?providerSlug=adorablesailing -> list experiences -> exp-uuid",
"POST /api/check-availability { experienceId: exp-uuid, date, timeSlotId }",
"POST /api/validate-booking-price { experienceId, bookingMode, adults, children, options, date, timeSlotId }",
"POST /api/embed/booking/create { experience_id, date, time_slot_id, booking_mode, adults, children, options, total_price }",
"Redirect user to /fr/booking/{id}?payment=true"
]
}