Booking.com API — Getting Started — FetchLayer Docs
Documentation menu
FetchLayer FetchLayer API

Getting Started

Booking.com properties, priced room options and guest reviews as structured JSON. No Booking.com account — just your FetchLayer key.

1. Get your API key

Create an account and generate a FetchLayer API key from your dashboard. The same key works on every platform.

Create free account →

2. Search a destination

destination is the only required field. Every property comes back with its star rating, review score, badges, coordinates, photos — and a price for every bookable room, not just the cheapest.

bash
curl -X POST https://api.fetchlayer.dev/booking/search-hotels \
  -H "Authorization: Bearer ss-your-key" \
  -H "Content-Type: application/json" \
  -d '{"destination":"Lisbon, Portugal","checkIn":"2026-10-15","checkOut":"2026-10-18"}'

That returned 200 in 3.61–3.94s across three runs on 17 September 2026, with 21 properties out of a resultsTotal of 2,403 — every one priced. Each property's rooms look like this:

json
{
  "price": { "amount": 642.75, "currency": "EUR" },
  "roomOptions": [
    {
      "roomId": "1425667101",
      "occupancy": 2,
      "price":         { "amount": 642.75, "currency": "EUR" },
      "originalPrice": { "amount": 642.75, "currency": "EUR" },
      "freeCancellationUntil": null,
      "mealPlan": null,
      "hasCrib": false
    }
  ]
}

Leave the dates off and it still prices — and tells you for when

Without checkIn/checkOut the search picks a default stay window and reports it straight back on the response's own checkIn and checkOut, plus a note: “No check-in/check-out was given, so this search used a default stay this service chose (2026-10-17 to 2026-10-18). Pass checkIn and checkOut to price a specific stay.” So a price is never silently for dates you did not ask about. Read those two fields before comparing prices across requests made on different days.

Read hasMore, never divide resultsTotal

resultsTotal is how many properties Booking.com says match the destination, not a promise that all of them can be walked. One Lisbon query returned 26 with hasMore: true and a cursor; the same query later returned 21 with hasMore: false, and asking for pages: 2 on it still reported pagesFetched: 1 — one credit, not two. Page with hasMore and nextCursor.

3. Read one property in full

Pass a booking.com URL or the compact "countryCode/pageName" id every search row carries. You get the formatted address, the full photo gallery, amenities, languages spoken, and every room type with its size in square metres, bed configuration, occupancy and its own photos.

bash
curl -X POST https://api.fetchlayer.dev/booking/hotel-detail \
  -H "Authorization: Bearer ss-your-key" \
  -H "Content-Type: application/json" \
  -d '{"hotel":"fr/du-printemps-paris"}'

Seven room types and 51 photos on that property, in 3.62–3.93s.

This route does not price a stay — and says so when you pass dates

checkIn and checkOut are accepted here and answered with a note rather than a price: “This route does not return a live price for this stay. Use search-hotels with the same destination and dates to get this property's priced room options.” That is the documented answer, not a failure — and it names the route that does price. /search-hotels prices every bookable room option, which is more than a detail read could give you anyway.

The category scores are here; the single headline number is not

On this route reviewScore comes back {average: null, count: null, label: null} while reviewScoreBreakdown carries all seven categories in full — Staff 8.94, Facilities 7.82, Cleanliness 8.51, Comfort 8.57, Value for money 8.09, Location 9.44, Free Wifi 8.79 on the property above. For the headline score and review count, read a /search-hotels row ({average: 8.1, count: 3854, label: "Very Good"}) or /hotel-reviews, which reports totalReviewCount and the same seven categories. Measured on two unrelated properties.

A bad id is a 404 with a message

Sending {"hotel":"zz/no-such-hotel-xyz"} returns 404 and {"error":"no property was found for \"zz/no-such-hotel-xyz\""}. Not a 503 — there is nothing to retry, the id names nothing.

4. Read the reviews — and know how many you got

The route worth coming here for. Every response reports the property's full review count alongside how many came back.

bash
curl -X POST https://api.fetchlayer.dev/booking/hotel-reviews \
  -H "Authorization: Bearer ss-your-key" \
  -H "Content-Type: application/json" \
  -d '{"hotel":"fr/du-printemps-paris","limit":10}'

The response says whether it is partial — no second call needed

That request returned totalReviewCount: 3854, reviewCount: 10, hasMore: true, and in words: “These are 10 of the 3854 reviews the source reports for this property.” plus “More reviews are available. Continue with nextCursor.” An average over ten of 3,854 and an average over all of them are different numbers; this is how you know which one you have computed.

Liked and disliked arrive as separate fields

positiveText and negativeText are kept apart, in the language the guest wrote them, with a language code beside them — so counting complaints is a filter, not sentiment analysis. Each review also carries the stay it refers to: stayCheckIn, stayCheckOut, nights and the roomType the guest actually slept in, plus their public first name, country and travelerType (“Couple”, “Family”).

A scored review with no text is ordinary

Of the ten measured, one carried a score of 9 with title, positiveText and negativeText all null. Either text field can be null on its own too. Three languages appeared in those ten — Italian, Turkish and German — so branch on language before running anything over the text.

5. Sort a pile of pasted links first

Free, fetches nothing, and hands you the compact id and a canonical URL with the tracking and date parameters stripped.

bash
curl -X POST https://api.fetchlayer.dev/booking/resolve-url \
  -H "Authorization: Bearer ss-your-key" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://www.booking.com/hotel/fr/du-printemps-paris.html"}'

That returns {"kind":"hotel","canonicalUrl":"https://www.booking.com/hotel/fr/du-printemps-paris.html","hotelId":"fr/du-printemps-paris","pagesFetched":0} in 0.065–0.13s. A results link answers kind: "search"; a link it does not recognise answers kind: "unknown" with a note rather than an error to catch. Run it over a list before spending a single page fetch.

Speed and billing

One credit per page fetched, reported on every response as pagesFetched. Measured against production on 17 September 2026, three runs each: a one-page Lisbon search answered in 3.61–3.94s with 21 properties, Paris in 3.60–3.82s, a property read in 3.62–3.93s, and ten reviews in 4.28–4.49s. Sixty reviews across three pages took 6.14s and reported pagesFetched: 3. A review walk stops early when limit is reached, so pagesFetched is what was actually read rather than what was asked for. /resolve-url is free and reports pagesFetched: 0.

Errors

A 400 names what is wrong — a missing required field, or an unrecognised one, because the body is a strict object: {"error":"Unrecognized key: \"sortBy\""}. A 401 means the key is missing or invalid. A 404 on /hotel-detail or /hotel-reviews means the id names no property, and says so in the message.

Next steps