{
  "openapi": "3.0.1",
  "info": {
    "title": "Yanolja Scraper: Stays, Rates & Reviews",
    "description": "Scrape Yanolja (NOL), Korea's largest accommodations marketplace, by region or URL. Every row carries name, address, coordinates, phone, star class, room types with per-night rates, review score and review text, amenities, policies and photos from one stay page read.",
    "version": "1.0",
    "x-build-id": "JzByVDujg2xJWRnlT"
  },
  "servers": [
    {
      "url": "https://api.apify.com/v2"
    }
  ],
  "paths": {
    "/acts/abotapi~yanolja-com-scraper/run-sync-get-dataset-items": {
      "post": {
        "operationId": "run-sync-get-dataset-items-abotapi-yanolja-com-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/abotapi~yanolja-com-scraper/runs": {
      "post": {
        "operationId": "runs-sync-abotapi-yanolja-com-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/abotapi~yanolja-com-scraper/run-sync": {
      "post": {
        "operationId": "run-sync-abotapi-yanolja-com-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": [
          "mode"
        ],
        "properties": {
          "mode": {
            "title": "Mode",
            "enum": [
              "search",
              "url"
            ],
            "type": "string",
            "description": "How to start the scrape. 'search' browses the site's own ranked collection pages for the regions you list. 'url' processes the Yanolja URLs you paste: a stay URL (a page ending in /stay/domestic/<number>) is fetched as a single stay, and a collection URL (a page starting with /programmatic/domestic-accommodation/) is browsed like a search, its own path deciding the scope.",
            "default": "search"
          },
          "locations": {
            "title": "Regions and places",
            "type": "array",
            "description": "One or more Korean region or place names, for example '서울', '제주', '부산', '가평'. Each name is matched against the region names of the site's ranked collection pages, and every matching collection is scraped independently. Search mode only: in URL mode the pasted URL decides its own scope. A match with no ranked collection for a typed group is skipped with a warning, never invented.",
            "default": [
              "서울"
            ],
            "items": {
              "type": "string"
            }
          },
          "categoryGroup": {
            "title": "Stay group",
            "enum": [
              "all",
              "hotel",
              "motel",
              "pension"
            ],
            "type": "string",
            "description": "Narrow search mode to one availability group the site itself publishes: hotels and resorts, motels, or pensions and poolvillas. Search mode ONLY: in URL mode the pasted URLs decide their own scope. Leave empty for every group the region publishes.",
            "default": "all"
          },
          "urls": {
            "title": "Yanolja URLs",
            "type": "array",
            "description": "Paste stay URLs (https://nol.yanolja.com/stay/domestic/1000102592, optionally with ?checkIn=YYYY-MM-DD&checkOut=YYYY-MM-DD, or the m.yanolja.com form), collection URLs (https://nol.yanolja.com/programmatic/domestic-accommodation/<region-name>) or the two published sitemap indexes (expanded up to Max items). A pasted stay URL returns that single stay in full for its dates; a pasted collection URL is browsed for its list. Leaving this field untouched reads three different stays (plain, dated, m.yanolja.com form), two collection pages and the two sitemap indexes, which expand to as many stays as Max items allows. The price and ordering filters below still narrow what the run keeps.",
            "default": [
              "https://nol.yanolja.com/programmatic/domestic-accommodation/천안시-천안역-근처-펜션-풀빌라-추천-베스트-10",
              "https://nol.yanolja.com/programmatic/domestic-accommodation/%EA%B0%95%EC%9B%90%EB%8F%84-%EA%B7%BC%EC%B2%98-%EB%AA%A8%ED%85%94-%EC%88%99%EC%86%8C-%EC%B6%94%EC%B2%9C-%EB%B2%A0%EC%8A%A4%ED%8A%B8-10",
              "https://nol.yanolja.com/programmatic/domestic-accommodation/sitemap.xml",
              "https://nol.yanolja.com/stay/sitemap.xml",
              "https://nol.yanolja.com/stay/domestic/1000102592",
              "https://nol.yanolja.com/stay/domestic/1000114130/?checkIn=2026-11-10&checkOut=2026-11-11",
              "https://m.yanolja.com/stay/domestic/10040685"
            ],
            "items": {
              "type": "string"
            }
          },
          "minPriceKrw": {
            "title": "Minimum price (KRW)",
            "minimum": 0,
            "type": "integer",
            "description": "Only keep rows whose lowest available rate is at least this many Korean won. Applied to the rows this run reads. Applies in BOTH search mode and URL mode."
          },
          "maxPriceKrw": {
            "title": "Maximum price (KRW)",
            "minimum": 0,
            "type": "integer",
            "description": "Only keep rows whose lowest available rate is at most this many Korean won. Applied to the rows this run reads. Applies in BOTH search mode and URL mode."
          },
          "checkIn": {
            "title": "Check-in date",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "type": "string",
            "description": "Stay date (YYYY-MM-DD) the stay pages are read for, so room rates and sold-out flags are those of that night. Set together with Check-out date; a pair that is incomplete or not in order is ignored. A date pair already in a pasted stay URL wins. Applies in BOTH search mode and URL mode, only when full stay details are on."
          },
          "checkOut": {
            "title": "Check-out date",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "type": "string",
            "description": "Departure date (YYYY-MM-DD), after the check-in date. See Check-in date."
          },
          "sortResultsBy": {
            "title": "Order the returned rows by",
            "enum": [
              "site_order",
              "price_asc",
              "price_desc",
              "rating_desc",
              "review_count_desc"
            ],
            "type": "string",
            "description": "Arranges the rows this run collected, after they are read. This is a local tidy up of one run's output, not a catalogue wide ordering. Applies in BOTH search mode and URL mode.",
            "default": "site_order"
          },
          "fetchDetails": {
            "title": "Fetch full stay details",
            "type": "boolean",
            "description": "Read each listing's own stay page for the complete record: room types with per-night rates, review score and review text, address, coordinates, phone, amenities, policies and photos. This IS the main data read, because a bare listing row carries almost none of it, so leave it on unless you only want the cheap name-and-image listing stubs. Turning it on adds the Stay detail enrichment charge per record actually enriched this run.",
            "default": true
          },
          "maxItems": {
            "title": "Max items",
            "minimum": 0,
            "type": "integer",
            "description": "The single cap on this run: stop after collecting this many records across all regions, values and URLs. Set 0 for unlimited.",
            "default": 20
          },
          "maxPages": {
            "title": "Max collection pages per region",
            "minimum": 0,
            "type": "integer",
            "description": "Safety bound on how many of the site's ranked collection pages to walk per region name. Leave empty (0) for no page limit: the run then stops at Max items, not here. Collection pages are single ranked lists, so in practice one region rarely needs more than a handful of pages.",
            "default": 0
          },
          "proxy": {
            "title": "Proxy configuration",
            "type": "object",
            "description": "Leave the default: every stay page read travels through a Korean resident-type Exit connection, because stay pages were refused on shared and plain residential pools in live tests and are only served by the backup tier. Prices quoted on each row are Korean won.",
            "default": {
              "useApifyProxy": true,
              "apifyProxyGroups": [
                "RESIDENTIAL"
              ],
              "apifyProxyCountry": "KR"
            }
          },
          "resumeFromRunId": {
            "title": "Resume from a previous run",
            "type": "string",
            "description": "Paste a previous run ID or dataset ID to continue a large crawl without returning or charging for records already collected there. Use this after an interrupted or capped run. For recurring monitoring of the same search, use Incremental mode below instead."
          },
          "incrementalMode": {
            "title": "Incremental changes for scheduled runs",
            "type": "boolean",
            "description": "Turn this on for daily or weekly recurring monitoring of the same scope, for example a region price watch. The first run returns everything matching as NEW. Later runs normally return only NEW, UPDATED and REAPPEARED records. What is compared: every field of the stay record (rates, sold-out flags, room rates, description, facilities, photos, ratings and review counts); a change to any of them returns UPDATED with changedFields naming it. Always excluded: scrapedAt and detailCarriedForward, plus any field you list in Ignore fields. Rate moves ARE reported as UPDATED: on an accommodation marketplace the rate is the signal, not noise; use What counts as a change to compare only rates and sold-out flags. Turn on Emit unchanged or Emit expired only when you also want those rows returned (and billed). State is kept separately per region, URL and filter setup; use State key to name or deliberately share a monitoring campaign. To continue one specific interrupted run instead, use Resume from a previous run above.",
            "default": false
          },
          "stateKey": {
            "title": "State key (optional, incremental mode only)",
            "type": "string",
            "description": "Optional. Name this monitoring campaign to keep its state stable, or to deliberately share state across differently configured runs. Leave empty to let the actor derive a key automatically from the regions, URLs and filters, so two different scopes never mix state."
          },
          "changeScope": {
            "title": "What counts as a change (incremental mode only)",
            "enum": [
              "all",
              "priceAndStock"
            ],
            "type": "string",
            "description": "All fields (default): any change to the stay record, except the always-ignored bookkeeping fields, returns an UPDATED row. Price and availability only: only the lowest, overnight and day-use rates, the sold-out flags and each room's rates return an UPDATED row; changes to descriptions, photos, facilities, reviews or ratings never do. Changing this option keeps your saved state: stays are compared against what was stored, so it does not return the whole catalogue as NEW.",
            "default": "all"
          },
          "emitUnchanged": {
            "title": "Emit unchanged records (incremental mode only)",
            "type": "boolean",
            "description": "Off by default. Turn on to also return records that have not changed since the last run, marked UNCHANGED. This returns, and bills, extra rows you already have, so leave it off unless you specifically want a full snapshot every run.",
            "default": false
          },
          "emitExpired": {
            "title": "Emit expired records (incremental mode only)",
            "type": "boolean",
            "description": "Off by default. Turn on to also return records that were present in a previous run but are no longer found, marked EXPIRED. Only produced once a run has fully scanned the tracked scope: not when Max items capped it, not when Resume was used, and not when the run was refused. This returns, and bills, extra synthetic rows, so leave it off unless you need expiry tracking.",
            "default": false
          },
          "expireAfterMissedScans": {
            "title": "Expire after N missed full scans (incremental mode only)",
            "minimum": 1,
            "maximum": 30,
            "type": "integer",
            "description": "Only used with Emit expired records. A stay is marked EXPIRED only after it has been missing from this many full scans in a row (default 1). Raise it (for example 3) so a stay that briefly drops out of a listing is not reported as gone. A stay that comes back resets the count. A stay missing from the listing is not proof it is unavailable to book: use the sold-out fields for availability.",
            "default": 1
          },
          "compactUpdates": {
            "title": "Compact update rows (incremental mode only)",
            "type": "boolean",
            "description": "Off by default. When on, UPDATED rows (and EXPIRED rows) are short: ids, url, name, the current rates and sold-out flags, plus priceChanges and variantChanges showing the old and new value of each moved rate or sold-out flag, per room type. NEW rows always carry the complete stay record.",
            "default": false
          },
          "ignoreFieldsForChanges": {
            "title": "Ignore fields when detecting changes (incremental mode only)",
            "type": "array",
            "description": "Optional. Field names to exclude from UPDATED change detection, in addition to the built-in handling (scrapedAt and detailCarriedForward never trigger an UPDATED). Use this if a field in your setup turns out to be noisy. To ignore a field inside each room while still tracking room rates, use a dotted name such as rooms.photos (also rooms.benefits, rooms.subTitle). Ignoring rooms itself would also hide room rate changes.",
            "items": {
              "type": "string"
            }
          },
          "mcpConnectors": {
            "title": "Pipe results into your apps (optional)",
            "type": "array",
            "description": "Optionally send results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize one under Apify, Settings, API & Integrations, then select it here. Notion gets a page per record; other connectors get a best-effort write or digest. Each connector receives a condensed summary per record, not the full record; the complete record always stays in the dataset. Leave empty to skip; this never changes the dataset output. Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com)."
          },
          "notionParentPageUrl": {
            "title": "Notion parent page (Notion connector only)",
            "type": "string",
            "description": "URL or id of the Notion page under which record pages are created. Required to enable the Notion export; ignored by other connectors."
          },
          "maxNotifyListings": {
            "title": "Max items to export per connector",
            "minimum": 1,
            "maximum": 1000,
            "type": "integer",
            "description": "Cap on records written to each connector per run. Does not affect the dataset.",
            "default": 50
          }
        }
      },
      "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
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}