{
  "openapi": "3.0.1",
  "info": {
    "title": "Booking.com Hotel Room Price, Availability & Min-Stay Scraper",
    "description": "Per-room, per-date prices and availability from Booking.com at any stay length, so minimum-stay properties don't read as sold out - with Booking's real per-date minimum stay. Search by hotel URL, or by destination to compare an area: up to 365 days ahead, any party size, full property details.",
    "version": "0.1",
    "x-build-id": "eYcjgmoTsLXlqzlEt"
  },
  "servers": [
    {
      "url": "https://api.apify.com/v2"
    }
  ],
  "paths": {
    "/acts/aurith_labs~booking-availability/run-sync-get-dataset-items": {
      "post": {
        "operationId": "run-sync-get-dataset-items-aurith_labs-booking-availability",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/acts/aurith_labs~booking-availability/runs": {
      "post": {
        "operationId": "runs-sync-aurith_labs-booking-availability",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor and returns information about the initiated run in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/runsResponseSchema"
                }
              }
            }
          }
        }
      }
    },
    "/acts/aurith_labs~booking-availability/run-sync": {
      "post": {
        "operationId": "run-sync-aurith_labs-booking-availability",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "inputSchema": {
        "type": "object",
        "properties": {
          "hotelUrls": {
            "title": "Hotel URLs",
            "type": "array",
            "description": "Booking.com hotel page URLs, e.g. https://www.booking.com/hotel/za/inyati-game-lodge-sabi-sand-game-reserve.html. Alternative to 'Destination (search text)' and 'Search URL' below: exactly one of the three must be supplied. This is the field used by the actor's own default input, below — leave 'Destination (search text)' and 'Search URL' empty when using this one.",
            "items": {
              "type": "object",
              "required": [
                "url"
              ],
              "properties": {
                "url": {
                  "type": "string",
                  "title": "URL of a web page",
                  "format": "uri"
                }
              }
            }
          },
          "searchText": {
            "title": "Destination (search text)",
            "type": "string",
            "description": "Free-text destination to search, e.g. \"Cape Town\" or \"Sabi Sand\" — resolved the same way Booking.com's own search box does, then every matching property (up to 'Max properties' below) is swept the same way a manually-listed hotel URL would be. Alternative to 'Hotel URLs' and 'Search URL': exactly one of the three must be supplied."
          },
          "searchUrl": {
            "title": "Search URL",
            "type": "string",
            "description": "A Booking.com search-results URL copied straight from your browser (must include dest_id and dest_type in its query string) — used instead of resolving 'Destination (search text)' via autocomplete, e.g. if you already have a specific Booking.com search you want reproduced exactly. Alternative to 'Hotel URLs' and 'Destination (search text)': exactly one of the three must be supplied."
          },
          "maxProperties": {
            "title": "Max properties",
            "minimum": 1,
            "maximum": 1000,
            "type": "integer",
            "description": "Destination mode only ('Destination (search text)' / 'Search URL'). Upper bound on how many distinct properties to discover and then sweep. This is a ceiling approached, not a count guaranteed: Booking's own result pagination is not perfectly stable (roughly 19% duplicate results observed across a full 1,000-property pagination sweep in testing), so a run may resolve fewer than this many distinct hotels even when more exist for the destination. Cost multiplies with 'Days ahead' and 'Proxy countries' — maxProperties: 50 x daysAhead: 14 is 700 hotel-date probes from this one field alone, before any proxy-country multiplier, so raise it deliberately, not by default.",
            "default": 50
          },
          "minReviewScore": {
            "title": "Minimum review score",
            "minimum": 0,
            "maximum": 10,
            "type": "number",
            "description": "Destination mode only. Only discover properties with a review score of at least this, on Booking's own 0-10 scale (e.g. 8.0 for an \"8+\"-rated property). Applied as a coarse server-side prefilter first (Booking's own filter only offers 6/7/8/9 floor buckets, so this is rounded down to the nearest one to avoid excluding a qualifying property), then re-checked exactly against each property's real score at no extra fetch cost."
          },
          "maxReviewScore": {
            "title": "Maximum review score",
            "minimum": 0,
            "maximum": 10,
            "type": "number",
            "description": "Destination mode only. Only discover properties with a review score of at most this, on Booking's own 0-10 scale. Booking.com has no server-side filter for an upper bound, so this is applied entirely client-side against each property's real score, already present in the search response at no extra fetch cost — but unlike 'Minimum review score', it cannot reduce how many results are actually fetched from Booking."
          },
          "propertyType": {
            "title": "Property type",
            "enum": [
              "Hotels",
              "Apartments",
              "Resorts",
              "Villas",
              "Vacation Homes",
              "Bed and Breakfasts",
              "Chalets",
              "Guesthouses",
              "Hostels",
              "Lodges",
              "Country Houses",
              "Homestays",
              "Campgrounds",
              "Boats",
              "Capsule Hotels",
              "Luxury tents"
            ],
            "type": "string",
            "description": "Destination mode only. Restrict discovery to one Booking.com property category, applied server-side."
          },
          "startDate": {
            "title": "Start date",
            "type": "string",
            "description": "First check-in date to probe, as YYYY-MM-DD. Defaults to today when left empty."
          },
          "daysAhead": {
            "title": "Days ahead",
            "minimum": 1,
            "maximum": 365,
            "type": "integer",
            "description": "How many consecutive check-in dates to probe, starting at the start date. Up to 365 (a full year out). A large value multiplied by several hotels can be thousands of probes — at the default 'Run timeout (seconds)' this may stop before every date is attempted, and every unattempted probe still becomes an explicit error record rather than being silently dropped. Raise 'Run timeout (seconds)' to match, or turn on 'Tiered cadence' to probe far-out dates less often and cut both time and cost.",
            "default": 7
          },
          "tieredCadence": {
            "title": "Tiered cadence",
            "type": "boolean",
            "description": "Probe every date near check-in, but thin out far-future dates: daily for the next 2 weeks, every 2nd day for the next ~6 weeks, every 3rd day beyond that. Far-out prices move the least, so this cuts probe count (and cost) substantially on a long 'Days ahead' window without losing near-term granularity. Off by default, which probes every date.",
            "default": false
          },
          "nights": {
            "title": "Nights per stay",
            "minimum": 1,
            "maximum": 7,
            "type": "integer",
            "description": "Stay length for each probe. Minimum-stay properties (safari lodges, villas, resorts) return no rooms at 1 night even when they are bookable at 2 or more. Set this to the stay length the property actually sells.",
            "default": 1
          },
          "useMinimumStay": {
            "title": "Probe each date at its own minimum stay",
            "type": "boolean",
            "description": "Ask every date for the stay length that date actually sells, instead of one fixed length for the whole run. We read each date's minimum stay from Booking's own availability calendar first, then price that date at that length. Turn this on when you do not know a property's minimum stay, or when it varies across the season - a min-stay-3 date probed at 1 night comes back with nothing bookable, which looks identical to a sell-out. 'Nights per stay' above becomes the fallback, used only on dates where the calendar did not tell us a minimum. Costs nothing extra: the same one fetch per hotel-date, just asking a different question. Each record's 'nights' field carries the length that record's price actually covers, so check it before comparing prices across dates.",
            "default": false
          },
          "mode": {
            "title": "Output mode",
            "enum": [
              "offers",
              "calendar",
              "calendar_rooms"
            ],
            "type": "string",
            "description": "What one dataset row represents. Billing is per offer or per answered date, never per row — so the same data costs the same whichever shape you pick.\n\n• 'One row per room rate' (default) — the full room-level result: every rate plan a property is selling for every date, with prices, taxes, cancellation terms and amenities. One page fetch per hotel-date. Rows per hotel-date vary by property: a single-room lodge yields one or two, a large hotel ~34.\n\n• 'One row per date (availability calendar)' — Booking's own per-date availability and minimum stay across the whole window, plus one property record per hotel. Costs ONE page fetch per hotel plus one availability call per 61 days, and bills one result per date. On a large hotel that is roughly 34x fewer billed results than the default; on a single-room lodge, about the same. Carries no room detail and no real prices.\n\n• 'One row per date, rooms nested' — the same room-level data as the default, grouped by date. Same fetches and the SAME bill as the default: a nested row bills one result per rate plan inside it. Only the JSON shape differs.\n\n'Days ahead' multiplies results in every mode.",
            "default": "offers"
          },
          "currency": {
            "title": "Currency",
            "enum": [
              "USD",
              "EUR",
              "GBP",
              "ZAR",
              "AUD",
              "CAD",
              "CHF",
              "JPY",
              "AED",
              "NZD",
              "INR",
              "SGD",
              "HKD",
              "IDR",
              "COP",
              "THB",
              "MYR",
              "PHP",
              "CNY",
              "KRW",
              "MXN",
              "BRL",
              "SEK",
              "NOK",
              "DKK",
              "PLN",
              "TRY",
              "ILS",
              "SAR",
              "QAR",
              "EGP",
              "KES"
            ],
            "type": "string",
            "description": "Currency for returned prices. This dropdown lists the currencies subscribers have actually asked for; the underlying validation accepts any non-empty Booking.com currency code, including ones not in this list — type one in via the API if you need it.",
            "default": "USD"
          },
          "adults": {
            "title": "Adults",
            "minimum": 1,
            "maximum": 30,
            "type": "integer",
            "description": "Number of adults per room in the availability search.",
            "default": 2
          },
          "rooms": {
            "title": "Rooms",
            "minimum": 1,
            "maximum": 30,
            "type": "integer",
            "description": "Number of rooms requested in the availability search.",
            "default": 1
          },
          "children": {
            "title": "Children",
            "minimum": 0,
            "maximum": 10,
            "type": "integer",
            "description": "Number of children per room in the search. Leave at 0 for an adults-only search. If you set this above 0, you MUST also fill in 'Children ages' below with exactly that many ages — Booking.com silently ignores a child with no age attached (verified against a live property: a search sent as 1 child with no age comes back from Booking's own response as 0 children), so an age-less child count would quietly search as if it were 0.",
            "default": 0
          },
          "childrenAges": {
            "title": "Children ages",
            "type": "array",
            "description": "One age (0-17) per child counted in 'Children' above, in the same order, e.g. [\"5\", \"10\"] for two children aged 5 and 10. Required whenever 'Children' is greater than 0, and must contain exactly that many ages — Booking.com drops any child search parameter that has no matching age.",
            "default": [],
            "items": {
              "type": "string"
            }
          },
          "includeSoldOut": {
            "title": "Include sold-out dates",
            "type": "boolean",
            "description": "This does not change how many records you get: a record for every date is always produced, sold out or not, because absence and emptiness are different facts and we never destroy that distinction at source. This flag is stamped onto every record as 'include_sold_out' so you can filter the sold-out shells out yourself downstream; turning it off does not shrink the dataset.",
            "default": true
          },
          "delayBetweenRequests": {
            "title": "Delay between requests (seconds)",
            "minimum": 0,
            "maximum": 30,
            "type": "number",
            "description": "Pause between hotel-date probes, for politeness and to reduce blocking risk. We fetch over plain HTTP rather than loading a browser page per hotel-date like the incumbent scraper, so a given delay costs us far less than it costs them — we can afford to be a slower, more considerate client without taking their cost penalty for it. The default (1s) still sweeps faster than the incumbent's own 3-4s anti-blocking pause. Set to 0 to disable.",
            "default": 1
          },
          "proxyConfiguration": {
            "title": "Proxy configuration",
            "type": "object",
            "description": "Proxy used for all requests. Datacenter is sufficient in testing; escalate to residential only if you observe blocking. If 'Proxy countries' below is set, each of its countries overrides the country here (if any) for that country's own requests.",
            "default": {
              "useApifyProxy": true
            }
          },
          "proxyCountries": {
            "title": "Proxy countries",
            "type": "array",
            "description": "Booking.com prices the same room differently depending on the visitor's country. Add one or more two-letter country codes (e.g. \"ID\" for Indonesia) to run the entire sweep once per country and get one set of country-specific prices per hotel-date, each record tagged with its 'proxy_country'. Leave empty (default) to run once with whatever the proxy configuration above gives you — on Apify Proxy that is typically US egress, and a live measurement found US-egress prices come back tax-exclusive with 'taxes_included'/'price_includes_taxes' both empty, roughly 15% below every other country tested (South Africa's VAT). If you want the price a guest in your source market actually pays, set this to that market, e.g. [\"ZA\"]. Each country requires its own browser session mint, so this multiplies both run time and platform cost by the number of countries listed — see the actor's SPEC.md and README for the measured cost model, including the datacenter-vs-residential cost difference for reaching a country your proxy plan doesn't already cover.",
            "default": [],
            "items": {
              "type": "string"
            }
          },
          "timeoutSecs": {
            "title": "Run timeout (seconds)",
            "minimum": 120,
            "type": "integer",
            "description": "Stop the run after this many seconds so a hung fetch cannot bill unbounded. The minimum is set above the one-time browser mint's own worst case (~75s), so a run always has time left to attempt at least one hotel-date probe before stopping. A run that hits this timeout stops cleanly (not a failure) and writes an explicit error record for every probe it never got to — raise this if 'Days ahead', the hotel count, or 'Proxy countries' push the sweep past what the default 900s can finish; each proxy country multiplies both the mint cost and the total probe count.",
            "default": 900
          }
        }
      },
      "runsResponseSchema": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "actId": {
                "type": "string"
              },
              "userId": {
                "type": "string"
              },
              "startedAt": {
                "type": "string",
                "format": "date-time",
                "example": "2025-01-08T00:00:00.000Z"
              },
              "finishedAt": {
                "type": "string",
                "format": "date-time",
                "example": "2025-01-08T00:00:00.000Z"
              },
              "status": {
                "type": "string",
                "example": "READY"
              },
              "meta": {
                "type": "object",
                "properties": {
                  "origin": {
                    "type": "string",
                    "example": "API"
                  },
                  "userAgent": {
                    "type": "string"
                  }
                }
              },
              "stats": {
                "type": "object",
                "properties": {
                  "inputBodyLen": {
                    "type": "integer",
                    "example": 2000
                  },
                  "rebootCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "restartCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "resurrectCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "computeUnits": {
                    "type": "integer",
                    "example": 0
                  }
                }
              },
              "options": {
                "type": "object",
                "properties": {
                  "build": {
                    "type": "string",
                    "example": "latest"
                  },
                  "timeoutSecs": {
                    "type": "integer",
                    "example": 300
                  },
                  "memoryMbytes": {
                    "type": "integer",
                    "example": 1024
                  },
                  "diskMbytes": {
                    "type": "integer",
                    "example": 2048
                  }
                }
              },
              "buildId": {
                "type": "string"
              },
              "defaultKeyValueStoreId": {
                "type": "string"
              },
              "defaultDatasetId": {
                "type": "string"
              },
              "defaultRequestQueueId": {
                "type": "string"
              },
              "buildNumber": {
                "type": "string",
                "example": "1.0.0"
              },
              "containerUrl": {
                "type": "string"
              },
              "usage": {
                "type": "object",
                "properties": {
                  "ACTOR_COMPUTE_UNITS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_WRITES": {
                    "type": "integer",
                    "example": 1
                  },
                  "KEY_VALUE_STORE_LISTS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_INTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_EXTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_SERPS": {
                    "type": "integer",
                    "example": 0
                  }
                }
              },
              "usageTotalUsd": {
                "type": "number",
                "example": 0.00005
              },
              "usageUsd": {
                "type": "object",
                "properties": {
                  "ACTOR_COMPUTE_UNITS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_WRITES": {
                    "type": "number",
                    "example": 0.00005
                  },
                  "KEY_VALUE_STORE_LISTS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_INTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_EXTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_SERPS": {
                    "type": "integer",
                    "example": 0
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}