Streaming flight search
Stream flight results as they arrive over server-sent events, for a fast first paint and progressively filling results.
POST/Air/AirLowFareSearchIncremental
Access requires the permission air_low_fare_search
This is the streaming twin of AirLowFareSearch. It takes the same request body. Instead of one JSON response, it returns a text/event-stream. Read it as a stream and keep the connection open until the done event.
Request
The body is identical to AirLowFareSearch.
curl -N -X POST https://api.hermeseus.com/api/Air/AirLowFareSearchIncremental \
-H "Content-Type: application/json" \
-d '{
"SessionId": "8f3c81d2-4a5b-4c6d-9e0f-1a2b3c4d5e6f",
"AdultCount": 1,
"PricingSourceType": "All",
"RequestOption": "All",
"TravelPreference": { "CabinType": 3 },
"OriginDestinationInformations": [
{ "OriginLocationCode": "IST", "DestinationLocationCode": "DXB", "DepartureDateTime": "2026-10-12T00:00:00" }
]
}'const res = await fetch("https://api.hermeseus.com/api/Air/AirLowFareSearchIncremental", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
SessionId: "8f3c81d2-4a5b-4c6d-9e0f-1a2b3c4d5e6f",
AdultCount: 1,
PricingSourceType: "All",
RequestOption: "All",
TravelPreference: { CabinType: 3 },
OriginDestinationInformations: [
{ OriginLocationCode: "IST", DestinationLocationCode: "DXB", DepartureDateTime: "2026-10-12T00:00:00" }
]
})
});
const reader = res.body.getReader();
const decoder = new TextDecoder();
let itineraries = [];
while (true) {
const { value, done } = await reader.read();
if (done) break;
for (const block of decoder.decode(value).split("\n\n")) {
if (!block.startsWith("event:")) continue;
const event = block.match(/event: (.*)/)[1];
const data = JSON.parse(block.match(/data: (.*)/)[1]);
if (event === "itineraries") itineraries = data.PricedItineraries; // replace, do not append
if (event === "done") console.log("finished", data.Total);
}
}import json
import requests
payload = {
"SessionId": "8f3c81d2-4a5b-4c6d-9e0f-1a2b3c4d5e6f",
"AdultCount": 1,
"PricingSourceType": "All",
"RequestOption": "All",
"TravelPreference": {"CabinType": 3},
"OriginDestinationInformations": [
{"OriginLocationCode": "IST", "DestinationLocationCode": "DXB", "DepartureDateTime": "2026-10-12T00:00:00"}
],
}
with requests.post(
"https://api.hermeseus.com/api/Air/AirLowFareSearchIncremental",
json=payload, stream=True,
) as res:
event = None
for line in res.iter_lines(decode_unicode=True):
if line.startswith("event:"):
event = line.split(": ", 1)[1]
elif line.startswith("data:"):
data = json.loads(line.split(": ", 1)[1])
if event == "itineraries":
itineraries = data["PricedItineraries"] # replace, do not appendResponse
The response is a stream of events. Each event has an event line and a data line.
| Event | Data | Meaning |
|---|---|---|
itineraries | { "PricedItineraries": [ ... ], "Replace": true } | The complete current basket. Replace your list with it. Do not append. |
done | { "Success": true, "SearchId": N, "Total": N } | Terminal. The search finished. SearchId works like the blocking endpoint. |
error | { "Success": false, "Error": { "Id", "Message" } } | Terminal. The search failed. |
event: itineraries
data: {"PricedItineraries":[ { "FareSourceCode": "...", "AirItineraryPricingInfo": { "ItinTotalFare": { "TotalFare": 219.60, "Currency": "USD" } } } ],"Replace":true}
event: itineraries
data: {"PricedItineraries":[ { "FareSourceCode": "..." }, { "FareSourceCode": "..." } ],"Replace":true}
event: done
data: {"Success":true,"SearchId":428713,"Total":137}
Replace, do not append.
Every
itineraries event carries the whole basket so far, sorted and de-duplicated. Overwrite your list each time rather than adding to it.

