{
  "openapi": "3.0.1",
  "info": {
    "title": "BEST Agoda Scraper",
    "description": "Every row gives you: name, stars, review score, nightly price (tax-inclusive), availability, cover image, address, geo. Two modes. Search a city for every hotel + homestay, or monitor specific properties across dates with price-change deltas and next-available on sold-out. Covers Agoda Homes.",
    "version": "0.1",
    "x-build-id": "IhBATxV67yvfQvjv3"
  },
  "servers": [
    {
      "url": "https://api.apify.com/v2"
    }
  ],
  "paths": {
    "/acts/bestscraper~agoda-property-scraper/run-sync-get-dataset-items": {
      "post": {
        "operationId": "run-sync-get-dataset-items-bestscraper-agoda-property-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/bestscraper~agoda-property-scraper/runs": {
      "post": {
        "operationId": "runs-sync-bestscraper-agoda-property-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/bestscraper~agoda-property-scraper/run-sync": {
      "post": {
        "operationId": "run-sync-bestscraper-agoda-property-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",
        "required": [
          "dateMatrix"
        ],
        "properties": {
          "searchDestinations": {
            "title": "Search a destination (recommended)",
            "type": "array",
            "description": "Paste an Agoda city URL (copy from the browser address bar after choosing a destination on agoda.com). The scraper finds every hotel + homestay in that city and returns one row per property × check-in date. Example: https://www.agoda.com/city/singapore-sg.html. You can also paste a numeric cityId as a string. Full-text queries like 'Bangkok' do NOT work. Use the city URL or cityId.",
            "items": {
              "type": "string"
            }
          },
          "searchCities": {
            "title": "Search by numeric cityId",
            "type": "array",
            "description": "Alternative to Search destinations for power users who already know Agoda's internal cityId (e.g. 4064 = Singapore, 5085 = Tokyo). Same output as destination search."
          },
          "propertyUrls": {
            "title": "Property URLs (single-property mode)",
            "type": "array",
            "description": "List of specific Agoda property URLs to monitor. Use this when you want rich per-property detail (amenities, host info, review themes, images) across a date range. Accepts canonical /<slug>/hotel/<location>.html URLs. Leave empty if you're using Search destinations above.",
            "items": {
              "type": "string"
            }
          },
          "dateMatrix": {
            "title": "Check-in dates & stay length",
            "type": "object",
            "description": "Which dates to scan. startFrom = 'today' or a YYYY-MM-DD date. scanDays = how many consecutive check-in dates to cover. lengthOfStay = nights per booking. stride = step between check-ins (1 = every day, 7 = weekly snapshots). Example: scanDays=14, stride=1 → 14 rows per property covering the next two weeks."
          },
          "flexDays": {
            "title": "Flex days (±N around your dates)",
            "minimum": 0,
            "maximum": 7,
            "type": "integer",
            "description": "Flexible-traveler mode. Also scans N days before AND after each check-in in your dateMatrix, so you can see how price shifts if the trip moves by a day or two. With one target date, flexDays=2 produces 5 rows (day-2, day-1, day, day+1, day+2). The property summary tells you which window is cheapest. Cost scales with rows. Default 0 means off.",
            "default": 0
          },
          "adults": {
            "title": "Adults per booking",
            "minimum": 1,
            "type": "integer",
            "description": "Number of adult guests. Affects pricing and availability (Agoda hides rooms that can't fit the party).",
            "default": 2
          },
          "rooms": {
            "title": "Rooms per stay",
            "minimum": 1,
            "type": "integer",
            "description": "Number of rooms to book. Most travel searches use 1.",
            "default": 1
          },
          "children": {
            "title": "Children per booking",
            "minimum": 0,
            "type": "integer",
            "description": "Number of children. Combined with adults to determine the occupancy filter Agoda applies.",
            "default": 0
          },
          "currency": {
            "title": "Currency",
            "type": "string",
            "description": "ISO currency code for prices in the output. Search mode supports any code Agoda displays. Monitor mode (propertyUrls) is reliable on USD, EUR, GBP, AUD, JPY, THB, MYR, PHP, IDR, VND, HKD; anything else falls back to USD with a warning.",
            "default": "USD"
          },
          "locale": {
            "title": "Locale",
            "type": "string",
            "description": "Controls the language of text fields in the output (property names, descriptions, review snippets). Common values: en-us, en-gb, de-de, fr-fr, ja-jp, zh-cn.",
            "default": "en-us"
          },
          "searchMaxResults": {
            "title": "Max results per destination (search mode only)",
            "minimum": 1,
            "maximum": 1000,
            "type": "integer",
            "description": "Cap how many properties to return per (city × check-in date). 45 = one page (cheapest). 200+ triggers pagination and scales cost linearly. Only applies when using Search destinations / Search by cityId.",
            "default": 45
          },
          "sortBy": {
            "title": "Sort order (search mode)",
            "enum": [
              "Recommended",
              "PriceAsc",
              "PriceDesc",
              "ReviewScore"
            ],
            "type": "string",
            "description": "How Agoda ranks results. Agoda caps unique properties per sort at roughly 650-1000 per city, so different sorts expose different inventory. Running all four sorts (Recommended, PriceAsc, PriceDesc, ReviewScore) back-to-back with skipSeen=true typically reveals 1.5 to 2 times more unique hotels than a single sort.",
            "default": "Recommended"
          },
          "skipSeen": {
            "title": "Skip properties emitted on past runs (dedup via KV store)",
            "type": "boolean",
            "description": "Dedup across runs without setting up your own storage. When true, the actor remembers every propertyId it already returned for each city (in a private per-account store) and suppresses duplicates before they hit the dataset — so you never pay twice for the same hotel. Pair with sortBy to fan out across all four sorts: run 1 grabs the bulk, runs 2-4 only pay for new inventory each sort surfaces.",
            "default": false
          },
          "skipPropertyIds": {
            "title": "Skip these propertyIds (manual dedup list)",
            "type": "array",
            "description": "Suppress emission for any propertyId in this list. Works in addition to skipSeen; both inputs are merged. Use this when you have your own database of IDs (e.g. a UI that tracks scraped hotels). The actor still fetches the search pages (compute cost unchanged) but drops matching rows before pushData so you do not pay the per-row dataset charge."
          },
          "mode": {
            "title": "Performance mode (legacy)",
            "enum": [
              "api-cold",
              "api",
              "browser"
            ],
            "type": "string",
            "description": "Kept for backwards compatibility. The actor always uses the same fast JSON API path today, so this setting has no effect. Older saved configs with `api` or `browser` are accepted and silently routed to the default. Leave as is.",
            "default": "api-cold"
          },
          "concurrency": {
            "title": "Parallel property scrapers",
            "minimum": 1,
            "maximum": 10,
            "type": "integer",
            "description": "How many properties to scrape in parallel. Higher is faster but raises the chance of rate limiting. 5 is a safe default.",
            "default": 5
          },
          "searchPoolConcurrency": {
            "title": "Parallel search pools (search mode only)",
            "minimum": 1,
            "maximum": 10,
            "type": "integer",
            "description": "How many (city, check-in date) search pools to scan in parallel. A pool is one city for one check-in; pages within a pool stay sequential because Agoda's pagination needs the previous page's token. Default 5 cuts wall-clock roughly linearly on multi-city or multi-date runs. Raise carefully: too high and Agoda may rate-limit.",
            "default": 5
          },
          "propertyCacheTtlHours": {
            "title": "Property intel cache (hours)",
            "minimum": 0,
            "type": "integer",
            "description": "Caches property name, rating, amenities, and host info in a named key-value store so repeat runs skip the full property detail fetch. Great for hourly price monitoring schedules. 168 = one week. Set to 0 to disable.",
            "default": 168
          },
          "proxyConfiguration": {
            "title": "Proxy configuration",
            "type": "object",
            "description": "Which proxy to use. The default (Apify Proxy, residential) is a safety net for the initial page load where Agoda blocks datacenter IPs. The price-monitoring API calls underneath use cheaper datacenter proxy automatically. You don't need to change anything unless you bring your own proxy pool."
          },
          "proxyCountry": {
            "title": "Proxy country (ISO code)",
            "enum": [
              "US",
              "GB",
              "DE",
              "FR",
              "IT",
              "ES",
              "AU",
              "JP",
              "KR",
              "SG",
              "TH",
              "VN",
              "ID",
              "PH",
              "MY",
              "IN",
              "HK",
              "TW",
              "AE",
              ""
            ],
            "type": "string",
            "description": "Pins proxy traffic to one country so Agoda's response is reproducible. This matters. Agoda serves different base prices by country (geo pricing), and forces tax inclusive display in EU, UK, and AU (regulated) while showing pre tax prices to US and most APAC markets. Pick the market you want to monitor. Default \"US\" gives pre tax USD. Leave blank to let proxy rotate freely. Not recommended, because it breaks cross run price deltas.",
            "default": "US"
          },
          "findNextAvailable": {
            "title": "Surface nearest-available dates on sold-out rows (±3 days)",
            "type": "boolean",
            "description": "Planning mode. When a scanned date is sold out, annotate the row with the nearest available check-in BEFORE (daysOffset -1, -2, -3) and AFTER (+1, +2, +3) drawn from the same scan. Zero extra API cost, because it uses only dates already scraped in this run. Your scanDays window must actually cover the neighbors for annotations to appear (for example, scanning a single sold-out date gives no neighbors). Perfect for 'is the trip shiftable?' answers: if Monday is sold out but Sunday or Tuesday is open, the row tells you. Populated fields: nextAvailableBefore and nextAvailableAfter, each with {checkIn, checkOut, nights, priceNightly, daysOffset}. Default off.",
            "default": false
          },
          "changedOnly": {
            "title": "Emit only rows whose price or availability changed vs. prior run",
            "type": "boolean",
            "description": "Price monitor mode. When enabled, a quote row is emitted to the dataset ONLY if (a) it's the first time we've seen this (property, check-in date, currency, occupancy, proxy country), meaning no prior run to compare against, or (b) the nightly price moved vs. the prior run, or (c) availability transitioned (for example, available to sold-out). Unchanged rows are silently skipped. The run still scrapes every date (cost is unchanged). It just suppresses dataset writes for no-op rows. Perfect for hourly schedules where you only want to process diffs. Summary and run-report rows always emit. The FIRST run with changedOnly=true emits everything (no prior state). Subsequent runs emit only changes. Count of skipped rows is in run-report.totals.rowsSkippedUnchanged.",
            "default": false
          },
          "alertBelowPrice": {
            "title": "Alert when nightly price drops below this threshold",
            "minimum": 0,
            "type": "integer",
            "description": "Threshold in the run's currency (see `currency`). When any available quote row has nightly price strictly below this, the row is recorded in the run's key-value store under `PRICE_ALERTS` (array of {propertySlug, checkIn, priceNightly, threshold, savingsVsThresholdPct, priceChangePct}). A boolean flag `HAS_PRICE_ALERTS` is always set so run-complete webhooks can gate on one key. Leave blank or 0 to disable. Typical recipe: schedule the actor hourly with `changedOnly: true` + `alertBelowPrice: 120`, then wire an Apify webhook that fires only when `HAS_PRICE_ALERTS=true`."
          },
          "apiProxyDatacenter": {
            "title": "Route price-monitoring calls via datacenter proxy (cheaper, enabled by default)",
            "type": "boolean",
            "description": "Routes the price monitoring API calls (date fan out and /graphql/property) through Apify's datacenter proxy instead of residential. About 8 times cheaper bytes, and validated as byte identical to residential on both endpoints as of 2026-04-16, so this is the default. Note: the initial property discovery page load still uses residential regardless of this flag, because Agoda gates datacenter IPs on that page. Set to false only if you need geo accurate local pricing on every date cell (rare, because Agoda's final booking flow price is authoritative either way).",
            "default": true
          },
          "canaryTtlMinutes": {
            "title": "Schema drift canary TTL (minutes)",
            "minimum": 0,
            "maximum": 1440,
            "type": "integer",
            "description": "Before each run, probe 3 known-stable properties and check that critical fields (name, price, rating, etc.) come back populated. If all 3 return null on the same field, Agoda likely changed their API and the run is aborted before emitting bad data. The result is cached in a named key-value store for this many minutes so frequent runs amortize the probe. 0 disables the canary entirely. Default 60 (one probe per hour across all runs). Adds about $0.001 per run the first time each hour.",
            "default": 60
          },
          "includeRaw": {
            "title": "Include raw HTML snapshot (debug)",
            "type": "boolean",
            "description": "Attach a truncated HTML snapshot to each row for debugging selectors. Increases dataset size significantly. Leave off in production.",
            "default": false
          }
        }
      },
      "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
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}