{
  "openapi": "3.0.1",
  "info": {
    "title": "Booking.com Price Scraper & Room Rate Tracker",
    "description": "Track Booking.com room prices by date. Compare competitor hotels, monitor daily price changes and receive a digest. Get selected and cheapest rates, reported taxes, cancellation terms and availability. One-night quotes for revenue managers and developers. Export JSON, CSV or Excel.",
    "version": "0.0",
    "x-build-id": "IN9G7juXTYpIWdVxk"
  },
  "servers": [
    {
      "url": "https://api.apify.com/v2"
    }
  ],
  "paths": {
    "/acts/noraview~Booking-price-scraper/run-sync-get-dataset-items": {
      "post": {
        "operationId": "run-sync-get-dataset-items-noraview-Booking-price-scraper",
        "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/noraview~Booking-price-scraper/runs": {
      "post": {
        "operationId": "runs-sync-noraview-Booking-price-scraper",
        "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/noraview~Booking-price-scraper/run-sync": {
      "post": {
        "operationId": "run-sync-noraview-Booking-price-scraper",
        "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": {
          "searchText": {
            "title": "🔤 Search by text (auto-build URL)",
            "type": "string",
            "description": "e.g. 'riad with pool in fes'. Used only if searchUrl and hotelUrls are empty. Builds the Booking search URL automatically."
          },
          "searchUrl": {
            "title": "Search URL (paste from browser)",
            "type": "string",
            "description": "Paste a Booking.com search results URL with all your filters applied (stars, meal plan, distance, etc). Must be a booking.com link — Google Travel, Traveloka, and other sites are not supported."
          },
          "hotelUrls": {
            "title": "🏨 Target Hotels (direct mode)",
            "type": "array",
            "description": "Add Booking.com hotel URLs here to scrape specific hotels directly. If provided, destination mode is skipped.",
            "items": {
              "type": "object",
              "required": [
                "url"
              ],
              "properties": {
                "url": {
                  "type": "string",
                  "title": "URL of a web page",
                  "format": "uri"
                }
              }
            }
          },
          "maxProperties": {
            "title": "📊 Max Properties to Discover",
            "minimum": 1,
            "maximum": 500,
            "type": "integer",
            "description": "Maximum number of hotels to scrape from search results.",
            "default": 50
          },
          "startDate": {
            "title": "📅 Start Date",
            "type": "string",
            "description": "Check-in date. Leave empty for today — today at each hotel, so a run after midnight in Perth or Sydney starts on their new day instead of asking for a night that has already passed. Leave it empty on a daily schedule; a fixed date slides into the past."
          },
          "daysAhead": {
            "title": "📆 Days to Scrape",
            "minimum": 1,
            "maximum": 365,
            "type": "integer",
            "description": "Number of days to scan forward. 7-14 is a good daily window; 30+ shows the season taking shape. Each day is one page per hotel, so this is also the setting that drives run time and cost.",
            "default": 3
          },
          "includeSoldOut": {
            "title": "🔴 Track Sold-Out Rooms",
            "type": "boolean",
            "description": "Detect which rooms are sold out. Essential for demand analysis and revenue management. A competitor's sold-out room tells you more than their price: it is the date filling up, and it usually happens before anyone reprices. With this on, a room that cannot be booked still gets a row, marked ⚫ Sold out, instead of vanishing from the comparison and quietly making the market look cheaper than it is. Cost: every row is a result you pay for, and on busy dates most rows can be sold-out ones (140 of 233 on a live four-motel run). Switching this off cuts those rows — and with them the demand read: a sold-out room simply disappears, and market_summary leaves sold_out_ratio and demand_pressure empty rather than guess.",
            "default": true
          },
          "trackPriceChanges": {
            "title": "📈 Track price changes between runs",
            "type": "boolean",
            "description": "Adds Move, Δ Rate, Δ %, Days at this price and Availability change to every row — what the price and the availability did since the last run. Costs no extra requests: the last price of each room-night is kept between runs and compared on the next one. Turn this off only if you want pure snapshots with no memory. With it on you stop finding out three days late that a competitor cut 12%, and you can tell an actively yielded rate from one nobody has touched in a fortnight.",
            "default": true
          },
          "myHotelUrl": {
            "title": "🏠 Your own hotel's Booking.com URL",
            "type": "string",
            "description": "Optional. Paste YOUR property's URL here, and keep it in the hotel list above as well. Every row of yours is then marked is_my_hotel: true — so you can put your own rates at the top of the screen, and tell an AI \"my hotel is the row where is_my_hotel is true\" instead of letting it guess from a name. Any form of the URL works: query strings, check-in dates and language suffixes (.fr.html) are ignored. Leave it empty if you are watching a market rather than pricing a property.\n\n⚠️ Whether or not you fill this in, put your hotel AND its competitors in the SAME run. The ranking compares properties inside one run, so scraping them one at a time leaves compset_rank, market_median_rate and every gap column empty — prices with nothing to compare them to."
          },
          "historyStoreName": {
            "title": "Price history store name — optional, leave empty",
            "type": "string",
            "description": "Leave this empty. It fills itself with 'booking-price-history' and you never have to think about it. Change it only when you run two separate monitoring jobs that must not mix their history — a different compset, a different currency, a different occupancy — and give each job its own name. Changing this name on an existing job starts its history over from zero.",
            "default": "booking-price-history"
          },
          "digestEmail": {
            "title": "📬 Email the daily digest to",
            "type": "string",
            "description": "One or more email addresses, separated by commas. Sent through Apify's own Send Email Actor, on the account that runs this Actor (a fraction of a cent per run). Some Apify plans cannot start another public Actor from inside a run — the log then says \"Digest email NOT sent\". The reliable way to get it by email on any plan: a Make, Zapier or n8n webhook in the field below, with an email step behind it."
          },
          "digestWebhookUrl": {
            "title": "📬 Post the daily digest to (webhook URL)",
            "type": "string",
            "description": "A Slack incoming-webhook URL or a Discord webhook URL gets the digest as a message. Any other URL (Make, Zapier, n8n, your own endpoint) receives JSON: {\"text\": …, \"digest\": {…}} with every figure in structured form. Separate from the error-alert webhook under Alerts."
          },
          "currency": {
            "title": "Currency",
            "enum": [
              "USD",
              "EUR",
              "GBP",
              "MAD",
              "AED",
              "SAR",
              "CAD",
              "AUD",
              "JPY",
              "BRL",
              "INR",
              "SGD",
              "HKD",
              "COP",
              "IDR",
              "CNY",
              "PLN",
              "KRW",
              "THB",
              "TWD",
              "CHF",
              "MXN",
              "ZAR"
            ],
            "type": "string",
            "description": "Output currency for all prices. The scraper handles all currency formats automatically (Indian Lakh, European thousands, etc).",
            "default": "USD"
          },
          "propertyType": {
            "title": "🏡 Property Type Filter",
            "enum": [
              "",
              "hotel",
              "apartment",
              "hostel",
              "villa",
              "guesthouse",
              "resort",
              "homestay",
              "campsite",
              "motel",
              "riad",
              "boutique"
            ],
            "type": "string",
            "description": "Filter by property type (only applies when using destinationId, not searchUrl)."
          },
          "minReviewScore": {
            "title": "⭐ Min Review Score",
            "minimum": 0,
            "maximum": 10,
            "type": "number",
            "description": "Minimum review score (0-10). Example: 8.5",
            "default": 0
          },
          "maxReviewScore": {
            "title": "⭐ Max Review Score",
            "minimum": 0,
            "maximum": 10,
            "type": "number",
            "description": "Maximum review score (0-10). Leave 0 for no limit.",
            "default": 0
          },
          "adults": {
            "title": "Adults",
            "minimum": 1,
            "maximum": 10,
            "type": "integer",
            "description": "Number of adults.",
            "default": 2
          },
          "children": {
            "title": "Children",
            "minimum": 0,
            "maximum": 10,
            "type": "integer",
            "description": "Number of children. Fill in their ages below too — Booking.com prices a family stay by each child's age.",
            "default": 0
          },
          "childrenAges": {
            "title": "Children's ages",
            "type": "array",
            "description": "One entry per child, age 0-17 at check-in (e.g. 4 and 9). Booking.com needs every child's age to price a family stay: infants are often free and teenagers usually count as adults. Sets the number of children when the two disagree. Leave empty for adults-only searches.",
            "items": {
              "type": "string"
            }
          },
          "rooms": {
            "title": "Rooms",
            "minimum": 1,
            "maximum": 5,
            "type": "integer",
            "description": "Number of rooms.",
            "default": 1
          },
          "proxyConfiguration": {
            "title": "🔒 Proxy configuration",
            "type": "object",
            "description": "Standard Apify proxy picker. Every request leaves from one IP, and Booking.com does block datacenter and shared IPs — the risk grows with the number of properties, dates and parallel hotels, so a large or frequent run wants RESIDENTIAL. A small daily run often works without any proxy at all, and that is cheaper; the sign to switch it on is rows coming back '⚠️ Not scraped — Booking.com served a block page'. The country chosen here is used only when 'Proxy Countries' below is empty; set that field to scrape the same hotel from several source markets in one run."
          },
          "useApifyProxy": {
            "title": "Use Apify Proxy (legacy, hidden)",
            "type": "boolean",
            "description": "Deprecated and hidden from the form — 'Proxy configuration' above replaces it and is read first. Kept so saved tasks and API callers that send this key keep working: when proxyConfiguration is absent from the input, this boolean still decides whether a RESIDENTIAL proxy is used.",
            "default": true
          },
          "proxyCountries": {
            "title": "Proxy Countries (multi-market)",
            "type": "array",
            "description": "OPTIONAL. Leave empty to use the single country from 'Proxy configuration' above. Fill it to scrape the same hotels once PER country and compare geo-pricing — each result is tagged with proxy_country. Accepts any 2-letter code your Apify Residential proxy offers (not a fixed list): FR, US, GB, DE, ES, IT, NL, BE, PL, CH, CN, HK, SG, JP, KR, AE, SA, MA, IN, ID, AU, CA, BR… A code your plan lacks falls back to a weaker proxy and says so in the log. Keep the same currency across countries for a clean cohort comparison.",
            "default": [
              "US"
            ],
            "items": {
              "type": "string"
            }
          },
          "concurrency": {
            "title": "⚡ Parallel Hotels (concurrency)",
            "minimum": 1,
            "maximum": 5,
            "type": "integer",
            "description": "How many hotels are scraped at the same time, per country. Each one runs its own browser on its own proxy IP, so this multiplies both speed and memory: 3 hotels in parallel is three browsers in the same container. Raise it for a faster run on a small compset. Lower it to 1 or 2 on a small memory allowance, or if rows come back '⚠️ Not scraped' mid-run: fewer browsers use less memory.",
            "default": 3
          },
          "delayBetweenRequests": {
            "title": "Delay Between Requests (seconds)",
            "minimum": 2,
            "maximum": 10,
            "type": "integer",
            "description": "Wait time between each date scrape (per hotel). Higher = safer but slower.",
            "default": 3
          },
          "includeLosAnalysis": {
            "title": "Include multi-room prices",
            "type": "boolean",
            "description": "Adds los_prices: what Booking.com charges for booking 1, 2, 3… of each room at once for the night, and has_los_discount when booking several is cheaper per room. These are Booking's quantity dropdown prices, not multi-night quotes. No extra page loads.",
            "default": false
          },
          "includeWholesaleRates": {
            "title": "Include Wholesale/Partner Rates (bbasic)",
            "type": "boolean",
            "description": "Capture partner/wholesale rates (Hotelbeds, Expedia, etc). Important for Asian markets. Tagged separately as is_wholesale, never mixed with retail rates. This is the rate a bed bank is reselling a competitor at — often well under their own published price, and what your guest actually compares you against on those channels.",
            "default": false
          },
          "tieredCadence": {
            "title": "Tiered Scrape Cadence (cost saver)",
            "type": "boolean",
            "description": "Scrape near dates daily, mid dates every 2 days, far dates every 3 days. Reduces cost ~50%. Best for your own monitoring. The logic is that a date 90 days out rarely moves overnight while next week does. One catch if you also track price changes: it makes the gap between captures uneven, so read the Days since last check column before calling a far-date move a daily one.",
            "default": false
          },
          "webhookUrl": {
            "title": "Alert Webhook URL (Optional)",
            "type": "string",
            "description": "Slack/Discord webhook for error alerts."
          }
        }
      },
      "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
                  },
                  "PROXY_UNBLOCKER_UNITS": {
                    "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
                  },
                  "PROXY_UNBLOCKER_UNITS": {
                    "type": "integer",
                    "example": 0
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}