Booking.com Search API — Hotels and Prices by City
Documentation menu
FetchLayer FetchLayer API

Search properties by destination

Search Booking.com for a destination and a stay. `destination` is the only required field — a place name like "Paris" or "Lisbon, Portugal" — and every property comes back priced, with **every bookable room option priced separately** beside it, plus its star rating, review score, badges, coordinates and photos. Money is `{amount, currency}` — a number and a currency code, never a display string to unpick. A search row carries `price` for the cheapest bookable stay **plus `roomOptions[]`, each priced on its own**, with `originalPrice`, `occupancy`, `freeCancellationUntil`, `mealPlan` and `hasCrib`. So you can compare rooms inside one property as easily as properties against each other. Every property returned was priced across the runs measured — 21 of 21 in Paris, 26 of 26 in Lisbon. **Dates are optional, and this route always prices.** Give `checkIn`/`checkOut` and every property is priced for that stay. Omit them and the search still prices — for a default stay window it chooses — and reports that window straight back on the response's `checkIn`/`checkOut` *and* in `notes[]`, 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. **`resultsTotal` is a match count, not a walkable promise.** It is how many properties Booking.com says match the destination; it does not mean all of them can be paged through. Read `hasMore` and `nextCursor` to decide whether there is another page. One measured Lisbon query returned 26 with `hasMore: true` and a cursor, and the same query later returned 21 with `hasMore: false`. Never divide `resultsTotal` by a page size to plan a walk. Billing: 1 credit per page fetched, reported as `pagesFetched`. A page is about 25 properties — Booking.com's own page size, not ours — and costs the same whether it comes back full or half empty; `hotelCount` is what actually arrived. Measured on 17 September 2026, three runs each: Lisbon with dates returned 200 in 3.61-3.94s with 21 properties of a `resultsTotal` of 2403, and Paris returned 200 in 3.60-3.82s with 21 of 3000. All priced.

POST /search-hotels Properties
https://api.fetchlayer.dev/booking/search-hotels
OpenAPI JSON

Headers

Authorization string required

Bearer token — your FetchLayer API key.

Parameters

destination string required

A place name, e.g. "Paris" or "Lisbon, Portugal". The only required field.

checkIn string optional

Stay start date, YYYY-MM-DD. Optional — without it the search prices a default stay window and reports that window back on the response's `checkIn`. Must be given together with `checkOut`.

checkOut string optional

Stay end date, YYYY-MM-DD. Required if `checkIn` is given, and vice versa.

adults integer optional

Adult guests, up to 30. (default: 2)

children integer optional

Children, up to 30. (default: 0)

rooms integer optional

Rooms to book, up to 30. (default: 1)

pages integer optional

How many pages of results to collect in one request, up to 20. Each page fetched is 1 credit. Pass -1 for the maximum. A walk stops when there are no pages left, and `pagesFetched` reports what was actually read. (default: 1)

cursor string optional

Continue from a previous response's `nextCursor`.

format string optional

Envelope format: json or markdown. (default: json)

Response Fields

search-hotels

Search properties by destination

transport string

The source. "booking".

notes string[]

Caveats and completeness statements you can act on, in plain words. On /hotel-reviews this is where the "10 of the 3854" line lives; on /search-hotels it is where a default stay window is declared when you passed no dates; on /hotel-detail it is where the "this route does not price a stay" answer lives.

pagesFetched number

Pages read, and what the request is billed at: 1 credit per page. A page costs the same whether it comes back full or half empty. /resolve-url reports 0 and is free.

destination string

The destination that was searched, echoed back.

hotelCount number

How many properties came back in this response.

resultsTotal number

**How many properties Booking.com reports for the destination — a match count, not a page plan.** 2403 for Lisbon and 3000 for Paris on the runs measured. It is not a promise that all of them are walkable: use `hasMore`.

hasMore boolean

**The field that decides whether to page.** Whether there are further results beyond this response. The same Lisbon query returned `true` on one run and `false` on another; read it rather than computing from `resultsTotal`.

nextCursor string

Pass back as `cursor` to continue. Null when there is nothing more.

checkIn string

