niklas‑stephan.de

Lidl-Reisen: unofficial search API (GraphQL) for trip/price scans

Last updated 05/11/2026 · travel, api, web, booking, lidl

Endpoints (de; AT/CH have their own hosts, discover via $ENV in the page HTML)

The Angular app shell at https://www.lidl-reisen.de/... embeds a $ENV={...} block with every base URL:

  • Search GraphQL: https://search-api-mvp2prod.lidl-reisen.de/graphql — no auth, introspection enabled (query { __schema { types { name } } }).
  • Product REST: https://product-api-mvp2prod.lidl-reisen.de/product/{productId}/journey?offer-id=<oId>&adult-count=2 plus /accommodation, /accommodation/{tourOperator}, /destination, /included-services, /service-extras. Needs header application-domain: Lidl_de_DE (valid: Lidl_de_DE|Lidl_de_AT|Lidl_de_CH).
  • Booking / payment / rating: booking-api-mvp2prod.lidl-reisen.de, travel-payment-api.mvp2prd.sit.sys.odj.cloud, travel-accommodation-rating-api.mvp2prd.sit.sys.odj.cloud (booking not tested).

Copy-paste probe:

curl -sS -X POST https://search-api-mvp2prod.lidl-reisen.de/graphql \
  -H 'Content-Type: application/json' -H 'application-domain: Lidl_de_DE' \
  -d '{"query":"query { travelSearchResults(destination:\"Hurghada\", adultCount:2, dateFrom:\"2026-11-22\", dateTo:\"2026-12-06\", minTravelDuration:7, maxTravelDuration:7, departureAirports:[\"FRA\"], selectedFilter:[\"All-Inclusive\",\"Direktflug\",\"Doppelzimmer\"], orderBy: PRICE_TOTAL_ASCENDING, resultLimit:10) { totalResults searchResult { productId accommodation { title stars } fromPrice { priceTotal pricePerson } } } }"}'

GraphQL root fields (on TravelSearch)

Field Use
travelSearchResults product/destination search; args incl. destination, freeText, productIds, departureAirports, dateFrom/dateTo, minTravelDuration/maxTravelDuration, adultCount, childrenAges, selectedFilter, minPriceTotal/maxPriceTotal, minAccommodationRating, orderBy, resultLimit, category
offerSearchResults all offers for one productId → GroupedTravelOffers { groupName, groupType, offers[...] }
offerAvailableTravelDates the price calendar (isBestPrice, fromDate, offerId, price)
offerAvailableTravelDurations cheapest price per duration for a window
filterResults list of valid selectedFilter tags + match counts
travelSearchSurroundingResults hotels near a product
availableAirportSearchResults airports that actually serve a product (requires **productId**)
travelSearchAlternativeRequests, travelSearchAlternativeProducts, destinationSuggestResults, teaserSearchResult misc

Enums: SearchResultOrderBy = BEST_OFFERS_FIRST, PRICE_TOTAL_ASCENDING, PRICE_TOTAL_DESCENDING, PRICE_PER_NIGHT_ASCENDING, PRICE_PER_PERSON_ASCENDING; DealType = ALL, ONLY_TOP_DEALS, ONLY_NON_TOP_DEALS; SearchRouteType = HOTEL, CITY, REGION, COUNTRY, DEPARTURE_AIRPORT, ARRIVAL_AIRPORT, ...; OfferLiteGroupBy = NONE, ROOM_TITLE.

Object shapes: TravelProduct → productId, accommodation, offerInformation, travelInformation, inclusiveServices, tourOperator, traveller, travelDate, roomBoard, fromPrice. Price → priceTotal, pricePerson, priceDiscountTotal, currency, isOfferSourcePrice. AccommodationRating → rating, recommendation, reviewCount, ratingType.

Gotchas

  • fromPrice / price are lists of Price objects, not objects.
  • selectedFilter values are the literal German tag strings from the URL tags= param (All-Inclusive, Direktflug, Doppelzimmer, Strand, Rail & Fly, …) — get the full list from filterResults.
  • Prices are live: they moved by ~30 € within minutes during one session. Always timestamp a snapshot.
  • travelInformation.departureAirport.airportCode often returns TBA in travelSearchResults; unknown codes silently fall back (e.g. TXL → FRA). Use offerSearchResults for per-offer airport accuracy.
  • InclusiveService objects have only description / isIncluded / until — there is no name field.
  • Duration levers: shorter trips are cheaper in absolute terms but worse per night; the sweet spot for Sentido Naga Bay was 7 nights (~200 €/night). minTravelDuration/maxTravelDuration can be a range in one call.
  • In offerAvailableTravelDates, priceTotal is 0 — use pricePerson.
  • In every search, dateFrom/dateTo bound the departure date, not the return. To enforce “back by X”, filter results on travelDate.dateTo <= X client-side.
  • travelSearchResults with only productIds (no dates) returns arbitrary default offers.
  • robots.txt for *.de is self-contradictory (Allow: / then Disallow: /) — keep request volume low and personal.

Scanner script

Kept in the KB repo so every host has it after mem sync: <kb>/bin/lidl-reisen.py (/data/ai-persistence/bin/lidl-reisen.py on metis/iris/handtrixxx.com, /home/handtrixxx/code/ai-persistence/bin/lidl-reisen.py on the ThinkPad; also /home/handtrixxx/code/lidl-reisen/lidl_api.py). Stdlib only, verified working 2026-10-28:

./lidl_api.py find    --destination Hurghada --from 2026-11-22 --to 2026-12-06 --nights 7 \
                      --airport FRA --filter All-Inclusive --filter Direktflug --filter Doppelzimmer --max-price 2100
