Hermeseus API
A travel-booking gateway that unifies flights, hotels, and activities behind one consistent JSON API. Built for high-throughput agency platforms with multi-source aggregation and USD-normalized pricing.
Introduction #
Hermeseus is a unified travel-booking API that acts as a single entry point for three product families:
- Air β aggregates multiple flight inventory sources behind a single endpoint. Region-specific fare and routing rules are handled transparently by the gateway.
- Hotel β hotel availability and booking with rich property metadata. All foreign-currency rates are converted to USD before the response reaches you.
- Activity β a comprehensive activity / tours / attractions catalog and booking flow under
/api/Activity/v2/*, with money fields normalized to USD and full booking state managed by the gateway.
The public contract is stable and language-agnostic; clients only need an HTTP/JSON library.
Architecture #
Every booking lives in our local database with its own UniqueId. Upstream write operations are kept to an absolute minimum β the platform fulfils each booking through its own controlled process. This gives full control of pricing, cancellation, and refund policy without depending on upstream behavior.
| Layer | Responsibility |
|---|---|
| Flight aggregation | Routes a search to the appropriate inventory source, normalises output, and enforces regional routing rules transparently. |
| Hotel aggregation | Routes hotel availability between inventory sources and merges the responses into a single shape. |
| Booking engine | Owns the entire booking state machine, balance holds/releases, and duplicate detection. |
| Balance ledger | Atomic hold / release / deduct / charge operations on office wallets. |
| Currency normaliser | Recursively rewrites any money field to USD using cached FX rates. |
| Activity gateway | Manages the activity catalogue, bookings, vouchers, and webhook polling. |
Base URL & Environments #
The environment is selected by the base URL (host) you call β there is no longer a header or query flag to toggle it.
| Environment | Base URL | Behaviour |
|---|---|---|
| Production | https://api.hermeseus.com/api | Real providers, real wallet. |
| Sandbox | https://demo.hermeseus.com/api | Real providers up to the booking commit; seeded test wallet (see Sandbox Mode). |
All endpoints in this document are relative to the base URL. Every request and response is JSON. Responses are always shaped as { "Success": boolean, ...payload..., "Error": null|object }.
403 (Err0101007). Use the base URL that matches your credentials.Conventions #
Currency
All monetary fields visible to the client are denominated in USD. Any upstream amounts in other currencies are converted via cached FX rates before being returned.
FareSourceCode
Every priced itinerary is identified by an opaque FareSourceCode string. Treat it as a black box β pass it back to the gateway exactly as received in subsequent Revalidate / Book / Cancel calls. The gateway uses it internally to route the request to the correct inventory source.
Booking lifecycle (status_category / status_code)
Coded fields & enumerations
Several fields in flight responses are integer/string codes rather than human text. The tables below list every coded field and its possible values. These are identical across AirLowFareSearch and AirLowFareSearchIncremental β each itinerary is normalized the same way in both.
NonRefundableType β itinerary & per-segment
Derived from the fare's refund-before / refund-after-departure availability. 0 means βunknownβ (the supplier did not provide refund terms) β it does not mean refundable.
| Value | Meaning | Condition |
|---|---|---|
0 | Unknown / not stated | No refund terms supplied by the source. |
1 | Non-refundable | Refund not allowed before and after departure. |
2 | Non-refundable after departure | Refund after departure not allowed (before may be). |
3 | Non-refundable before departure | Refund before departure not allowed (after may be). |
RefundMethod
| Value | Meaning |
|---|---|
0 | Unknown β no refund terms supplied. |
1 | Online β refund is permitted (handled online). |
2 | Non-refundable. |
DirectionInd β trip direction
| Value | Meaning |
|---|---|
1 | One-way (a single OriginDestinationOption). |
2 | Round-trip / multi-leg (more than one OriginDestinationOption). |
CabinClassCode β per flight segment
| Value | Cabin |
|---|---|
1 | Economy |
2 | Premium Economy |
3 | Business |
4 | Premium Business |
5 | First |
6 | Premium First |
PassengerType β in PtcFareBreakdown and travelers
| Value | Meaning |
|---|---|
1 | Adult (ADT) |
2 | Child (CHD) |
3 | Infant (INF) |
FareType β in AirItineraryPricingInfo
| Value | Meaning |
|---|---|
1 | Published fare. |
2 | Private / negotiated fare (the value returned by the aggregated low-cost / OTA sources). |
SegmentRefundRules / Refund & Change flags
Inside SegmentRefundRules[*] (and each Legs[*]), the Refund and Change objects carry BeforeFlight / AfterFlight availability as a tri-state, plus an optional penalty:
| Value | Meaning |
|---|---|
true | Allowed. |
false | Not allowed. |
null | Unknown β the source did not state this rule. |
At the leg level the shape is { "Available": true|false|null, "Penalty": { "currency_code": "USD", "value": 0 } | null }. A Penalty.value of 0 means a fee-free change/refund; null means no penalty information.
Boolean itinerary flags
Fields such as IsLowcost, IsCharter, IsReturn, IsPassportMandatory, IsPassportIssueDateMandatory, IsDestinationAddressMandatory, IsClosed, HasAmenities, IsSeatServiceMandatory and IsMealServiceMandatory are plain booleans (true/false).
Request β TravelPreference.CabinType
On the search request, CabinType accepts these integer codes:
| Value | Cabin requested |
|---|---|
1 | Economy |
2 | Premium Economy |
3 | Business |
4 | Premium Business (mapped to Business on sources without a separate cabin) |
5 | First |
6 | Premium First (mapped to First on sources without a separate cabin) |
Pagination
Where supported, pagination uses query parameters page (1-based) and per_page (default 50, capped per-endpoint).
Sandbox Mode π§ͺ
Sandbox is selected by calling the dedicated base URL https://demo.hermeseus.com/api β point your client's host at it; the request/response bodies are identical to production. (The previous X-Sandbox header / ?sandbox=1 query flag is deprecated and no longer the documented mechanism.)
In sandbox the pipeline behaves exactly like production right up to the moment a booking is committed:
- Real provider data. Every search, revalidate, rules, baggage, availability, check-rate and activity catalogue call hits the real upstream providers. Responses are byte-for-byte the same shape as production (no synthetic generator), so what you test is what you ship.
- Real local lifecycle.
AirBook/HotelBook, status transitions, cancellation and refund all run end-to-end against your database. - Simulated commit. The three endpoints that would otherwise create a live, money-bearing booking with a provider are simulated β no real booking is ever created upstream. The response is generated from the structure of real logged responses, so it is indistinguishable in shape:
POST /Air/AirOrderTicketPOST /Hotel/HotelOrderPOST /Activity/v2/bookings
- Separate wallet. The first sandbox call lazily creates an office named
"<your-office> (sandbox)"with a seeded balance of USD 10,000. All holds / deducts / releases hit this sandbox wallet β real funds are never touched. - Real auth. Login flow,
SessionId, role permissions, and sliding TTL behave identically.
demo.hermeseus.com and your production build at api.hermeseus.com; nothing else in the request changes.curl -X POST https://demo.hermeseus.com/api/Air/AirLowFareSearch \
-H "Content-Type: application/json" \
-d '{"SessionId":"...","OriginDestinationInformations":[{"OriginLocationCode":"IST","DestinationLocationCode":"DXB","DepartureDateTime":"2026-06-12T00:00:00"}],"AdultCount":1,"TravelPreference":{"CabinType":1}}'
Authentication #
Hermeseus uses session-based authentication. Call CreateSession once, then include the returned SessionId in the JSON body of every subsequent request. Sessions are valid for 15 minutes and renew on every authenticated request (sliding TTL).
Password field must be the SHA-512 hash of your account UUID, uppercased: strtoupper(hash('sha512', $userUuid)). Plain passwords are not accepted.Create a new API session. No authentication required.
Request
{
"OfficeId": "OFFICE_NAME_OR_ID",
"UserName": "your_username",
"Password": "A1B2C3...HASHED_SHA512_UPPERCASE"
}
Response β 200 OK
{
"Success": true,
"SessionId": "7f3c81d2-0e8d-4f9a-9c12-4d5e2b9a8f01",
"Error": null
}
Response β Error
{
"Success": false,
"SessionId": null,
"Error": {
"Id": "Err0101003",
"Message": "Invalid credentials. Username or password is incorrect."
}
}
Terminate the current session immediately. After this call the SessionId is invalid.
Request
{ "SessionId": "7f3c81d2-0e8d-4f9a-9c12-4d5e2b9a8f01" }
Response
{ "Success": true, "Error": null }
Error Codes #
| Code | Meaning |
|---|---|
Err0101001 | Invalid or missing SessionId β recreate the session. |
Err0101002 | Session expired (idle > 15 min) β recreate the session. |
Err0101003 | Wrong credentials. |
Err0101004 | User has no access to the specified office. |
Err0101005 | Account is not verified. |
Err0101006 | Permission denied for this endpoint. |
Err0102004 | Revalidation required before booking (cache miss). |
Err0103003 | Booking not found. |
Err0103004 | Booking not cancellable in current state. |
Err0103005 | Booking already cancelled. |
Err0103006 | Booking not in ticketable / confirmable state. |
Err0103007 | Only the booking owner or an admin can perform this action. |
Err0104002 | Hotel booking not found. |
Err0106001 | Missing required field in request body. |
Err0106002 | At least one of UniqueId / ClientUniqueId must be provided. |
Air β End-to-end booking guide #
The complete flight journey, from a route the traveller enters to an issued ticket. Each step names the exact endpoint, its request parameters (taken directly from the platform's own validation rules β not guesswork), what comes back, and the one value you carry forward. Every Air endpoint requires the SessionId from Authenticate/CreateSession in the JSON body. All fares are USD.
AirLowFareSearch β (optional AirRules/AirBaggages) β AirRevalidate β AirBook β AirOrderTicket β AirBookingData, with AirCancel / AirRefund for undoing. The value you carry changes at each stage: FareSourceCode (search) β UniqueId (book).1 β Search for itineraries
Send the passenger mix, the cabin, and one OriginDestinationInformations entry per leg (one for one-way, two for round-trip, more for multi-city). Use AirLowFareSearch for a single JSON response, or its streaming twin AirLowFareSearchIncremental (Server-Sent Events) for progressive results.
POST /api/Air/AirLowFareSearch
{
"SessionId": "β¦",
"AdultCount": 1,
"ChildCount": 0,
"InfantCount": 0,
"PricingSourceType": "All",
"RequestOption": "All",
"TravelPreference": { "CabinType": 3, "AirTripType": "OneWay" },
"OriginDestinationInformations": [
{ "OriginLocationCode": "IST", "DestinationLocationCode": "DXB", "DepartureDateTime": "2026-10-12T00:00:00" }
]
}
| Parameter | Type | Req. | Description & allowed values |
|---|---|---|---|
SessionId | string | yes | Session from CreateSession. |
AdultCount | integer β₯1 | yes | Adult passengers. |
ChildCount | integer β₯0 | yes | Child passengers. |
InfantCount | integer β₯0 | yes | Infant passengers (no seat). |
PricingSourceType | string | yes | Pricing scope, e.g. All. |
RequestOption | string | yes | Result-set option, e.g. All. |
NumberOfResults | integer β₯0 | β | Basket size. Omit / 0 / 100 = default 100; other values scale the mix; max 5000. |
Provider | string | β | Optional inventory-source hint. Omit to let the gateway aggregate all sources and pick the best; unknown values fall back to the configured default. |
TravelPreference.CabinType | integer | β | Cabin: 1 Economy, 2 Premium Economy, 3 Business, 4 Premium Business, 5 First, 6 Premium First. Default 1. See Conventions. |
TravelPreference.AirTripType | string | β | e.g. OneWay / Return. Defaults from the number of legs (one leg β one-way, more β return). |
TravelPreference.MaxStopsQuantity | string/int | β | Cap on stops, e.g. All, 0 (non-stop). |
TravelPreference.VendorPreferenceCodes | array<string> | β | Restrict to these airline codes. |
TravelPreference.VendorExcludeCodes | array<string> | β | Exclude these airline codes. |
OriginDestinationInformations | array (β₯1) | yes | One entry per leg. |
β¦[].OriginLocationCode | string | yes | Departure IATA code (e.g. IST). |
β¦[].DestinationLocationCode | string | yes | Arrival IATA code (e.g. DXB). |
β¦[].DepartureDateTime | string | yes | Departure date/time, e.g. 2026-10-12T00:00:00. |
β¦[].OriginType / DestinationType | string | β | Optional point qualifier (airport / city). |
The response is { Success, SearchId, PricedItineraries[], Error }. Each itinerary has a FareSourceCode, the OriginDestinationOptions[].FlightSegments[] (with per-segment CabinClassCode in the same integer enum as the request), and AirItineraryPricingInfo.ItinTotalFare. Full field list in Search & Pricing. Carry the FareSourceCode of the chosen itinerary β and note the SearchId for AirSearchData.
2 β (Optional) Fare rules & baggage
Before committing, show the traveller the fare conditions and baggage allowance for the selected FareSourceCode.
| Endpoint | Body | Returns |
|---|---|---|
POST /Air/AirRules | FareSourceCode (or UniqueId for a booked fare) | { Success, FareType, FareRules[], Error } |
POST /Air/AirBaggages | FareSourceCode | { Success, BaggageInfoes[]{Departure,Arrival,FlightNo,Baggage}, Services[], Error } |
3 β Revalidate the fare
At the moment the traveller confirms, re-price the itinerary to confirm it is still available at the quoted fare.
POST /api/Air/AirRevalidate
{ "SessionId": "β¦", "FareSourceCode": "β¦from step 1β¦" }
| Parameter | Type | Req. | Description |
|---|---|---|---|
SessionId | string | yes | Session from CreateSession. |
FareSourceCode | string | yes | The itinerary code from step 1. |
Returns { Success, PricedItinerary, Services, MealTypeServices, SeatServices, Error }. If the fare expired or is gone, Success is false with Error.Id Err0102003/Err0102005 β go back to search. Otherwise proceed to book with the same FareSourceCode.
4 β Book (reserve & hold)
Reserve the itinerary with the passengers' details. This creates the reservation and a ticketing deadline; the ticket is not yet issued.
POST /api/Air/AirBook
{
"SessionId": "β¦",
"FareSourceCode": "β¦from step 3β¦",
"ClientUniqueId": "your-own-ref-0001",
"TravelerInfo": {
"PhoneNumber": "+441234567890",
"Email": "traveller@example.com",
"AirTravelers": [
{
"PassengerType": 1,
"Gender": 0,
"DateOfBirth": "1990-05-14",
"PassengerName": { "PassengerFirstName": "Jane", "PassengerLastName": "Doe" }
}
]
}
}
| Parameter | Type | Req. | Description & allowed values |
|---|---|---|---|
SessionId | string | yes | Session from CreateSession. |
FareSourceCode | string | yes | The revalidated code from step 3. |
TravelerInfo.PhoneNumber | string | yes | Lead contact phone. |
TravelerInfo.Email | yes | Lead contact email. | |
TravelerInfo.AirTravelers | array (β₯1) | yes | One entry per passenger; the count must match the search pax mix. |
β¦AirTravelers[].PassengerType | integer | yes | Passenger type code (adult / child / infant), forwarded to the source. |
β¦AirTravelers[].Gender | integer | yes | Gender code: 0 Male, 1 Female. |
β¦AirTravelers[].DateOfBirth | string (date) | yes | Passenger date of birth, YYYY-MM-DD. |
β¦AirTravelers[].PassengerName.PassengerFirstName | string | yes | Given name as on the passport. |
β¦AirTravelers[].PassengerName.PassengerLastName | string | yes | Surname as on the passport. |
ClientUniqueId | string | β | Your own reference; later usable in AirBookingData instead of UniqueId. |
MarkupForAdult / MarkupForChild / MarkupForInfant | numeric | β | Per-passenger markup added to the fare. Default 0. |
Returns { Success, UniqueId, TktTimeLimit, TktTimeLimitTimeZone, Category: 10, Status: 10, PriceChange, WarningMessage, Error }. Keep the UniqueId β it drives every remaining step β and the TktTimeLimit: the reservation is held only until then.
Status 10) holds a seat but is not a ticket. Issue the ticket before TktTimeLimit or the booking is released.5 β Issue the ticket
Once the traveller has paid you, issue the ticket. This finalizes the booking with the airline.
POST /api/Air/AirOrderTicket
{ "SessionId": "β¦", "UniqueId": "β¦from step 4β¦" }
| Parameter | Type | Req. | Description |
|---|---|---|---|
SessionId | string | yes | Session from CreateSession. |
UniqueId | string | yes | The booking id from AirBook (step 4). |
Returns { Success, Category: 20, Status: 20, Error } β Status 20 means ticketed. See the status_category / status_code table for the full lifecycle.
6 β Track the order
Fetch the full booking β itinerary, passengers, pricing, e-tickets, status β at any time.
POST /api/Air/AirBookingData
{ "SessionId": "β¦", "UniqueId": "β¦from step 4β¦" }
| Parameter | Type | Req. | Description |
|---|---|---|---|
SessionId | string | yes | Session from CreateSession. |
UniqueId | string | one of | The booking id from AirBook. |
ClientUniqueId | string | one of | Your own reference from AirBook. Provide at least one of UniqueId / ClientUniqueId. |
Full response shape (itinerary, pricing, passengers, services) in AirBookingData.
7 β Cancel or refund
| Endpoint | Body parameters | Use when |
|---|---|---|
POST /Air/AirCancel | SessionId, UniqueId (both required) | Void a reservation before the ticket is issued. Returns cancellation policies, a new PaymentDeadline, and CanExtendPaymentDeadline. |
POST /Air/AirRefundDisplay | UniqueId (required) | Preview refund terms after ticketing β per-ticket PenaltyAmount, IsNonRefundable, IsRefunded. |
POST /Air/AirRefund | UniqueId (required); optional RefundType (int, default 1), EticketNumbers (array), RefundPaymentMode (int) | Execute the refund for an issued ticket. |
Air β Search & Pricing #
Search for the cheapest itinerary per unique flight group across the selected provider. The response is capped at 500 itineraries with category diversity (cheapest, fastest, direct, baggage tiers, morning departures, evening arrivals).
Request
{
"SessionId": "7f3c81d2-...",
"Provider": "example-provider-1", // optional; gateway picks the right source by default
"AdultCount": 1,
"ChildCount": 0,
"InfantCount": 0,
"PricingSourceType": "All",
"RequestOption": "All",
"NumberOfResults": 100, // optional; omit / 0 / 100 = default 100. Any other value scales the same mix (50β50, 1000β1000). Max 5000.
"TravelPreference": {
"CabinType": 1, // integer enum: 1=Economy, 2=Premium Economy, 3=Business, 4=Premium Business, 5=First, 6=Premium First
"MaxStopsQuantity": "All",
"AirTripType": "OneWay",
"DirectFlight": false
},
"OriginDestinationInformations": [
{
"OriginLocationCode": "IST",
"DestinationLocationCode": "DXB",
"DepartureDateTime": "2026-06-12T00:00:00"
}
]
}
Response β 200 OK (truncated)
{
"Success": true,
"SearchId": 428713,
"PricedItineraries": [
{
"FareSourceCode": "dHA6ODc0NTE6ZmwxMjM0OjMyNw==",
"ValidatingAirlineCode": "TK",
"DirectionInd": 1,
"IsPassportMandatory": true,
"AirItineraryPricingInfo": {
"FareType": 1,
"ItinTotalFare": {
"BaseFare": 182.40,
"TotalTax": 37.20,
"TotalFare": 219.60,
"Currency": "USD"
},
"PtcFareBreakdown": [ /* per-pax breakdown */ ]
},
"OriginDestinationOptions": [
{
"JourneyDurationPerMinute": 270,
"FlightSegments": [
{
"DepartureAirportLocationCode": "IST",
"ArrivalAirportLocationCode": "DXB",
"DepartureDateTime": "2026-06-12T08:25:00",
"ArrivalDateTime": "2026-06-12T14:55:00",
"MarketingAirlineCode": "TK",
"FlightNumber": "764",
"ResBookDesigCode": "Y",
"Baggage": "1PC",
"CabinClassCode": 1,
"SeatsRemaining": 9
}
]
}
]
}
],
"Error": null
}
Provider value you sent.Streaming twin of AirLowFareSearch. Same request body, but the response is a Server-Sent Events stream (Content-Type: text/event-stream): itineraries are pushed in batches as the provider's incremental search returns them, instead of waiting for the whole search to finish. Use it for a fast first paint and progressively-filling results.
Each streamed itinerary is identical in shape to an AirLowFareSearch entry and is cached for later AirRevalidate/AirBook exactly the same way. De-duplication is by FareSourceCode across the stream. Unlike the blocking endpoint, results are not collapsed to cheapest-per-flight-group and the 500-item diversity cap is not applied (those are whole-result-set operations) β every proposal is emitted as it arrives.
Request
Identical to AirLowFareSearch.
Response β text/event-stream
event: itineraries
data: {"PricedItineraries":[ { /* same shape as AirLowFareSearch[*] */ }, ... ]}
event: itineraries
data: {"PricedItineraries":[ ... more as they arrive ... ]}
event: done
data: {"Success":true,"SearchId":428713,"Total":137}
Event types:
| event | data |
|---|---|
itineraries | { "PricedItineraries": [ ... ] } β a batch of new itineraries (only items not sent earlier in this stream). |
done | { "Success": true, "SearchId": N, "Total": N } β terminal event; SearchId is reusable just like the blocking endpoint. |
error | { "Success": false, "Error": { "Id": "...", "Message": "..." } } β terminal event on failure. |
curl -N -X POST https://demo.hermeseus.com/api/Air/AirLowFareSearchIncremental \
-H "Content-Type: application/json" \
-d '{"SessionId":"...","OriginDestinationInformations":[{"OriginLocationCode":"IST","DestinationLocationCode":"DXB","DepartureDateTime":"2026-06-12T00:00:00"}],"AdultCount":1,"TravelPreference":{"CabinType":1},"RequestOption":"Fifty"}'
curl -N, a fetch ReadableStream, or any SSE client). Keep accumulating itineraries events until you receive done. The connection stays open for the duration of the search.Air β Price Calendar #
The cheapest fare for each day on a route β a price calendar. Give an origin, a destination and the trip type (OneWay / RoundTrip); you get one entry per available date. This endpoint is provider-agnostic: there is no Provider field and the data always comes from the same aggregated fare source. All prices are in USD by default.
Request
{
"SessionId": "...",
"Origin": "DXB",
"Destination": "IST",
"TripType": "RoundTrip",
"DepartDate": "2026-11",
"ReturnDate": "2026-12",
"CalendarType": "departure_date",
"Currency": "USD"
}
| field | required | notes |
|---|---|---|
Origin / Destination | yes | 3-letter IATA city/airport code. |
TripType | no | OneWay or RoundTrip. Default RoundTrip. |
DepartDate | yes | yyyy-mm (whole month) or yyyy-mm-dd. |
ReturnDate | round-trip | Required when TripType=RoundTrip; ignored for one-way. |
CalendarType | no | departure_date (default) or return_date β which date the calendar iterates over. |
Currency | no | ISO code. Default USD. |
Response
{
"Success": true,
"Origin": "DXB", "Destination": "IST", "TripType": "RoundTrip",
"CalendarType": "departure_date", "Currency": "USD",
"DaysCount": 50, "MinPrice": 370, "MaxPrice": 608,
"Cheapest": { "Date": "2026-11-27", "Price": 370, ... },
"Days": [
{
"Date": "2026-11-01", "Price": 412, "Currency": "USD",
"Transfers": 1, "Airline": "PS", "FlightNumber": 576,
"DepartureAt": "2026-11-01T06:35:00+03:00", "ReturnAt": "2026-12-01T13:30:00+02:00",
"ExpiresAt": "2026-08-10T12:34:14Z", "Origin": "DXB", "Destination": "IST"
}, ...
],
"Error": null
}
Days is sorted by date. Price is the cheapest fare found for that day and Transfers its stop count; ExpiresAt is when the cached price may no longer be valid. Use it to drive a month view, then run AirLowFareSearch for a live, bookable quote on the chosen day.Re-check price and availability for a specific itinerary before calling AirBook. Required: the booking flow rejects book attempts that have not been revalidated within the current session.
Request
{
"SessionId": "7f3c81d2-...",
"FareSourceCode": "dHA6ODc0NTE6ZmwxMjM0OjMyNw=="
}
Response
{
"Success": true,
"PricedItinerary": { /* same shape as in AirLowFareSearch[*] */ },
"PriceChange": false,
"Error": null
}
Fare rules (penalties, changes, refundability) for an itinerary. Accepts either FareSourceCode (pre-booking) or UniqueId (post-booking).
Request
{ "SessionId": "...", "FareSourceCode": "..." }
Air β Booking #
Reserves the previously revalidated itinerary. Funds equal to TotalFare are held on your office wallet. The reservation auto-cancels after 10 minutes if the ticket is not issued via AirOrderTicket.
Request
{
"SessionId": "7f3c81d2-...",
"FareSourceCode": "dHA6ODc0NTE6ZmwxMjM0OjMyNw==",
"ClientUniqueId": "order_29013",
"TravelerInfo": {
"PhoneNumber": "+971501234567",
"Email": "customer@example.com",
"AirTravelers": [
{
"PassengerType": "Adt",
"Gender": 0, // 0=Male 1=Female
"DateOfBirth": "1992-04-18",
"Nationality": "AE",
"PassengerName": {
"PassengerTitle": "Mr",
"PassengerFirstName": "John",
"PassengerLastName": "Smith"
},
"Passport": {
"PassportNumber": "A12345678",
"ExpiryDate": "2030-12-01",
"IssueDate": "2020-12-01",
"Country": "AE"
}
}
]
}
}
Response β Reserved
{
"Success": true,
"UniqueId": "4d2e91c8-3b71-4a55-8e9c-5f1a2d3b6c80",
"TktTimeLimit": "2026-05-20T10:23:01+00:00",
"TktTimeLimitTimeZone": "UTC",
"Category": 10,
"Status": 10,
"PriceChange": false,
"WarningMessage": null,
"Error": null
}
Response β Insufficient balance
{ "Success": true, "Category": 40, "Status": 41, "WarningMessage": "Insufficient balance.", "Error": null }
Transitions the booking from 10/10 to 20/20 (TicketInProcess), queuing it for ticket issuance.
Request
{ "SessionId": "...", "UniqueId": "4d2e91c8-..." }
Response
{ "Success": true, "Category": 20, "Status": 20, "Error": null }
Cancel a booking that has not been ticketed yet. Released funds are returned to the office's available balance immediately. Ticketed bookings (status 21) require AirRefund instead.
Request
{ "SessionId": "...", "UniqueId": "4d2e91c8-..." }
Returns the list of e-tickets eligible for refund and any per-ticket penalty.
Issues a refund. Booking moves to 21/25 (TicketCancelled). The refunded amount is credited back to the office wallet via a charge transaction.
Request
{
"SessionId": "...",
"UniqueId": "4d2e91c8-...",
"RefundType": 1,
"EticketNumbers": ["235-2148764520"]
}
Full reservation record: travelers, e-tickets, segments, fare breakdown, booking notes, and audit timestamps. Lookup by either internal UniqueId or your ClientUniqueId.
Each segment in TravelItinerary.ItineraryInfo.ReservationItems additionally carries three fields: DepartureAirportTerminal and ArrivalAirportTerminal (plain strings) and AirlineRules (an HTML string of the fare/airline conditions). They are empty until populated for the booking and may be blank for segments left unfilled.
Request
{ "SessionId": "...", "UniqueId": "4d2e91c8-..." }
Air β Reference Data #
Static lookup datasets for resolving the codes returned in flight results (IATA airport/city codes, airline codes, country codes). These are plain JSON files served from this domain β fetch them once, cache them on your side, and refresh occasionally (they change rarely). No SessionId is required.
| Dataset | URL | Records |
|---|---|---|
| Countries | https://api.hermeseus.com/data/reference/countries.json | ~253 |
| Cities | https://api.hermeseus.com/data/reference/cities.json | ~9,600 |
| Airports | https://api.hermeseus.com/data/reference/airports.json | ~10,300 |
| Airlines | https://api.hermeseus.com/data/reference/airlines.json | ~1,150 |
Array of countries. code is the 2-letter ISO country code used by city/airport records; currency is the country's ISO-4217 currency; name_translations holds localized names.
[
{
"code": "GM",
"name": "Gambia",
"currency": "GMD",
"name_translations": { "en": "Gambia" },
"cases": { "su": "Gambia" }
}
]
Array of cities. code is the 3-letter IATA city code (matches OriginLocationCode/DestinationLocationCode in search), country_code links to countries, coordinates gives lat/lon, and has_flightable_airport indicates the city is reachable by air.
[
{
"code": "FSZ",
"name": "Shizuoka",
"country_code": "JP",
"time_zone": "Asia/Tokyo",
"coordinates": { "lat": 34.796112, "lon": 138.18944 },
"has_flightable_airport": true,
"name_translations": { "en": "Shizuoka" },
"cases": { "su": "Shizuoka" }
}
]
Array of airports. code is the 3-letter IATA airport code (matches segment DepartureAirportLocationCode/ArrivalAirportLocationCode), city_code links to cities, iata_type is the entity type (e.g. airport), and flightable indicates bookable flights operate there.
[
{
"code": "BLK",
"name": "Blackpool Airport",
"city_code": "BLK",
"country_code": "GB",
"iata_type": "airport",
"time_zone": "Europe/London",
"coordinates": { "lat": 53.778385, "lon": -3.041985 },
"flightable": true,
"name_translations": { "en": "Blackpool Airport" }
}
]
Array of airlines. code is the 2-character IATA airline code (matches ValidatingAirlineCode and segment MarketingAirlineCode/OperatingAirline.Code), name is the carrier name, and is_lowcost flags low-cost carriers.
[
{
"code": "ZM",
"name": "Air Manas",
"is_lowcost": true,
"name_translations": { "en": "Air Manas" }
}
]
Airline logo image proxy. {code} is a 1β4 character airline code (alphanumeric). Returns a square PNG (200Γ200, transparent background) served from this domain, cached for 24h. Use it directly as an <img> src. Returns 404 if no logo exists for that code.
<img src="https://api.hermeseus.com/logos/airline-logos/UN" alt="airline logo" width="80" height="80">
# or
curl -L 'https://api.hermeseus.com/logos/airline-logos/TK' -o tk.png
Alternate logo source β same contract as above but served from a different image provider with a square fill crop (200Γ200 PNG). Use whichever renders best for a given carrier; 404 if no logo exists.
<img src="https://api.hermeseus.com/logos/airline-logos-v2/UN" alt="airline logo" width="80" height="80">
Hotel β City Lookup #
Hotel cities are an offline dataset synced from each provider's catalog. These lookups are public β no session required.
Fuzzy search across city name, destination name, and country. Matches starting with the query string are boosted first.
Response
{
"data": [
{
"id": 1248,
"external_id": 35487,
"name": "Istanbul",
"destination": "Istanbul, Turkey",
"country": { "code": "TR", "name": "Turkey" }
}
],
"meta": { "query": "istanbul", "count": 1, "limit": 50 }
}
Bulk resolve up to 500 cities by provider-side external_id.
Request
{ "external_ids": [35487, 35488], "provider": "example-provider-1" }
Hotel β Availability & Booking #
Search available hotel offers. Provider=example-provider-1 (default) returns the rich payload with full hotel metadata (images, facilities, geo); Provider=example-provider-2 uses an alternate inventory source. All prices are normalised to USD.
Request
{
"SessionId": "7f3c81d2-...",
"Provider": "example-provider-1",
"CheckIn": "2026-07-10",
"CheckOut": "2026-07-14",
"CityId": 35487,
"CountryCode": "TR",
"NationalityId": "AE",
"Occupancies": [
{ "AdultCount": 2, "ChildCount": 1, "ChildAges": [7] }
]
}
Response (truncated)
{
"Success": true,
"PricedItineraries": [
{
"FareSourceCode": "cGFydG86SE9URUw0Mjg3MQ==",
"HotelId": 42871,
"HotelName": "Grand Hyatt Istanbul",
"NetRate": 487.20,
"Currency": "USD",
"NonRefundable": false,
"Hotel": {
"name": "Grand Hyatt Istanbul",
"rating": 5,
"location": { "latitude": 41.0427, "longitude": 28.9882 },
"images": ["https://cdn.../1.jpg", ...]
},
"Rooms": [ ... ],
"CancellationPolicies": [ ... ]
}
],
"Error": null
}
Verify a hotel rate before booking. The returned itinerary is cached and is what HotelBook reads from β calling Book without CheckRate fails with Err0102004.
FareSourceCode on CheckRate than the one you sent. Both codes are cached at the gateway, so HotelBook accepts either.Reserve the previously rate-checked offer. Funds are held; the reservation auto-cancels after 10 minutes if not confirmed via HotelOrder.
Request
{
"SessionId": "...",
"FareSourceCode": "cGFydG86SE9URUw0Mjg3MQ==",
"PhoneNumber": "+971501234567",
"Email": "customer@example.com",
"NationalityId": "AE",
"ClientUniqueId": "hotel_order_88112",
"Rooms": [
{
"RoomId": "R-1",
"Passengers": [
{ "PassengerType": 1, "Title": "Mr", "FirstName": "John", "LastName": "Smith", "Nationality": "AE" }
]
}
]
}
Confirm the reservation. Status moves 10β20 and the voucher is then issued.
Cancel a hotel booking and release held funds.
Full hotel reservation record: hotel metadata, booked rooms, voucher numbers, cancellation policies, audit timestamps.
Hotel v2 β Overview #
A second-generation hotel API served under /api/v2/Hotel/*. It exposes a richer, discovery-first surface: typed place search (city / region / country / hotel / landmark / airport / district), first-class filters, pagination, sorting, a map viewport, and deep hotel detail pages. All rates are quoted in USD.
Authenticate/CreateSession) and pass the returned SessionId. cities/search and cities/lookup are public. HotelBook / HotelOrder / HotelCancel / HotelBookingData under /v2/Hotel/ are identical to their v1 counterparts β see the Hotel section above.1s (e.g. -1456928 β 11111111456928); send it back verbatim as CityId. Positive ids (hotels) are unchanged.Hotel v2 β End-to-end booking guide #
This is the complete journey from a destination the traveller types to a confirmed, paid booking. Each step names the exact endpoint, what to send and why, what you get back, and β most importantly β the one value you must carry into the next step. Every step below is followed by a parameter-by-parameter request table taken directly from the platform's own validation rules β not guesswork. All prices are USD.
cities/search and cities/lookup need no session. Every other endpoint here, including HotelAvailability, requires your SessionId from Authenticate/CreateSession (in the JSON body). The platform manages the upstream provider session separately, so you never handle that one.1 β Find the destination
The traveller types a destination into a search box; you resolve it to an external_id. The value of q is exactly what the user typed.
GET /api/v2/Hotel/cities/search?q=paris&limit=20&type=city
| Parameter | Type | Req. | Description & allowed values |
|---|---|---|---|
q | string (β€128) | β | The user's raw search text. Empty q returns an empty list. |
limit | integer 1β100 | β | Max results. Default 20. |
type | string | β | Restrict to one place kind: city, district, region, country, landmark, hotel. Omit for all kinds. |
{
"data": [
{
"id": 13,
"external_id": "11111111456928",
"type": "city",
"name": "Paris",
"label": "Paris, Ile de France, France",
"destination": "Paris, Ile de France, France",
"country": { "code": "FR", "name": "France" },
"nr_hotels": 25708
}
],
"meta": { "query": "paris", "count": 1, "limit": 20, "type": "city" }
}
The response gives you the external_id you use in the next step. For display you can also use name/label, the country (country.name), the number of properties (nr_hotels), and the place type (city, country, district, region, landmark, hotel). type restricts results to one kind. To re-resolve ids you already stored, use POST /api/v2/Hotel/cities/lookup with { "external_ids": [ β¦ ] }.
2 β List the hotels in a place
Put the external_id from step 1 into CityId, add stay dates and guests (Occupancies), and optionally Filters, Sort, Page/PageSize and a Map viewport. See Availability for the full filter/sort menus.
POST /api/v2/Hotel/HotelAvailability
{
"SessionId": "β¦",
"CheckIn": "2026-10-15",
"CheckOut": "2026-10-16",
"CityId": 11111111456928,
"Occupancies": [ { "AdultCount": 2, "ChildCount": 0 } ],
"Filters": { "class": [4,5], "mealplan": ["breakfast_included"] },
"Sort": "price",
"Page": 1
}
| Parameter | Type | Req. | Description & allowed values |
|---|---|---|---|
SessionId | string | yes | Your session from CreateSession. |
CheckIn | date YYYY-MM-DD | yes | Arrival date. |
CheckOut | date YYYY-MM-DD | yes | Departure date. Must be after CheckIn. |
CityId | integer | one of | The external_id from step 1 (city mode). Provide exactly one of CityId / HotelId. |
HotelId | integer | one of | A single property (step 3). When set, send CityId as null. |
Occupancies | array (β₯1) | yes | One entry per room requested. |
Occupancies[].AdultCount | integer 1β8 | yes | Adults in that room. |
Occupancies[].ChildCount | integer 0β6 | β | Children in that room. |
Occupancies[].ChildAges | array<int 0β17> | β | Age of each child at check-out; supply one age per child when ChildCount > 0. |
Filters | object | β | Facet filters (class, mealplan, price, β¦). Full menu in Availability. |
Sort | string (β€64) | β | e.g. price. See the Sorting list in Availability. No default β omit for the supplier's natural order. |
Page | integer β₯1 | β | 1-based page number. Default 1. |
PageSize | integer 1β200 | β | Results per page. |
Map | object | β | Optional viewport bounds to constrain results to a map area. |
lang | string (β€8) | β | Content language, e.g. ar. |
The response is a page of properties under PricedItineraries[] β each with HotelId, HotelName, NetRate, Currency, NonRefundable, FreeCancellation, MealPlan and a Hotel object (name, rating, location, images) β plus Meta (pagination, price range, the applied/available sorts and filters). Show this as a list. Carry the HotelId of the chosen property into the next step.
lang (e.g. ar) and, where the supplier has translated content, hotel names and text come back in that language.3 β Open a hotel: rooms, amenities, prices
Call the same endpoint with the same parameters as step 2, but set CityId to null and pass the HotelId from step 2. This switches the response from a list of properties to the full room-and-rate list for that one hotel.
POST /api/v2/Hotel/HotelAvailability
{
"SessionId": "β¦",
"CheckIn": "2026-10-15",
"CheckOut": "2026-10-16",
"CityId": null,
"HotelId": 7825691,
"Occupancies": [ { "AdultCount": 2 } ]
}
You receive the hotel's Hotel profile, Policies, Reviews, and every bookable room-rate under PricedItineraries[]: price, MealPlan/Board, NonRefundable, RefundableUntil, CancellationPolicy, AvailableRooms, and a full Room object (name, beds, size, description, highlights, amenities, amenity groups, room images). See Hotel details for every field.
FareSourceCode of the room-rate the traveller picks β it is what you validate and book with in the next steps.4 β Validate the rate
Collect the guests' details first. At the exact moment the traveller confirms, re-price the chosen offer.
POST /api/v2/Hotel/HotelCheckRate
{ "SessionId": "β¦", "FareSourceCode": "β¦from step 3β¦" }
| Parameter | Type | Req. | Description |
|---|---|---|---|
FareSourceCode | string | yes | The room-rate code from step 3. |
SessionId | string | yes | Your session from CreateSession. The validated fare is cached against it for 15 minutes so the following HotelBook/HotelOrder need no further upstream re-price. Omit it and the flow cannot continue. |
The response may return the same FareSourceCode or a new one (the price may have moved). Either way, use the FareSourceCode returned here for the next call.
5 β Hold the booking
After the traveller confirms, hold the room with the validated FareSourceCode plus the guest details.
POST /api/v2/Hotel/HotelBook
{
"SessionId": "β¦",
"FareSourceCode": "β¦from step 4β¦",
"PhoneNumber": "+441234567890",
"Email": "traveller@example.com",
"Nationality": "GB",
"ClientUniqueId": "your-own-ref-0001",
"Note": "High floor if possible",
"Rooms": [
{
"Passengers": [
{
"FirstName": "Jane", "LastName": "Doe",
"PassengerType": 1, "PassengerTitle": 2,
"NationalId": null, "PassportNumber": "123456789"
}
]
}
],
"HotelTransfers": []
}
| Parameter | Type | Req. | Description & allowed values |
|---|---|---|---|
SessionId | string | yes | Same session used at CheckRate. |
FareSourceCode | string | yes | The code returned by HotelCheckRate (step 4), not the step-3 one. |
PhoneNumber | string | yes | Lead contact phone. |
Email | yes | Lead contact email. | |
Rooms | array (β₯1) | yes | One entry per booked room; the count must match the Occupancies you searched with. |
Rooms[].Passengers | array (β₯1) | yes | Guests in that room. |
β¦Passengers[].FirstName / LastName | string | yes | Guest name as on ID/passport. |
β¦Passengers[].PassengerType | integer | yes | Guest type code forwarded to the supplier (adult vs child). Match it to the Occupancies you searched. |
β¦Passengers[].PassengerTitle | integer | yes | Title/salutation code forwarded to the supplier (e.g. Mr / Mrs / Ms / child). |
β¦Passengers[].NationalId | string | β | National ID where the property requires it. |
β¦Passengers[].PassportNumber | string | β | Passport number where required. |
Nationality | string | β | Lead guest nationality (ISO country code); some rates are nationality-scoped. |
ClientUniqueId | string | β | Your own reference for the booking; you can later fetch it by this instead of UniqueId. |
Note | string | β | Free-text special request passed to the property. |
HotelTransfers | array | present | Must be present (send [] if none). Each entry: TransferType (int), ServiceType (int), and optional AirLine, FlightNo, ArrivalTime, DepartureTime. |
Nothing is charged yet β this is a temporary hold, valid for up to another ~15 minutes. The response returns a UniqueId β keep it; it drives every remaining step. Call at 10:12 β you have until ~10:27 to place the order.
6 β Place the order (pay & finalize)
Once you have taken payment from the traveller (your own gateway), finalize with the UniqueId.
POST /api/v2/Hotel/HotelOrder
{ "SessionId": "β¦", "UniqueId": "β¦from step 5β¦" }
| Parameter | Type | Req. | Description |
|---|---|---|---|
UniqueId | string | yes | The booking id returned by HotelBook (step 5). |
SessionId | string | yes | Same session as the rest of the flow. |
Miss the window and the hold is released β you must restart from step 4. On success the booking total is deducted from your deposit with us and the reservation is confirmed.
HotelOrder β never at Book. Always confirm the traveller has paid you before calling it.7 β Track, extend, or cancel
All keyed on the booking UniqueId:
| Endpoint | Body parameters | Purpose |
|---|---|---|
POST /v2/Hotel/HotelBookingData | UniqueId or ClientUniqueId (one required) | Fetch the booking and its current status at any time. |
POST /v2/Hotel/HotelExtendPaymentDeadline | UniqueId (required) | Extend the payment deadline on a still-held (unconfirmed) booking. |
POST /v2/Hotel/HotelCancelDisplay | UniqueId (required) | Preview cancellation terms and any penalty before cancelling. |
POST /v2/Hotel/HotelCancel | UniqueId (required); optional CancelActor (int, default 0), RefundPaymentMode (int, default 0), HasAnyCanceledUser (bool, default false) | Cancel the booking. |
Full request/response shapes for these are in Booking lifecycle.
Hotel v2 β Places #
Typed place search. Optional type restricts results to one of city, region, country, hotel, landmark, airport, district. Public β no session required.
Response
{
"data": [
{
"id": 13,
"external_id": 11111111456928,
"type": "city",
"name": "Paris",
"label": "Paris, Ile de France, France",
"country": { "code": "FR", "name": "France" },
"nr_hotels": 25708
}
],
"meta": { "query": "paris", "count": 5, "limit": 20 }
}
Bulk resolve up to 500 places by public external_id. Public.
Request
{ "external_ids": [11111111456928, 2281] }
Hotel v2 β Availability #
City search. Send CityId (from cities/search) with optional Filters, Page, Sort and Map. Provide exactly one of CityId or HotelId.
Request
{
"SessionId": "7f3c81d2-...",
"CheckIn": "2026-10-15",
"CheckOut": "2026-10-16",
"CityId": 11111111456928,
"Occupancies": [ { "AdultCount": 2 } ],
"Page": 1,
"Sort": "price",
"Filters": { "class": [4,5], "mealplan": ["breakfast_included"], "free_cancellation": [1] },
"Map": { "Center": { "lat": 48.86, "lng": 2.35 }, "RadiusKm": 2 }
}
Response (truncated)
{
"Success": true,
"CheckIn": "2026-10-15", "CheckOut": "2026-10-16", "Nights": 1,
"PricedItineraries": [
{
"FareSourceCode": "",
"HotelId": 7825691,
"HotelName": "Example Paris Hotel",
"NetRate": 98.53, "Currency": "USD",
"NonRefundable": true, "FreeCancellation": false, "MealPlan": "Room Only",
"Hotel": { "external_id": 7825691, "review_score": 7.4, "location": { ... }, "images": [ ... ] }
}
],
"Meta": {
"Pagination": { "Page": 1, "PageSize": 140, "TotalPages": 8, "HasNextPage": true },
"PriceRange": { "min": 98.53, "max": 204.22, "currency": "USD" },
"Sort": "price", "AvailableSorts": [ ... ], "AvailableFilters": { ... }, "Map": { ... }
}
}
Sort accepts price (default), popularity, review_score, stars_desc, stars_asc, distance. The full menu is echoed back in Meta.AvailableSorts. Pages are ~130β140 hotels each.Filters
Send selections under Filters as { "key": [ idsβ¦ ] }; conditions combine with AND. For each search, every filter key, its human title, and the selectable option ids (with a live result count) are returned under Meta.AvailableFilters β each entry is { "title": β¦, "categories": [ { "id", "name", "count" } ] } β and the filters you sent are echoed back in Meta.AppliedFilters. The complete set of filter keys:
facility and room_facility are curated filter catalogues β the short, fixed set of facilities you can search by (their values are listed in full below). They are not the same as the per-hotel/per-room amenities / amenity_groups you get back in a hotel-details response β that is an open-ended, free-text descriptive list (Bathrobe, Slippers, Wake-up service, Socket near the bed, β¦) that varies by property and has no fixed value set. Filter by the catalogue ids here; display the descriptive amenities from the response.| Key | Filters by | Value |
|---|---|---|
class | Property star rating | 0 unrated Β· 1β5 stars. e.g. [4,5] |
mealplan | Meals included | breakfast_included Β· breakfast_and_dinner Β· half_board Β· full_board Β· all_inclusive |
free_cancellation | Free-cancellation rates only | 1 = free cancellation |
reviewscorebuckets | Min guest review score | 50 = 5+ Β· 60 = 6+ Β· 70 = 7+ Β· 80 = 8+ Β· 90 = 9+ |
rshl | Min location score | 6 = 6+ Β· 7 = 7+ Β· 8 = 8+ Β· 9 = 9+ |
property_type | Property type | 3 Entire homes & apartments Β· 201 Apartments Β· 203 Hostels Β· 204 Hotels Β· 205 Motels Β· 206 Resorts Β· 208 Bed and breakfasts Β· 209 Ryokans Β· 210 Farm stays Β· 212 Holiday parks Β· 213 Villas Β· 214 Campsites Β· 215 Boats Β· 216 Guest houses Β· 220 Holiday homes Β· 221 Lodges Β· 222 Homestays Β· 223 Country houses Β· 224 Luxury tents Β· 225 Capsule hotels Β· 226 Love hotels Β· 228 Chalets Β· 231 Economy hotels Β· 235 Student accommodation |
stay_type | Travel group | 2 = Adults only Β· 4 = Travel Proud (LGBTQ+ friendly) |
distance | Max distance from city centre | 1000 = <1 km Β· 3000 = <3 km Β· 5000 = <5 km |
twin_double_bed | Bed preference | 2 = 2 single beds Β· 3 = double bed |
num_beds | Minimum number of beds | 1 = 1+ Β· 2 = 2+ Β· 3 = 3+ Β· 4 = 4+ Β· 5 = 5+ |
entire_place_bedroom_count | Minimum bedrooms (entire places) | 1 = 1+ Β· 2 = 2+ Β· 3 = 3+ Β· 4 = 4+ |
SustainablePropertyLevelFilter | Sustainability certification | 4 = certified |
facility | Property facilities | 2 Parking Β· 3 Restaurant Β· 4 Pets allowed Β· 5 Room service Β· 8 24-hour front desk Β· 11 Fitness centre Β· 16 Non-smoking rooms Β· 17 Airport shuttle Β· 28 Family rooms Β· 46 Free parking Β· 54 Spa & wellness centre Β· 72 BBQ facilities Β· 107 Free WiFi Β· 139 Airport shuttle (free) Β· 182 Electric vehicle charging station Β· 185 Wheelchair accessible Β· 433 Swimming pool |
room_facility | Room facilities | 5 Bath Β· 11 Air conditioning Β· 16 Kitchenette Β· 17 Balcony Β· 23 Desk Β· 34 Washing machine Β· 37 Patio Β· 38 Private bathroom Β· 71 Fireplace Β· 75 Flat-screen TV Β· 79 Soundproofing Β· 81 View Β· 86 Electric kettle Β· 93 Private pool Β· 108 Sea view Β· 120 Coffee machine Β· 123 Terrace Β· 998 Coffee/tea maker Β· 999 Kitchen/kitchenette |
price | Budget for the stay (USD) | minβmax range; the search's bounds are in Meta.PriceRange |
chaincode | Hotel chain | chain ids β vary by search; e.g. 1080 Marriott Β· 1078 Hilton Β· 1053 ibis Β· 1050 Novotel Β· 8647 Radisson Β· 12902 Sheraton |
district | Neighbourhood / district | city-specific ids β read the options from Meta.AvailableFilters.district |
popular_nearby_landmarks | Near a landmark | city-specific landmark ids β from Meta.AvailableFilters.popular_nearby_landmarks |
popular | Curated shortcut of the most-used filters | mixed ids β from Meta.AvailableFilters.popular |
Hotel v2 β Hotel details #
Deep hotel page. Send HotelId (and CityId: null) to get full property content plus one offer per room-rate.
Response (abridged)
{
"Success": true, "Nights": 1,
"Hotel": { "external_id": 6331862, "name": "...", "rating": 3, "description": "...", "facility_groups": [ ... ], "images": [ ... ] },
"Policies": {
"CheckIn": { "from": "15:00", "until": "23:30" }, "CheckOut": { "from": "08:00", "until": "11:00" },
"AgeRestriction": { "min_age": 18, "phrase": "..." }, "Curfew": null,
"Children": { "allowed": false }, "Pets": { "allowed": "NO", "charge": "NOT_APPLICABLE" },
"Groups": null, "Meals": [ { "type": "BREAKFAST", "offer": "BOTH" } ],
"AcceptedPaymentCards": [ "Visa", "Mastercard", ... ],
"DamageDeposit": null, "ImportantInfo": null, "License": { "numbers": [ "1462850" ], "phrases": [ ... ] }
},
"Reviews": { "Score": 8.2, "Count": 12408, "SubScores": [ ... ], "Featured": [ ... ] },
"PricedItineraries": [
{ "FareSourceCode": "NjMz...", "NetRate": 358.13, "MealPlan": "Breakfast",
"Room": {
"id": "...", "name": "Standard Twin Room", "adults": 2, "children": 0, "max_occupancy": 2, "beds": [ ... ],
"size_m2": 32, "size_feet2": 344.4, "description": "The spacious twin room featuresβ¦",
"highlights": [ { "name": "Free WiFi", "icon": "wifi" }, { "name": "Air conditioning", "icon": "air-conditioning" } ],
"amenities": [ "TV", "Minibar", "Private bathroom", ... ],
"amenity_groups": [ { "group": "Bathroom", "items": [ "Toilet", "Shower", "Hairdryer" ] }, ... ],
"images_count": 7, "images": [ { "url": "https://images.example.com/hotel/large/....jpg", "thumbnail_url": "https://images.example.com/hotel/thumb/....jpg" } ] } }
],
"Meta": { "HotelId": 6331862, "Currency": "USD", "Offers": 5 }
}
Room carries the full room profile:
size_m2/size_feet2β room area in square metres and square feet.descriptionβ free-text room description.highlightsβ key features as{ name, icon }(icon is an opaque UI icon token).amenitiesβ flat list of amenity names;amenity_groupsgroups them as{ group, items[] }(Bathroom, Kitchen, Media & Technology, β¦). This is a descriptive, open-ended list that varies per room (many hundreds of possible values) β not theroom_facilityfilter, which is the short curated set of ids you can search by.beds,max_occupancy,adults,childrenβ sleeping capacity.images(withimages_count) β that specific room's own photos, each a{ url, thumbnail_url }pair (full-size / thumbnail). Separate fromHotel.images, the whole-property gallery.
HotelId), not in city search.Policies carries every house rule the supplier publishes, so you can show the complete conditions per hotel:
CheckIn/CheckOutβ{ from, until }time windows (either bound may benull).AgeRestrictionβ{ min_age, phrase }minimum check-in age.Curfewβ a curfew note, ornull.Childrenβ{ allowed }.Petsβ{ allowed: "YES"|"NO", charge }.Groupsβ group-booking terms string, ornull.Mealsβ array of{ type, offer }(e.g.BREAKFAST/BOTH,LUNCH/OPTIONAL_PAID).AcceptedPaymentCardsβ cards accepted at the property.DamageDepositβ deposit terms, ornull.ImportantInfoβ free-text important information, ornull.Licenseβ{ numbers[], phrases[] }the property's licence/registration.
Policies block is returned only in hotel-details (send HotelId), not in city search.Re-prices a FareSourceCode live and keeps it valid for at least 15 minutes. The confirmed itinerary is cached at the gateway and is what /v2/Hotel/HotelBook reads from.
Request
{ "SessionId": "...", "FareSourceCode": "NjMz..." }
Hotel v2 β Booking lifecycle (Book / Order / Data / Cancel) #
Booking under v2 uses the same gateway lifecycle as v1 β only the path prefix changes to /v2/Hotel/. Call HotelCheckRate first so the offer is cached, then HotelBook holds the reservation and HotelOrder confirms it. Every endpoint below is POST, carries the SessionId, and β except HotelBook β is keyed on the UniqueId returned by HotelBook. Responses use the standard { "Success", "Error", β¦ } envelope and are identical in shape to the corresponding Hotel v1 endpoint.
| Endpoint | Purpose | Request body |
|---|---|---|
POST /v2/Hotel/HotelBook | Hold the reservation for a checked-rate offer. | SessionId, FareSourceCode and guest/traveller details β same shape as the v1 HotelBook. |
POST /v2/Hotel/HotelOrder | Confirm (issue) a held booking. | { "SessionId": "β¦", "UniqueId": "β¦" } |
POST /v2/Hotel/HotelBookingData | Retrieve a booking and its current status. | { "SessionId": "β¦", "UniqueId": "β¦" } (or ClientUniqueId) |
POST /v2/Hotel/HotelCancelDisplay | Preview the cancellation terms and any penalty before cancelling. | { "SessionId": "β¦", "UniqueId": "β¦" } |
POST /v2/Hotel/HotelCancel | Cancel a booking. | { "SessionId": "β¦", "UniqueId": "β¦", "CancelActor": 0, "RefundPaymentMode": 0, "HasAnyCanceledUser": false } β the last three are optional. |
POST /v2/Hotel/HotelExtendPaymentDeadline | Extend the payment deadline on a held (unconfirmed) booking. | { "SessionId": "β¦", "UniqueId": "β¦" } |
UniqueId is the booking reference returned by HotelBook; keep it to drive Order, BookingData, CancelDisplay, Cancel and ExtendPaymentDeadline. Each endpoint requires its role permission (hotel_book, hotel_order, hotel_booking_data, hotel_cancel_display, hotel_cancel, hotel_extend_payment_deadline).
Activity β Overview #
The Activity API exposes a full catalogue of tours, attractions and experiences and a complete booking lifecycle under /api/Activity/v2/.... Resources are organized into Products, Product Types, Pricing, Bookings, Vouchers, Configuration and Webhook Events. The gateway applies exactly these transforms before handing a response back:
- Money β USD. Every monetary field (
basePrice,totalAmount,amount,price, rateamounts, β¦) and itscurrency/currencyCodeis converted to USD. The original amount is preserved in the human-readableformatstring where the inventory source provides one (e.g."S$30.00"). - Links β this gateway. Every HATEOAS
links[].hrefpoints at this gateway ({your base URL}/api/Activity/v2/...); the upstream inventory host is never exposed. CDN/image URLs are left untouched so images keep loading. - Nothing else changes. UUIDs, titles, dates, statuses and every other field are returned verbatim.
Booking model β deferred fulfilment
Creating a booking does not immediately commit it with the inventory source. POST /v2/bookings returns a booking in reserved status and mirrors it locally; the platform then finalises it asynchronously. Key consequences for your integration:
- The
data.uuidyou receive at create time is your permanent reference β keep using it for details, status, vouchers and download. Once the platform issues the booking upstream it is transparently mapped onto the real supplier UUID; your original reference keeps working. - The office balance is held for the booking total. Vouchers exist only once the booking has been issued.
Typical flow
GET products β GET products/{uuid} β GET products/{uuid}/product-types β GET product-types/{uuid}/price-lists β POST bookings β (PUT bookings/{uuid}/confirm) β GET bookings/{uuid}/vouchers β GET .../download-voucher/{voucher}.
Authentication
Every operation requires an active SessionId (obtain it from CreateSession). Send it as a query parameter on GET requests and in the JSON body on POST/PUT. It is applied to all Activity operations and omitted from the per-endpoint parameter tables for brevity.
Common parameters
| Name | In | Type | Description |
|---|---|---|---|
SessionId | query / body | string | Required on every request. |
page | query | integer | 1-based page number (list endpoints). |
per_page | query | integer | Page size (list endpoints). |
Response envelope
Resource responses are wrapped as { "data": ..., "timestamp": "<ISO-8601 +08:00>" }; list responses add "meta": { "pagination": { "page", "pageCount", "perPage", "total" } }. Timestamps and booking date/time strings are in the +08:00 (Asia/Singapore) zone.
Status codes
| Code | Meaning |
|---|---|
200 OK | Success β body holds the resource / list. |
400 Bad Request | Malformed request or invalid parameters. |
401 Unauthorized | Missing / expired SessionId. |
403 Forbidden | Your role lacks permission for this operation. |
404 Not Found | Resource / UUID does not exist. |
422 Unprocessable | Validation failed (e.g. unavailable date, bad pax mix). |
5xx | Gateway or inventory-source error. |
Error responses use an error object with a machine code, a human message and the http_code:
{
"error": {
"code": "not_found",
"message": "Listing was not found or expired, provided UUID: d3bfa3e1-e",
"http_code": 404
},
"timestamp": "2026-06-02T16:43:33.184+08:00"
}
Activity β End-to-end booking guide #
The full journey from browsing the catalogue to a downloadable voucher. Each step names the exact endpoint, what to send, what you get back, and the id you carry forward. Send SessionId as a query parameter on GET and in the JSON body on POST/PUT. Prices are USD; times are in the activity's local timezone.
products β products/{uuid} β product-types β price-lists (+ timeslots) β POST bookings β PUT β¦/confirm β vouchers β download-voucher. Ids carried: product uuid β productTypeUuid β booking uuid.1 β Browse the catalogue
List products, filtered and sorted. Served from the gateway's local cache, so it is fast.
GET /api/Activity/v2/products?city=Singapore&category=Attractions&min_price=20&max_price=200&sort=bestselling&page=1&per_page=50&SessionId=β¦
| Query param | Type | Description & allowed values |
|---|---|---|
q | string | Name search in any stored language; matched rows carry matchedLanguage. |
type | string | Product-type name substring or type UUID. |
category | string | Category name or UUID; a parent UUID expands to all descendants. |
country / city | string | Filter by location name. |
tag | string | Filter by tag. |
language | string | Localize titles, e.g. KO, AR. |
min_price / max_price | float | Price band (USD). |
sort | string | One of price, -price, date, -date, bestselling, sold, -sold. Default: catalogue order. |
page / per_page | integer | Pagination. per_page default 50. |
Returns { data: [ productβ¦ ], meta.pagination, timestamp }. Each product carries uuid, title, basePrice, cityName, image_url, images[]. Carry the product uuid. For the whole catalogue in one call use products/all; for a countryβcity tree use products/by-location.
2 β Open the product
Fetch the full product β description, images, itinerary, policies β live from the source.
GET /api/Activity/v2/products/{uuid}?SessionId=β¦
See Product details for the full body. Use /products/{uuid}/restrictions for age/pax rules.
3 β Pick a bookable variant (product-type)
A product has one or more product-types (variants: ticket tiers, durations, packages). Each has its own uuid β the productTypeUuid you book.
GET /api/Activity/v2/products/{uuid}/product-types?SessionId=β¦
Carry the chosen productTypeUuid.
4 β Check available dates & times
Confirm the variant is available and priced for the traveller's date. Already-past dates and timeslots are filtered out automatically against the activity's own timezone.
| Endpoint | Returns |
|---|---|
GET /product-types/{uuid}/price-lists | { data: [ { date, timezone, available, price, currency } β¦ ] } β the bookable-date calendar. Optional date_start/date_end query bounds. |
GET /product-types/{uuid}/price-lists/{date} | Pricing/availability for one YYYY-MM-DD; data is null if that date is already past. |
GET /product-types/{uuid}?date=YYYY-MM-DD | Variant details incl. data.timeslots[] (each with startTime) and data.timezone. Timeslots already past for the given date are dropped. |
5 β Create the booking
Reserve with the variant, date, pax mix, optional timeslot, and the lead customer. The booking is created in reserved status and mirrored locally; the platform issues it asynchronously. The office balance is held for the total.
POST /api/Activity/v2/bookings
{
"SessionId": "β¦",
"productTypeUuid": "5f3ff236-40b1-4f0a-a780-1db8649d1f5b",
"arrivalDate": "2026-10-15",
"adults": 2, "children": 0, "seniors": 0,
"timeSlot": null,
"partnerReference": "your-ref-0001",
"customer": {
"salutation": "Mr.", "firstName": "Jane", "lastName": "Doe",
"email": "traveller@example.com", "phone": "+441234567890"
}
}
| Parameter | Type | Req. | Description |
|---|---|---|---|
productTypeUuid | string (uuid) | yes | The variant to book (step 3). |
arrivalDate | string (date) | yes | Visit date YYYY-MM-DD (alias date). Defaults to today+7 if omitted. |
adults | integer | yes | Adult tickets (alias pax). Default 1. |
children / seniors | integer | β | Child / senior tickets. Default 0. |
timeSlot | string | β | The chosen timeslot when the variant exposes them (step 4). |
customer | object | yes | salutation (default "Mr."), firstName, lastName, email, phone. |
partnerReference | string | β | Your own reference, echoed on the booking. |
options | object | cond. | Answers to the product-type's booking options β required when the variant has any required: true option. See below. |
GET /product-types/{uuid} and GET /products/{uuid}/product-types) carries an options object with two arrays β perBooking (answered once) and perPax (answered once per guest). Every option lists uuid, name, description, required, addOn, inputType (1β14: 1=list, 4=string, 5=boolean, 6=date, 7=file, 13=phone, 14=flight-no, β¦), formatRegex, optional price and, for list types, an items[] array of { label, value, price }.
Submit the answers under
options using the option uuid and the guest's value β perBooking is a flat list, perPax is an array of arrays, one per traveller (adults, then children, then seniors):
"options": {
"perBooking": [
{ "uuid": "46db421e-5727-46fc-9f2c-10679e026582", "value": "EK202" }
],
"perPax": [
[ { "uuid": "543f0e45-bdfe-4dc7-af73-e7fd5eda8246", "value": "zone_1" } ],
[ { "uuid": "543f0e45-bdfe-4dc7-af73-e7fd5eda8246", "value": "zone_2" } ]
]
}
Validation. Required options are enforced before the booking is held: if a
required option is missing β including a perPax option not answered for every traveller (adults + children + seniors) β the request fails with 422 and a message naming the option (e.g. Missing required per-guest booking option: "Full name" (traveller 2)) and no balance is held. Answer every required option to book.
Add-on pricing. An option's
price, and each list items[].price, is a per-choice surcharge in USD the guest pays on top of the ticket rates (the customer-facing price, markup already applied). Options with no price (e.g. a flight number) are informational and free. The submitted options are echoed on the booking and replayed to the supplier when the agent issues it.
Returns { data: { uuid, code, status: "reserved", totalAmount, amountBreakdown[], options, links[] β¦ }, timestamp }. Carry data.uuid β it is your permanent reference for the whole lifecycle, even after it is mapped onto the real supplier UUID. Full field list in Bookings.
6 β Confirm
Confirm the reserved booking to move it toward issuance. The status transitions reserved β waiting (awaiting issuance), then to approved once issued.
PUT /api/Activity/v2/bookings/{uuid}/confirm
{ "SessionId": "β¦" }
The {status} path segment accepts confirm (β waiting) or cancel (β cancelled). See Booking status for the full status set.
7 β Get the voucher
Vouchers exist only once the booking has been issued. Poll the booking (GET /bookings/{uuid}) until status is issued, then fetch and download.
GET /api/Activity/v2/bookings/{uuid}/vouchers?SessionId=β¦
GET /api/Activity/v2/bookings/{uuid}/download-voucher/{voucher}?SessionId=β¦
vouchers returns { data: [ { uuid, generatedAt, visualId, links[] } ], timestamp } (empty until issued); download-voucher streams the voucher application/pdf through this gateway. You can also subscribe to webhook events (e.g. booking_status_updated) instead of polling.
Activity β Products #
The product catalogue of tours, attractions, and experiences. Each list item is additionally augmented by the gateway with image_url (main image) and images (full gallery) from the local image cache.
Paginated list of products, served from the gateway's local catalogue cache (kept fresh by the daily sync + provider webhooks) rather than a live upstream call β so it's fast and resilient. Each item is augmented with image_url (main image) and images (full gallery), and the response keeps the same { data, meta.pagination } envelope. For the WHOLE catalogue in one call, use GET /v2/products/all.
Query Parameters
| Name | Type | Description |
|---|---|---|
q | string | Name search in ANY stored language (default title + translated titles). Matched rows carry matchedLanguage. |
type | string | Filter by product type β typeName/tourType (name substring) or typeUuid (exact). UUIDs from /config. |
category | string | Filter by category β name substring or UUID. |
country | string | Filter by location country β name substring or UUID. |
city | string | Filter by location city β name substring or UUID. |
tag | string | Broad classification match β any of the product's categories, type or tour type. |
min_price / max_price | number | Filter by the product's basePrice (USD) range. Combine freely with the other filters β all conditions AND together. |
sort | string | One of price, -price, date, -date (date = last update), or bestselling (most bookings first). Every item carries a sold_count. |
language | string | Return each product's name in the same language you searched in (auto-detected from the query script), or force it with a code like KO. |
page / per_page | integer | Pagination (1-based). No cap β per_page=1000 returns 1000. Default 50. |
Responses
200 OK β paginated array of product summaries:
{
"data": [
{
"uuid": "52d1b30f-9314-432a-9ec8-406b525a4f5c",
"updatedAt": "2026-06-06 08:00:14",
"title": "Hong Kong Disneyland Park Ticket",
"titleTranslated": "Hong Kong Disneyland Park Ticket",
"validFrom": null,
"validThrough": null,
"basePrice": 16.91,
"typeName": "Attraction",
"typeUuid": "d3c54653-dd05-598f-b193-f6683d1064ab",
"cityName": "Hong Kong",
"links": [
{ "method": "GET", "rel": "self", "href": "https://api.hermeseus.com/api/Activity/v2/products/52d1b30f-..." },
{ "method": "GET", "rel": "productTypes", "href": "https://api.hermeseus.com/api/Activity/v2/products/52d1b30f-.../product-types" }
],
"image_url": "https://cdn.example.com/images/content/original/b4d63993-....jpg",
"images": [ "https://cdn.example.com/images/content/original/b4d63993-....jpg", "..." ]
}
],
"meta": { "pagination": { "page": 1, "pageCount": 3, "perPage": 50, "total": 142 } },
"timestamp": "2026-06-07T14:10:58.976+08:00"
}
Returns the entire product catalogue in a single response, served from the gateway's local cache β there is no 100-per-page limit. Use this when you need to pull every product at once (e.g. to build your own index) instead of walking the paginated GET /v2/products list. The cache is refreshed by the gateway on a daily schedule, so results reflect the most recent sync. By default each row is a lightweight summary (UUID, name, main image, and the product's top-level scalar fields); pass full=1 to receive every product's complete object. Every row is augmented with image_url (and, in full mode, images) from the local image cache.
Query Parameters
| Name | Type | Description |
|---|---|---|
full | boolean | When 1/true, return each product's full object (heavy). Default returns a summary row. |
q | string | Case-insensitive name search. Matches the product name in any stored language β the default title plus every translated title field (title, titleTranslated, β¦). Descriptions are not matched. |
type | string | Filter by product type β typeName/tourType (name substring) or typeUuid (exact). |
category | string | Filter by a category β categories[].name (substring) or categories[].uuid (exact). Categories are hierarchical: passing a parent category UUID also matches products in any of its child categories. |
country | string | Filter by location country β locations[].country (substring) or locations[].countryUuid (exact). |
city | string | Filter by location city β locations[].city (substring) or locations[].cityUuid (exact). |
tag | string | Broad classification match β hits when the value matches any of the product's categories, type or tour type (name substring or UUID). |
min_price / max_price | number | Filter by basePrice (USD) range. ANDs with the other filters. |
sort | string | bestselling (most bookings first), price/-price, or date/-date. Every row carries a sold_count. |
language | string | Return each product's name in this language (e.g. KO, JA) when a translation exists. If omitted, a row that matched q is returned in the language q was written in β so a Korean query yields Korean names. Localized rows carry matchedLanguage. |
per_page / page | integer | Optional chunking with no hard cap. Omit per_page to receive the whole (filtered) catalogue in one response. |
All filters are optional and AND together. The response adds total (products matching the filters) next to count (rows in this response). UUIDs for country/city come from GET /v2/products/by-location; type/category UUIDs from GET /v2/config.
Responses
200 OK β summary rows (default; example for ?type=Attraction):
{
"success": true,
"count": 142,
"total": 142,
"data": [
{
"uuid": "52d1b30f-9314-432a-9ec8-406b525a4f5c",
"name": "Hong Kong Disneyland Park Ticket",
"image_url": "https://cdn.example.com/images/content/original/b4d63993-....jpg",
"title": "Hong Kong Disneyland Park Ticket",
"basePrice": 16.91,
"currency": "USD",
"typeName": "Attraction"
}
]
}
With ?full=1 each item in data is the complete Product object (same shape as GET /v2/products/{uuid}), plus image_url and images.
Public endpoint β no SessionId or credentials required. A breakdown of how many products are available in each country and, within it, each city β computed from the local catalogue cache (no upstream calls). Each country object lists its cities with a per-city product count and a country total. A product that spans several cities is counted once per distinct city, and once toward each distinct country's total (so total is the number of products available in that country, not the sum of its cities). Countries are sorted by total descending, cities by count descending.
Responses
200 OK:
{
"success": true,
"countries": 37,
"products": 2249,
"data": [
{
"country": "United Arab Emirates",
"countryUuid": "1f2e3d4c-...",
"cities": [
{ "city": "Dubai", "cityUuid": "aaaa-...", "count": 128 },
{ "city": "Abu Dhabi", "cityUuid": "bbbb-...", "count": 31 }
],
"total": 159
},
{
"country": "Singapore",
"countryUuid": "9a8b7c6d-...",
"cities": [
{ "city": "Singapore", "cityUuid": "cccc-...", "count": 142 }
],
"total": 142
}
]
}
Full product metadata. Returns a single data object with descriptive fields (description, highlights, additionalInfo, priceIncludes/priceExcludes, itinerary, warnings, safety), commercial fields (basePrice, currency, isFlatPaxPrice, minPax/maxPax, reviewCount, reviewAverageScore, typeName/typeUuid), logistics (latitude, longitude, address, hotelPickup, airportPickup, businessHoursFrom/To, language lists), media (photos, image_url, images), categories, locations, and links. *Translated twins carry the localized text.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
uuid | string (uuid) | yes | Product UUID (from the products list). |
Responses
200 OK β the Product object (excerpt):
{
"data": {
"uuid": "4dd77d8e-043b-4edb-b32a-0776043143ad",
"updatedAt": "2026-05-16 08:03:13",
"title": "MINT Museum of Toys Ticket",
"titleTranslated": "MINT Museum of Toys Ticket",
"description": "The MINT Museum of Toys houses Asia's largest collection of vintage toys ...",
"highlights": "Delve into nostalgia with Asia's largest collection of vintage toys ...",
"priceIncludes": "Admission ticket",
"priceExcludes": "Hotel pickup and drop-off",
"basePrice": 11.66,
"currency": { "code": "USD", "symbol": "$" },
"isFlatPaxPrice": false,
"minPax": 1,
"maxPax": 99,
"reviewCount": 0,
"reviewAverageScore": 0,
"typeName": "Attraction",
"typeUuid": "d3c54653-dd05-598f-b193-f6683d1064ab",
"latitude": "1.2931", "longitude": "103.8520",
"address": "26 Seah Street, Singapore 188382",
"hotelPickup": false, "airportPickup": false,
"categories": [ { "name": "Museums", "uuid": "..." } ],
"locations": [ { "city": "Singapore", "country": "Singapore", "cityUuid": "...", "countryUuid": "..." } ],
"photos": [ { "uuid": "...", "paths": { "original": "...", "1280x720": "..." } } ],
"image_url": "https://cdn.example.com/images/content/original/....jpg",
"images": [ "https://cdn.example.com/...jpg", "..." ],
"links": [
{ "method": "GET", "rel": "self", "href": "https://api.hermeseus.com/api/Activity/v2/products/4dd77d8e-..." },
{ "method": "GET", "rel": "productTypes", "href": "https://api.hermeseus.com/api/Activity/v2/products/4dd77d8e-.../product-types" }
]
},
"timestamp": "2026-06-07T14:10:58.976+08:00"
}
Nationality / country-of-origin booking restrictions for a product. allow is a whitelist and exclude a blacklist of country codes; both empty means no restriction.
Response
{
"data": { "exclude": [], "allow": [] },
"timestamp": "2026-06-07T14:10:59.732+08:00"
}
Bookable variants of a product (e.g. βAdmissionβ, βAdmission with Guided Tourβ, adult/child bundles). Each item carries the productTypeUuid you pass to POST /v2/bookings, plus links to its price-lists.
Response
{
"data": [
{
"uuid": "80590844-e665-4fdc-843c-ffc6fe893403",
"title": "Admission with Complimentary Guided Tour",
"links": [
{ "method": "GET", "rel": "self", "href": "https://api.hermeseus.com/api/Activity/v2/product-types/80590844-..." },
{ "method": "GET", "rel": "priceLists", "href": "https://api.hermeseus.com/api/Activity/v2/product-types/80590844-.../price-lists" },
{ "method": "GET", "rel": "product", "href": "https://api.hermeseus.com/api/Activity/v2/products/4dd77d8e-..." }
]
}
],
"timestamp": "2026-06-07T14:10:58.976+08:00"
}
Activity β Product Types #
Details of a single bookable variant: capacity (minPax/maxPax), per-category ticketTypes (adult/child/senior/infant/youth) with their gateRatePrice (USD), refundability (isNonRefundable, cancellationPolicies), durationDays/durationHours, voucher instructions, time slots, timezone, and options.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
uuid | string (uuid) | yes | Product-type UUID. |
Responses
200 OK β the ProductType object (excerpt):
{
"data": {
"uuid": "68ec3814-b54e-4e9a-ba84-7a92cfadbfb2",
"title": "Adult Pass",
"durationDays": 30, "durationHours": 0,
"firstAvailabilityDate": "2026-05-31",
"isNonRefundable": true,
"minPax": 1, "maxPax": 10,
"instantConfirmation": true,
"adultGateRatePrice": 25.54,
"ticketTypes": [
{ "type": "adult", "label": "Person", "allowed": true, "min": 1, "max": 10, "gateRatePrice": 20 },
{ "type": "child", "label": "Child", "allowed": false },
{ "type": "senior", "label": "Senior", "allowed": false }
],
"cancellationPolicies": [],
"timeslots": [],
"timezone": "Asia/Singapore",
"options": { "perBooking": [], "perPax": [] },
"links": [
{ "method": "GET", "rel": "self", "href": "https://api.hermeseus.com/api/Activity/v2/product-types/68ec3814-..." },
{ "method": "GET", "rel": "priceLists", "href": "https://api.hermeseus.com/api/Activity/v2/product-types/68ec3814-.../price-lists" },
{ "method": "GET", "rel": "product", "href": "https://api.hermeseus.com/api/Activity/v2/products/701cc19d-..." }
]
},
"timestamp": "2026-05-31T04:09:10.378+08:00"
}
Price & availability calendar for a product-type, one entry per date in the requested range (date_start / date_end, capped at 6 months from today). Each date lists availability per category, the bookable prices[] (each with a prices[].id = the priceId), voucherValidity, timezone and any cancellationPolicy.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
uuid | path | string (uuid) | yes | Product-type UUID. |
date_start | query | string (date) | no | Range start (default today). |
date_end | query | string (date) | no | Range end (capped 6 months out). |
Inside prices[].rates[] each rate has a type (retailPrice = sell price, nettPrice = your cost), a category (adult/child/senior), the USD amount, and a format string showing the original price (e.g. "S$30.00").
Response (excerpt)
{
"data": [
{
"date": "2026-07-01",
"weekday": "Wednesday",
"availability": [
{ "category": "adult", "type": "shared", "quantity": 999 },
{ "category": "child", "type": "shared", "quantity": 999 },
{ "category": "senior", "type": "shared", "quantity": 999 }
],
"cancellationPolicy": [],
"currency": "USD",
"timezone": "Asia/Singapore",
"voucherValidity": { "adult": { "date": "2026-10-01" }, "child": { "date": "2026-10-01" }, "senior": { "date": "2026-10-01" } },
"prices": [
{
"id": "ffa35637-4073-4432-9093-e79c5aa33b81",
"rates": [
{ "type": "retailPrice", "category": "adult", "currency": "USD", "amount": 23.32, "format": "S$30.00" },
{ "type": "nettPrice", "category": "adult", "currency": "USD", "amount": 18.66, "format": "S$24.00" }
/* ... child, senior ... */
]
}
]
}
/* ... one object per date in the range ... */
],
"timestamp": "2026-06-07T14:11:00.000+08:00"
}
Checks real-time availability and price for a single {date} (YYYY-MM-DD): data is one price-list object with the same availability / prices[].rates[] shape.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
uuid | string (uuid) | yes | Product-type UUID. |
date | string (date) | yes | The date to check, YYYY-MM-DD. |
Activity β Bookings #
Create an activity booking. Returns the booking in reserved status; the gateway mirrors it locally and finalises it asynchronously. Use data.uuid as the reference for all follow-up calls β it keeps working for the whole lifecycle. The office balance is held for the booking total.
Request Body application/json
| Property | Type | Required | Description |
|---|---|---|---|
SessionId | string | yes | Active session id. |
productTypeUuid | string (uuid) | yes | The bookable variant to reserve (from GET products/{uuid}/product-types). |
arrivalDate | string (date) | yes | Visit date, YYYY-MM-DD. |
adults | integer | yes | Number of adult tickets. May be 0 only when the product-type's minPax = 0. |
children | integer | no | Number of child tickets. Default 0. |
seniors | integer | no | Number of senior tickets. Default 0. |
customer | object | yes | Lead customer: salutation, firstName, lastName, email, phone. |
partnerReference | string | no | Your own booking id, echoed back on the booking. |
message | string | no | Free-text message to the host/supplier. |
timeSlotUuid | string (uuid) | no | Required only when the product-type exposes time slots. |
options | object | no | Selected add-on options: { "perBooking": [...], "perPax": [{ "uuid": "...", "value": "1" }] }. Each entry references an option uuid and its value. |
Request β example
{
"productTypeUuid": "5f3ff236-40b1-4f0a-a780-1db8649d1f5b",
"arrivalDate": "2026-06-15",
"partnerReference": "your-ref-123",
"adults": 2,
"children": 0,
"seniors": 0,
"customer": {
"salutation": "Mr.",
"firstName": "Jane",
"lastName": "Doe",
"email": "jane@example.com",
"phone": "+971501234567"
},
"options": { "perBooking": [], "perPax": [] }
}
Responses
200 OK β the created booking (envelope { "data": Booking, "timestamp": ... }), status reserved.
{
"data": {
"uuid": "1d59a17e-9726-4283-a5b7-bab7886e4709",
"code": "Z2KB84Z",
"partnerReference": "your-ref-123",
"status": "reserved",
"productTypeTitle": "Diamond Head State Monument Self-guided Hike",
"productTypeUuid": "5f3ff236-40b1-4f0a-a780-1db8649d1f5b",
"currencyCode": "USD",
"totalAmount": 18.50,
"amountBreakdown": [ { "name": "adult", "quantity": 2, "price": "9.25" } ],
"arrivalDate": "2026-06-15",
"adults": 2, "children": 0, "seniors": 0,
"ticketTypes": [ { "type": "adult", "quantity": 2 } ],
"links": [ "... self / confirm / cancel / productType / product ..." ]
},
"timestamp": "2026-06-15T16:24:49.648+08:00"
}
Filterable, paginated list of your bookings.
Query Parameters
| Name | Type | Description |
|---|---|---|
date_start / date_end | string (date) | Filter by arrival-date range. |
first_name / last_name | string | Filter by customer name. |
email / phone | string | Filter by customer contact. |
partner_reference | string | Filter by your own partnerReference. |
query | string | Free-text search (e.g. customer name). |
status | string | Filter by status β see the Booking statuses table (reserved, waiting, cancellation_requested, cancelled, approved, expired, rejected, refunded, refund_declined). |
page / per_page | integer | Pagination. |
Responses
200 OK β data is an array of Booking objects with meta.pagination.
A single booking by its reference UUID.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
uuid | string (uuid) | yes | Booking reference returned at create time, or the upstream UUID once issued. |
Responses
200 OK β the Booking object:
{
"data": {
"uuid": "ae80dd16-42c7-485c-8d7e-703d948366eb",
"code": "VKH3YHE",
"partnerReference": "TTA_1780405735695943",
"status": "reserved",
"productTypeTitle": "Professional Photo Shoot in Bali - 1 Hour",
"productTypeUuid": "97475b54-3378-4eb2-8f19-85c8b76255d8",
"currencyCode": "USD",
"totalAmount": 156.45,
"amountBreakdown": [ { "name": "adult", "quantity": 1, "price": "156.45" } ],
"arrivalDate": "2026-06-25",
"timeSlot": null,
"createdAt": "2026-06-02 21:08:59",
"updatedAt": "2026-06-02 21:08:59",
"salutation": "Mr.", "firstName": "Jane", "lastName": "Doe",
"email": "jane@example.com", "phone": "+9120175399",
"adults": 1, "children": 0, "seniors": 0,
"ticketTypes": [ { "type": "adult", "quantity": 1 }, { "type": "child", "quantity": 0 }, { "type": "senior", "quantity": 0 } ],
"options": [],
"completedAt": null,
"cancellationRequestAt": null,
"cancellationRequestStatus": "none",
"cancellationStatus": null,
"refundDate": null, "refundAmount": null, "refundTransaction": null,
"links": [
{ "method": "GET", "rel": "self", "href": "https://api.hermeseus.com/api/Activity/v2/bookings/ae80dd16-..." },
{ "method": "PUT", "rel": "confirm", "href": "https://api.hermeseus.com/api/Activity/v2/bookings/ae80dd16-.../confirm" },
{ "method": "PUT", "rel": "cancel", "href": "https://api.hermeseus.com/api/Activity/v2/bookings/ae80dd16-.../cancel" }
]
},
"timestamp": "2026-06-02T21:09:00.000+08:00"
}
Booking status values: reserved (created, awaiting confirmation), approved/confirmed (issued), cancelled, expired, rejected.
Drive a lifecycle transition with {status} = confirm or cancel:
- A new booking starts in the
reservedstatus. confirmmoves it towaitingand locks the inventory; on our side, funds equal tototalAmountare held on your office balance.- Bookings left in
waitingexpire if not actioned within 5 days. cancelraises a cancellation request (subject to approval) and releases any active hold.
See the full Booking statuses table below.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
uuid | string (uuid) | yes | Booking reference. |
status | string | yes | Target transition β typically confirm or cancel. |
Request Body application/json
Only SessionId is required; the rest of the body may be empty ({ "SessionId": "..." }).
Responses
200 OK β the updated booking status:
{
"data": {
"uuid": "ae80dd16-42c7-485c-8d7e-703d948366eb",
"status": "reserved",
"updatedAt": "2026-06-02 21:09:31"
},
"timestamp": "2026-06-02T21:09:31.053+08:00"
}
Booking statuses
A booking's status field moves through the lifecycle below (applies to both instant- and non-instant-confirmation products):
| Status | Meaning |
|---|---|
reserved | Created; inventory not yet locked. Confirm it (β waiting) or it will expire. |
waiting | Confirmed by you; inventory locked, awaiting issuance. Expires if not actioned within 5 days. |
approved | Confirmed/issued by the inventory source. Vouchers are available. |
cancellation_requested | A cancellation request was raised and is pending approval. |
cancelled | Cancellation approved. Any active balance hold is released. |
expired | A reserved/waiting booking that timed out. |
rejected | The inventory source rejected the booking. |
refunded | Cancelled and refunded. |
refund_declined | A refund request was declined. |
Retrieves the issued vouchers / tickets for a confirmed booking. Each voucher carries a uuid, generatedAt, an optional visualId, and links (a download link and a booking link). All links[].href point at this gateway β including the download link, which targets the voucher-download endpoint below.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
uuid | string (uuid) | yes | Booking reference. |
Responses
200 OK β list of vouchers:
{
"data": [
{
"uuid": "81995901-b405-4cf6-8cf8-df284f104117",
"generatedAt": "2026-05-28 16:25:15",
"links": [
{ "rel": "download", "href": "https://api.hermeseus.com/api/Activity/v2/bookings/{uuid}/download-voucher/81995901-..." },
{ "rel": "booking", "href": "https://api.hermeseus.com/api/Activity/v2/bookings/{uuid}" }
],
"visualId": null
}
],
"meta": { "pagination": { /* ... */ } },
"timestamp": "2026-06-02T06:40:59.864+08:00"
}
Downloads the actual voucher file (usually a PDF). The gateway fetches it from the supplier and streams the bytes back through this domain β the supplier URL is never exposed. The response is the raw file with Content-Disposition: attachment, not JSON. {uuid} accepts either the booking reference you received at create time or the upstream UUID. As with every endpoint, include your SessionId (query param) when requesting the file.
curl -L -X GET \
'https://api.hermeseus.com/api/Activity/v2/bookings/{uuid}/download-voucher/{voucher}?SessionId=...' \
-o voucher.pdf
Fetches essential configuration: supported languages, currencies, locations (countries, cities), product types and categories, timezones, the base URLs, and your own user profile. The UUIDs returned here are the ones you pass to the type / category / country / city / language filters elsewhere.
user block reflects your account: name/email/uuid are your credentials and the wallet* figures are your office balance (USD) β walletBalance = total, walletBlockedBalance = held (shown negative), walletAvailableBalance = available. serverUrl/photosUrl point at this gateway.Responses
200 OK β configuration object (excerpt):
{
"data": {
"now": "2026-06-07T00:05:01.740500Z",
"version": 2,
"serverUrl": "https://api.hermeseus.com/api/Activity",
"photosUrl": "https://api.hermeseus.com",
"activitiesSorting": [ "date", "-date", "price", "-price", "distance", "-distance" ],
"user": {
"name": "<your office name>",
"email": "<your email>",
"uuid": "<your user uuid>",
"defaultPagination": 50,
"defaultSortBy": "date",
"defaultCurrencyCode": "USD",
"defaultLanguageCode": "EN",
"walletBalance": 1000.00,
"walletBlockedBalance": -250.50,
"walletAvailableBalance": 749.50,
"walletAlertValue": 0
},
"languages": { "data": [ { "name": "english", "code": "EN", "uuid": "..." } ] },
"countries": { "data": [ { "name": "Singapore", "code": "SG", "uuid": "..." } ] },
"currencies": { "data": [ { "code": "USD", "symbol": "$", "uuid": "cd15153e-..." } ] },
"types": { "data": [ { "name": "Attraction", "uuid": "..." } ] },
"categories": { "data": [ { "name": "Museums", "uuid": "..." } ] }
},
"timestamp": "2026-06-07T08:05:01.740+08:00"
}
Activity β Webhook Event Polling #
The gateway captures upstream lifecycle events and exposes them as a polling API, so you do not need to stand up your own webhook receiver. Query the endpoint below to inspect the latest known state of a product, product-type, or booking.
| Event | Use |
|---|---|
product_available / product_updated | Catalogue changes. |
product_type_available / _not_available / _content_updated / _pricing_updated / _availability_updated | Per-variant changes. |
booking_status_updated | Booking transitioned to a new status. |
booking_data_updated | Booking metadata changed. |
booking_tickets_updated | Voucher / ticket issued. |
Latest payload received for that (event, UUID). Add ?history=1&limit=20 to get up to 50 most recent payloads instead.
Response β found
{
"event": "booking_status_updated",
"primaryUuid": "b8e2f01c-...",
"found": true,
"receivedAt": "2026-05-21T03:11:48+00:00",
"eventTimestamp": "2026-05-21 11:11:42 SGT",
"signature": "a1b2c3...",
"payload": { "bookingUuid": "...", "currentStatus": "APPROVED", ... }
}
Response β none yet
{ "event": "...", "primaryUuid": "...", "found": false, "message": "No webhook of this type has been received yet for this UUID." }
Activity β Schemas #
Reusable object models referenced by the Activity operations above. All monetary fields are in USD and all HATEOAS links[].href point at this gateway. Many text fields have a *Translated twin carrying the localized value for the requested language.
Catalogue
ProductSummary products list item
| Field | Type | Description |
|---|---|---|
uuid | string (uuid) | Product UUID. |
title / titleTranslated | string | Product title (English / requested language). |
basePrice | number | Base price (USD). |
typeName / typeUuid | string | Product type name & UUID. |
validFrom / validThrough | string | null | Availability window (null = no limit). |
updatedAt | string | Last update timestamp. |
links | Link[] | HATEOAS links (self, productTypes). |
image_url / images | string / string[] | Gateway-added main image & gallery. |
Product
Full product. Includes all ProductSummary fields plus:
| Field | Type | Description |
|---|---|---|
description, highlights, additionalInfo | string | Rich content (each with a *Translated twin). |
priceIncludes / priceExcludes | string | What is / isn't included. |
itinerary, warnings, safety | string | null | Itinerary, safety & insurance warnings. |
latitude / longitude / address | string | Location. |
minPax / maxPax | integer | Pax limits (adults). |
isFlatPaxPrice | boolean | Same price regardless of pax count. |
reviewCount / reviewAverageScore | integer / number | Review stats. |
hotelPickup / airportPickup | boolean | Pickup availability (deprecated). |
businessHoursFrom / businessHoursTo | string | Supplier business hours. |
categories | Category[] | Listing categories. |
locations | Location[] | Where the product operates. |
photos | Photo[] | Gallery photos with sized paths. |
guideLanguages, writtenLanguages, audioHeadsetLanguages | Language[] | Available languages. |
ProductType
| Field | Type | Description |
|---|---|---|
uuid | string (uuid) | Product-type UUID (used to book). |
title / titleTranslated | string | Variant title. |
description / descriptionTranslated | string | Variant description. |
durationDays / durationHours / durationMinutes | integer | Activity duration. |
minPax / maxPax | integer | null | Booking pax limits. |
firstAvailabilityDate | string | Earliest bookable date. |
daysInAdvance / cutOffTime | integer | null | Lead-time / cut-off constraints. |
isNonRefundable | boolean | Whether the variant is non-refundable. |
cancellationPolicies / cancellationPolicySummary | array / string | Cancellation terms (refundable variants). |
allowAdults / allowChildren / allowSeniors / allowInfant | boolean | Permitted ticket categories (see ticketTypes). |
ticketTypes | TicketType[] | Per-category booking rules. |
instantConfirmation | boolean | Booking confirms instantly. |
nonInstantVoucher / directAdmission / voucherRequiresPrinting | boolean | Voucher handling rules. |
voucherUse, voucherRedemptionAddress, meetingAddress, meetingTime | string | null | Redemption / meeting instructions (with *Translated twins). |
validity | object | E-ticket / voucher validity rule. |
timeslots | array | Time slots, when applicable. |
timezone | string | Product-type timezone. |
options / hasOptions | ProductTypeOptions / boolean | Add-on options. |
TicketType
| Field | Type | Description |
|---|---|---|
type | string | Category: adult / child / senior / infant / youth. |
quantity | integer | Ticket quantity (in bookings) or allowed flag (in product types). |
Pricing & availability
AvailabilityDate
| Field | Type | Description |
|---|---|---|
date / weekday | string | Travel date and weekday. |
availability | Availability[] | Remaining quantity per category. |
prices | PriceDetail[] | Bookable price configurations. |
currency | string | Currency (USD). |
timezone | string | Product-type timezone. |
cancellationPolicy | array | Per-date cancellation terms. |
voucherValidity | object | Voucher validity per category for this date. |
Availability
| Field | Type | Description |
|---|---|---|
category | string | Ticket category. |
type | string | Inventory type (e.g. shared, exclusive). |
quantity | integer | Remaining quantity. |
PriceDetail
| Field | Type | Description |
|---|---|---|
id | string (uuid) | Price configuration id. |
rates | RateDetail[] | Per-category rates. |
RateDetail
| Field | Type | Description |
|---|---|---|
type | string | Rate type: retailPrice (sell) or nettPrice (cost). |
category | string | Ticket category. |
amount | number | Price in USD. |
currency | string | Currency (USD). |
format | string | Original supplier price string, e.g. "S$30.00". |
Bookings
Booking BookingDetails
| Field | Type | Description |
|---|---|---|
uuid | string (uuid) | Booking reference (your permanent id). |
code | string | 7-character booking code. |
partnerReference | string | Your own reference. |
status | string | See Booking statuses. |
productTypeTitle / productTypeUuid | string | Booked variant. |
totalAmount | number | Total price (USD). |
currencyCode / currencyUuid | string | Currency (USD). |
amountBreakdown | AmountBreakdown[] | Per-category price breakdown. |
arrivalDate / timeSlot | string | Visit date and time slot. |
adults / children / seniors | integer | Pax counts. |
ticketTypes | TicketType[] | Per-category quantities. |
salutation, firstName, lastName, email, phone | string | Lead customer. |
options | array | Selected add-on options. |
completedAt, cancellationRequestAt, cancellationRequestStatus, cancellationStatus | string | null | Lifecycle timestamps / states. |
refundDate / refundAmount / refundTransaction | string | number | null | Refund details. |
createdAt / updatedAt | string | Timestamps. |
links | Link[] | HATEOAS links (self, confirm, cancel). |
BookingCustomer
| Field | Type | Description |
|---|---|---|
salutation | string | e.g. Mr., Ms.. |
firstName / lastName | string | Letters, spaces, hyphen and apostrophe only. |
email | string (email) | Customer email. |
phone | string | Customer phone. |
AmountBreakdown
| Field | Type | Description |
|---|---|---|
name | string | Category (e.g. adult). |
quantity | integer | Number of tickets. |
price | string | Per-ticket price (USD, string). |
BookingOptions
Sent on POST /v2/bookings as options:
| Field | Type | Description |
|---|---|---|
perBooking | { uuid, value }[] | Options applied once per booking. |
perPax | { uuid, value }[] | Options applied per passenger. |
Vouchers
BookingVoucher
| Field | Type | Description |
|---|---|---|
uuid | string (uuid) | Voucher UUID. |
generatedAt | string | When the voucher was generated. |
downloadedAt | string | null | When it was last downloaded via the API. |
visualId | string | null | Display id, when applicable. |
links | Link[] | download (file) and booking links on this gateway. |
Reference (config)
Compact lookup objects returned by GET /v2/config; each list is wrapped as { "data": [ ... ] }.
| Schema | Fields |
|---|---|
| Currency | code (e.g. USD), symbol, uuid |
| Language | name, code (e.g. EN), uuid |
| ActivityType | name, uuid |
| Category | name, uuid, children (nested Category[]) |
| Country | name, code, uuid, states (State[]) |
| State | name, uuid, cities (City[]) |
| City | name, uuid |
| Location | city/cityUuid, state/stateUuid, country/countryUuid |
| Photo | uuid, caption, paths ({ original, 1280x720, β¦ }) |
Common
| Schema | Fields |
|---|---|
| Link | method, rel, href (always on this gateway) |
| Pagination | page, pageCount, perPage, total (under meta.pagination) |
| ErrorResponse | error.code, error.message, error.http_code |
Common #
Returns the current office balance, held funds and available balance in USD.
Response
{
"Success": true,
"Balance": 12450.00,
"HeldBalance": 340.20,
"AvailableBalance": 12109.80,
"Currency": "USD",
"Error": null
}
Human-readable explanation for any Err... code.