**The stay window the prices are actually for.** Your date when you gave one; the default window the search chose when you did not — so a price is never silently for dates you did not ask about.

checkOut string

The other end of that window, on the same terms.

adults number

The adult count the prices are for, echoed back.

children number

As `adults`.

roomsRequested number

How many rooms the prices cover.

hotels[].id string

The compact `"countryCode/pageName"` id, e.g. `"pt/chiado-meets-bairro-apartment"`. Hand it straight to /hotel-detail or /hotel-reviews as `hotel`.

hotels[].url string

The property's booking.com address.

hotels[].name string

The property name as published.

hotels[].address string

The street line as published — the fuller, formatted address is on /hotel-detail.

hotels[].city string

The city the property is in, as published.

hotels[].countryCode string

Two-letter country code — the same one that opens the compact id.

hotels[].coordinates object

`{latitude, longitude}`.

hotels[].starRating object

`{value, scheme}`. `scheme` is what the stars actually mean — measured `"STARS"` on a hotel and `"TILES"` on an apartment, which are different rating systems and not comparable without it.

hotels[].reviewScore object

`{average, count, label}`, e.g. `{average: 8.1, count: 3854, label: "Very Good"}`. **This is where the headline score is populated** — it comes back null on /hotel-detail, which carries the per-category breakdown instead.

hotels[].badges string[]

Booking.com's own labels on the row, e.g. `["Limited-time Deal"]`. Empty where there are none.

hotels[].photos string[]

Photo URLs for the row. The full gallery is on /hotel-detail — 51 photos on the property measured there.

hotels[].price object

`{amount, currency}` for the cheapest bookable stay at this property over the dates searched.

hotels[].roomOptions object[]

Every bookable room offer, each priced on its own. This is what makes the route a price comparison rather than a listing.

hotels[].roomOptions[].roomId string

Booking.com's own id for that room offer.

hotels[].roomOptions[].occupancy number

How many guests the offer is for. Can be 0 where the source published no occupancy for the offer.

hotels[].roomOptions[].price object

`{amount, currency}` for this room over the stay searched.

hotels[].roomOptions[].originalPrice object

`{amount, currency}` before any discount. Equal to `price` when nothing is discounted, so a deal is `originalPrice.amount > price.amount` rather than a badge to parse.

hotels[].roomOptions[].freeCancellationUntil string

The date free cancellation runs to, where the offer carries one. Null otherwise.

hotels[].roomOptions[].mealPlan string

What is included in the rate, in Booking.com's own wording. Null where the offer is room-only or published none.

hotels[].roomOptions[].hasCrib boolean

Whether a crib is part of the offer.

Request

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"}'

Response

{
  "transport": "booking",
  "notes": [
    "These are 26 of the 2403 properties the source reports for this search. Continue with nextCursor."
  ],
  "pagesFetched": 1,
  "destination": "Lisbon, Portugal",
  "hotelCount": 26,
  "resultsTotal": 2403,
  "hasMore": true,
  "nextCursor": "26",
  "checkIn": "2026-10-15",
  "checkOut": "2026-10-18",
  "adults": 2,
  "children": 0,
  "roomsRequested": 1,
  "hotels": [
    {
      "id": "pt/chiado-meets-bairro-apartment",
      "url": "https://www.booking.com/hotel/pt/chiado-meets-bairro-apartment.html",
      "name": "Chiado Meets Bairro Apartment",
      "address": "Rua da Rosa 60 1 Floor",
      "city": "Lisboa",
      "countryCode": "pt",
      "coordinates": {
        "latitude": 38.7120426,
        "longitude": -9.1452802
      },
      "starRating": {
        "value": 3,
        "scheme": "TILES"
      },
      "reviewScore": {
        "average": 7.9,
        "count": 17,
        "label": "Good"
      },
      "badges": [
        "Limited-time Deal"
      ],
      "photos": [
        "https://cf.bstatic.com/xdata/images/hotel/square600/689982408.webp?k=000258df15bd9b9f89f40f679e6e2b3c263b555db2c0533a18bac6ac1362e50e&o="
      ],
      "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
        }
      ]
    }
  ]
}