./lidl_api.py compare --id PA-1423030 --from 2026-11-22 --to 2026-12-06 --nights 7 --filter All-Inclusive   # cheapest per departure airport
./lidl_api.py search  --id PA-1423030 --from 2026-11-22 --to 2026-12-06 --nights 7 --airport BER            # offers for one product
./lidl_api.py calendar --id PA-1423030 --from 2026-11-22 --to 2027-03-31 --nights 7 --airport BER
./lidl_api.py flex     --id PA-1423030 --from 2026-11-16 --to 2026-12-06 --min-nights 5 --max-nights 12 --filter All-Inclusive
./lidl_api.py product  --id PA-1423030
./lidl_api.py airports --id PA-1423030 --from 2026-11-22 --to 2026-12-06 --nights 7

--airport is repeatable and optional: omitting it queries all served airports, and flex/compare restrict their per-airport scan to the codes given (fixed 2026-10-28; previously flex ignored --airport).

Reference case (Niklas’ Hurghada trip, snapshot 2026-10-28)

  • PA-1423030 = Sentido Naga Bay Resort & Spa, Sharm el Naga south of Hurghada, 5★, ~766 HolidayCheck reviews; operators ITS / DERTOUR / Travelix (same inventory as DERTOUR — compare there too).
  • Baseline 22.11.–06.12.2026, 7 nights, 2 adults, Doppelzimmer Deluxe Meerblick, all-inclusive, direct flight: FRA ~1.780 € total, BER ~1.446 €, BSL ~1.402 €, MUC/ZRH ~1.560 €.
  • Flexible window 16.11.–06.12.2026 (arrive by 06.12.), 7 nights / AI / direct / Doppelzimmer, cheapest per airport: BSL 1.402 € (27.11.–04.12.), BER 1.446 € (26.11.–03.12.), GVA 1.450 €, LEJ 1.492 €, MUC/ZRH 1.560 €, HAM 1.590 €, STR 1.604 €, FRA 1.780 €. Absolute cheapest of any duration: 5 nights BER 1.178 € (28.11.–03.12.) at ~236 €/night — worse per night than the 7-night BSL deal (~200 €/night).
  • Cheapest levers, in order: departure airport (up to ~380 €), date shift, dropping Direktflug (~320 € cheaper from FRA but verify stopover in UI).

Niklas’ Hurghada constraint set (fixed 2026-10-28)

Product PA-1423030 (Sentido Naga Bay Resort & Spa), 2 adults, min 7 nights, return to Germany latest 06.12.2026, departure airports limited to NUE / HAJ / FRA (Erfurt not offered — departureAirports:["ERF"] → totalResults: 0; LEJ Leipzig/Halle is the closest served airport to Erfurt, ~130 km). Canonical scan:

./lidl_api.py flex --id PA-1423030 --from 2026-11-16 --to 2026-12-06 --min-nights 7 --max-nights 12 \
  --airport NUE --airport HAJ --airport FRA --filter All-Inclusive --filter Direktflug --filter Doppelzimmer

Best direct-flight 7-night prices (snapshot 2026-10-28, re-runs the same day gave FRA 1.478–1.780 € — prices move by hundreds of euros within hours, so timestamp every snapshot and re-scan before booking): NUE 1.602 € (28.11.–05.12.), FRA 1.780 € (27.11.–04.12.), HAJ 1.810 € (27.11.–04.12.). Prices rise steeply earlier in the window (NUE 16.11. = 2.680 €); the sweet spot is the last week of November. Per-night is better with longer stays while still returning by 06.12.: LEJ 10N 1.922 € (192 €/N), NUE 10N 2.072 € (207 €/N), NUE 9N 1.896 € (211 €/N). Dropping Direktflug is ~190 € cheaper (NUE/HAJ 7N 1.410 €) but the flight then has a stopover — confirm in the UI before booking. --links prints the booking deep link for each best offer (.../product/PA-1423030/details?...&dep=<airport>&durMin=N&durMax=N&oId=<url-encoded offerId>), which opens the exact offer in the web UI.

Departure airports Lidl-Reisen actually serves

35 for PA-1423030 (Hurghada, 4–12 nights, verified 2026-10-28): AMS, BER, BRE, BRU, BSL, BTS, BUD, CGN, CLJ, CPH, CRL, DRS, DTM, DUS, EIN, FMO, FRA, GRZ, GVA, HAJ, HAM, KTW, LEJ, LNZ, LUX, MUC, NUE, PRG, RTM, SOF, STR, SZG, VIE, WRO, ZRH. The list is per product and per date window — always fetch it with availableAirportSearchResults instead of guessing. Kassel (KSN) is not offered (verified 2026-10-28): departureAirports:["KSN"] → totalResults: 0, and KSN never appears in any airport list. destinationSuggestResults(term:"Kassel") only returns Kassel as a destination (124 docs) and as category: ARRIVAL_AIRPORT (7 docs) — those are self-drive/bus hotel stays with departureAirport: null or TBA, never flights. For someone near Kassel the practical airports are FRA (~80 km), HAJ (~130 km), NUE (~150 km), DUS/STR (~180–200 km).

  • Same-area alternatives: Viva Blue Resort & Diving Sharm El Naga (4★) ~1.374 €, Siva Golden Bay Makadi ~1.236 €, JAZ Soma Beach ~1.702 €.
  • All snapshots above are 2026-10-28; re-run compare/find before deciding.

Source: Wiki.js