# Ikyu Hotel Rate Scraper (`cobocus/ikyu-hotel-rate-scraper`) Actor

Captures ikyu.com selling prices, points, and effective (points-applied) prices for every room x plan x future check-in date at any hotel/ryokan, including sold-out dates.

- **URL**: https://apify.com/cobocus/ikyu-hotel-rate-scraper.md
- **Developed by:** [COBOCUS](https://apify.com/cobocus) (community)
- **Categories:** Travel, E-commerce, Automation
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 date scanneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

Turns any [ikyu.com](https://www.ikyu.com/) hotel/ryokan page into a daily, machine-readable
time series of **every room, every plan, every future check-in date** — including sold-out
dates — with the selling price, points, coupons, and the actual **effective price** a guest
ends up paying once points are used instantly.

This is a **Data Connector**, not a one-off scraper: run it on a daily Apify Schedule and it
builds up `hotel × stay_date × captured_at` history you can use for rate-parity monitoring,
competitor tracking, revenue-management analysis, or content research (e.g. "how far ahead do
prices rise for New Year's Eve").

### What does this Actor do?

Given one or more ikyu.com property URLs, it scans **every day from today through N months
ahead** (default 6) and records, for each date:

- Every room × plan combination that is bookable, with a full price breakdown (list price,
  selling price, points earned, coupon, and the points-applied effective price).
- Dates/rooms that are **sold out** — absence of availability is itself valuable data for
  rate intelligence, so it's captured, not silently dropped.

It talks directly to ikyu.com's own public GraphQL API (`www.ikyu.com/graphql`) with plain
HTTP requests — no headless browser needed for the price data itself, which makes runs fast,
cheap, and reliable. See `docs/ikyu-research.md` for the full technical investigation behind
this design.

### Why use Ikyu Hotel Rate Scraper?

- **Hotels/ryokan listed on ikyu.com** who want to know what price + points combination OTAs
  are actually showing guests, not just what price they configured.
- **Revenue managers & hotel consulting firms** monitoring OTA rate parity across their
  portfolio.
- **Data analysts / content creators** studying Japanese hotel pricing trends — lead-time
  pricing, day-of-week pricing, holiday surge pricing, ADR trends, and so on.

This is a general-purpose Actor: point it at any ikyu.com property URL. It has no
hard-coded logic for any specific hotel.

Running it on Apify rather than your own machine gets you scheduling, an API and webhooks to
trigger runs and collect results, ready-made integrations (Zapier, Make, Google Sheets and
others), Japanese residential proxy rotation without managing it yourself, and a per-run health
summary you can monitor — all of which are covered below.

### Features

- **"Today + N months" by default** — the natural way to monitor a hotel's rolling sale
  window; run it daily via an Apify Schedule and you get a real time series.
- **Room × plan granularity** — not just the cheapest price. Every bookable plan for every
  room is captured separately.
- **Full price decomposition** — list price, selling price, discount amount, point rate,
  point amount, coupon, and the computed **effective price** (see below) are all kept as
  separate fields, never collapsed into a single "price" number.
- **Sold-out tracking** — dates/rooms with nothing bookable are recorded as
  `availability: false` rows, not silently skipped.
- **Efficient batching** — a lightweight availability calendar is fetched first (up to a
  year in one request) to skip already-sold-out dates for free, and the remaining dates
  are fetched in batches of up to 15-18 per request via GraphQL aliasing, so a 6-month
  scan of one property takes a dozen or so requests, not hundreds.
- **Multi-property** — pass any number of `propertyUrls` in one run.
- **Clean separation of concerns** — ikyu-specific extraction is isolated from a
  normalized `NormalizedRate` output schema, keeping extraction, normalization, and
  output cleanly decoupled. See `src/` layout below.

### What data can Ikyu Hotel Rate Scraper extract?

One row per (property × check-in date × room × plan). The fields most people come for:

| Field | Type | What it is |
|---|---|---|
| `checkInDate` | string | The stay date, `YYYY-MM-DD`. The axis of the time series. |
| `capturedAt` | string | When this observation was made, so repeated runs stack into history. |
| `availability` | boolean | null | `true` bookable, `false` confirmed not bookable, `null` **not collected** — never conflated. |
| `roomName` / `planName` | string | The room type and the plan sold on it. |
| `basePrice` | number | The list price for the stay, before discounts. |
| `sellingPrice` | number | **What a guest pays.** |
| `discountAmount` | number | `basePrice` − `sellingPrice`. |
| `pointAmount` | number | Points earned on the stay. |
| `instantPointAmount` | number | The subset of those points usable immediately. |
| `effectivePrice` | number | `sellingPrice` − instantly usable points. See "Effective price definition". |
| `couponName` / `couponDiscountAmount` | string / number | Which coupon applied, and for how much. |
| `remainingRoomCount` | number | null | ikyu's own remaining-inventory signal, when shown. |
| `cancelPolicy*` | various | The cancellation ladder attached to the plan. |
| `validationFlags` | string\[] | Data-quality flags. Never used to drop a row. |

The full set — occupancy, meal codes, sale type, check-in/out times, bonus and 全国 coupon
fields — is in `.actor/dataset_schema.json`.

### How to scrape ikyu.com prices

1. Open the Actor and paste one or more ikyu.com property URLs into **Property URLs** — the page
   you would send a guest to, for example `https://www.ikyu.com/00002130/`.
2. Leave **Months ahead** at `6` to scan every date from today through six months out, or set
   **Start date** and **End date** if you want an explicit window.
3. Set **Adults** and **Rooms** to the occupancy you want priced. ikyu.com prices a whole stay
   for a given occupancy, so this changes every price it returns.
4. Optional but recommended for monitoring: put a name in **Run summary dataset name**, so each
   run appends one health row to the same table.
5. Leave **Proxy configuration** at its default. It is Japanese residential, which this Actor
   needs — see Known limitations.
6. Click **Start**. A six-month single-property run takes under a minute.
7. Open the **Dataset** tab and export as JSON, CSV or Excel, or fetch it from the API.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `propertyUrls` | string\[] | — (required) | ikyu.com property URLs, e.g. `https://www.ikyu.com/00002130/` |
| `monthsAhead` | integer | `6` | Scan today → today + N months. Ignored if `startDate`+`endDate` are both set. |
| `startDate` / `endDate` | string (`YYYY-MM-DD`) | — | Advanced: explicit date range instead of "today + N months". |
| `adults` | integer | `2` | Adults per room. |
| `children` | integer | `0` | Children per room (mapped to ikyu's default child age-band; see Known limitations). |
| `rooms` | integer | `1` | Rooms per booking. |
| `includeSoldOut` | boolean | `true` | Keep rows for dates/rooms with no bookable plan. |
| `datesPerRequest` | integer | `15` | Advanced: dates batched per GraphQL request (max 18; see Known limitations). |
| `maxConcurrency` | integer | `5` | Advanced: max parallel requests. |
| `proxyConfiguration` | object | `{ useApifyProxy: true }` | On by default and effectively required on the Apify platform — see Known limitations. |

Simple mode (recommended for most users):

```json
{
  "propertyUrls": ["https://www.ikyu.com/00002130/"],
  "monthsAhead": 6,
  "adults": 2,
  "children": 0,
  "rooms": 1
}
```

Advanced mode (explicit range, multiple properties, proxy):
see `examples/input-advanced.json`.

### Output

One dataset row per (property × check-in date × room × plan), plus one row per
(property × check-in date × room) that is sold out. See `examples/output-sample.json` for
two full real rows (one available, one sold out), and `.actor/dataset_schema.json` for the
formal schema used to render the dataset's "Rates overview" table view in the Apify Console.

### Example

Input: `examples/input.json` — one property, 6 months ahead, 2 adults.

A single available row looks like this (values are illustrative, captured live during
development — re-running will show current prices):

```json
{
  "propertyId": "00002130",
  "propertyName": "ホテルニューオータニ",
  "checkInDate": "2026-12-28",
  "checkOutDate": "2026-12-29",
  "roomName": "ザ・メイン スタンダード ダブル  禁煙",
  "planName": "正規料金",
  "availability": true,
  "remainingRoomCount": 11,
  "basePrice": 100746,
  "originalPrice": 90672,
  "sellingPrice": 86139,
  "pointRate": 5,
  "instantPointAmount": 4533,
  "effectivePrice": 81606,
  "capturedAt": "2026-09-22T07:39:14.551Z"
}
```

And a sold-out row for the same property, the very next night (New Year's Eve peak
season) — note `waitlistAvailable`, which distinguishes "sold out, nothing else to do"
from "sold out, but a waitlist exists":

```json
{
  "propertyId": "00002130",
  "checkInDate": "2026-12-29",
  "availability": false,
  "waitlistAvailable": true
}
```

See `examples/output-sample.json` for both rows in full.

### How many months can I scrape?

There's no hard cap enforced by the Actor beyond the Input Schema's sanity limit
(`monthsAhead` up to 18). In practice, ikyu.com itself does not accept bookings arbitrarily
far in the future — properties typically stop returning bookable plans somewhere between a
few months and roughly a year out (verified: this varies per property; dates beyond a
property's own booking window simply come back as sold out / no data, which the Actor
records normally rather than erroring). **6 months is the recommended default** and matches
most properties' realistic sale window.

### How much will it cost?

Two charges, because the work has two parts. Every check-in date costs the same fixed work — the
availability calendar and the detail query that cover it — while what a date *contains* depends
entirely on how many room × plan combinations the property sells.

| Charge | Price | What it covers |
|---|---|---|
| Date scanned | **$0.003** | One (property × check-in date), available or sold out. A date whose request permanently fails is **not** charged. |
| Room × plan row | **$0.0001** | Each row written: one room × plan on one date, with its selling price, points and effective price. A sold-out date writes one row. Rows recording a failed collection are **not** charged. |
| Property processed | $0.01 | Once per property per run. |
| Actor start | $0.006 | Once per run, so that very short runs still cover their own container startup. |

**The floor is predictable**: `properties × days × $0.003`, or **$0.55** for a six-month
single-property scan. Above that you pay for the rows you actually receive. On a measured six-month
scan of one property — 182 dates, 24,380 room × plan rows — the total came to **$3.00**.

This replaced a flat $0.006 per date on 2026-09-26. Under the flat price a property with few plans
paid the same as one with many; now a small property pays less and a large one pays for what it
returns. Per row, $0.0001 is a fraction of the $0.0015–$0.005 that row-priced competitors on Apify
Store charge for comparable hotel rate data.

### Pricing data definitions

Ikyu.com's own pricing is more layered than a single "price" — this Actor keeps every layer
separate so you can decide what "the price" means for your analysis:

| Field | Meaning |
|---|---|
| `basePrice` | The true list price ikyu holds for the plan, before any campaign discount. |
| `originalPrice` | `basePrice` after ikyu's own base campaign discount (`baseDiscountRate`, a percentage) — this is the price actually shown struck-through on ikyu.com. Equals `basePrice` when there's no base campaign running (`baseDiscountRate: 0`), but can differ — properties running a base discount show `originalPrice < basePrice`. |
| `sellingPrice` | The price actually displayed as *the* price on the page, before any point usage — after any further/instant discount on top of `originalPrice`. |
| `discountAmount` | `basePrice - sellingPrice` (the combined effect of both discount layers). |
| `pointRate` / `pointAmount` | The percentage and yen-equivalent value of ikyu points earned on this booking. |
| `instantPointRate` / `instantPointAmount` | Points available for **instant use** at checkout (in every sample observed during development this equals `pointRate`/`pointAmount`, but the API exposes them as distinct fields, so both are kept — they could diverge under other campaigns). |
| `bonusPointAmount` | Extra "bonus"/campaign points on top of the standard rate (ikyu calls this `uwanosePoint`). |
| `couponDiscountAmount` / `couponPrice` / `zenkokuCoupon*` | Coupon-driven discounts, when active for that property/date. |
| `pointBurdenAccommodationRate` / `pointBurdenOtherRate` | **Who funds the point-back campaign**, as two percentages: the hotel itself, vs. ikyu (or another party). This is the field that most directly answers this Actor's core question — whether a discount is coming out of the hotel's own pocket or an OTA-funded promotion. Null when ikyu doesn't disclose it for that row (the common case). |
| `remainingRoomCount` | Remaining bookable rooms for this exact room × plan × date (ikyu's own `inventory` field — verified against the "残りあと1室" urgency badge shown on ikyu.com itself). |
| `cancelPolicyRulesJson` | The plan's cancellation-fee rules as a JSON string (day-before-checkin tiers, at-booking, no-show) — kept as JSON rather than flattened columns since ikyu models it as a small polymorphic rule list. |
| `effectivePrice` | **The headline metric.** See below. |

### Effective price definition

Ikyu.com shows a "ポイント即時利用後" (after instant point use) price on its own site, but —
confirmed during technical research (`docs/ikyu-research.md`, section 7) — **this number is
computed in the browser, not returned by the API**. This Actor reproduces the identical
calculation explicitly:

```
effectivePrice = max(0, sellingPrice − instantPointAmount − bonusPointAmount)
```

This is "what the user actually pays" once they use their newly-earned points immediately —
the number that matters when comparing ikyu.com against a hotel's official Direct Booking
price or another OTA, since a lower *sellingPrice* elsewhere can still lose to a higher
*sellingPrice* here once points are counted.

A `validationFlags` array flags (but never discards) anomalous rows:

| Flag | Meaning |
|---|---|
| `SELLING_PRICE_EXCEEDS_BASE_PRICE`, `EFFECTIVE_PRICE_EXCEEDS_SELLING_PRICE`, `NEGATIVE_SELLING_PRICE` | The usual price relationships don't hold — either a data anomaly or a genuinely unusual campaign worth a closer look. |
| `PLANS_TRUNCATED` | This room had more bookable plans for this date than the Actor requests in one query, so the plan list for that room × date is incomplete. Rare: no property observed during development came close to the limit. |
| `COLLECTION_FAILED` | This date was requested but never successfully read — every retry was blocked. `availability` is `null`, not `false`: nothing is known about whether the property was bookable. See "Availability has three states" below. |

#### Availability has three states

`availability` is nullable, and the three values mean different things:

| Value | Meaning |
|---|---|
| `true` | Bookable. Price fields are populated. |
| `false` | **Confirmed** sold out for that date/room/guest combination. |
| `null` | **Not collected.** The request failed permanently; the row exists only so the gap is visible in the data. Always carries `COLLECTION_FAILED`. |

If you are building a time series, treat `null` as "retry this date", never as a sell-out — counting
it as one would fabricate a sold-out night that never happened. These rows are emitted even when
`includeSoldOut` is `false`, because a collection failure is not a sold-out row and you should not
lose the ability to notice a gap by asking for a leaner dataset.

### Sold-out dates

When a room (or an entire property) has no bookable plan for a given check-in date, the
Actor still emits a row: `availability: false`, with `roomId`/`roomName` set when it's a
specific room that's sold out, or `null` when the whole property came back sold out for
that date via the calendar pre-filter (see below). All price/plan fields are `null` on
these rows, except `waitlistAvailable`: ikyu distinguishes a plain sold-out date from one
where it still offers a waitlist, and this Actor keeps that distinction. Set
`includeSoldOut: false` if you only want bookable rows.

Internally, the Actor first fetches a lightweight availability calendar for the whole date
range (1-2 requests per property) and only sends the more expensive room × plan detail
query for dates that calendar says are actually bookable — sold-out dates are recorded
straight from the calendar, at no extra request cost.

### Scheduling daily monitoring

Run this Actor on an [Apify Schedule](https://docs.apify.com/platform/schedules) once a day
with the same Input (simple `monthsAhead` mode). Each run's `capturedAt` timestamp lets you
build a `property × checkInDate × capturedAt` time series in your own warehouse — join
consecutive days' datasets on `(propertyId, checkInDate, roomId, planId)` to see price
changes, new sell-outs, newly-released inventory, and point-rate campaign changes over time.

Set `runSummaryDatasetName` when you do this. Without it, confirming that each night's run
actually collected everything means opening every run separately; with it, every run appends
one row to a single table (see [Run summary](#run-summary-did-this-run-collect-everything)).

### Run summary: did this run collect everything?

A six-month single-property run writes roughly 24,000 rows. Checking whether it actually
collected every date by reading those rows is impractical, so each run answers the question
itself and writes the answer to its key-value store as `OUTPUT`:

```json
{
  "runDateJst": "2026-09-24",
  "complete": true,
  "totalDatesRequested": 182,
  "totalDatesCollected": 182,
  "totalCollectionFailedDates": 0,
  "totalRowsPushed": 24380,
  "minCheckInDate": "2026-09-24",
  "maxCheckInDate": "2027-03-25",
  "startsOnRunDate": true,
  "httpErrorsByStatus": {},
  "requestRetries": 0,
  "validationFlagCounts": {},
  "properties": [ { "propertyId": "00003159", "datesCollected": 182, "collectionFailedDates": [], "...": "..." } ]
}
```

`complete` is `true` only when every property returned every requested date with no
`COLLECTION_FAILED` row, so a single boolean tells you whether the day's data is trustworthy.
`httpErrorsByStatus` counts *every* failed attempt, including ones a retry later recovered —
a run that fought its way through repeated blocks looks nothing like one that sailed through,
and that difference is invisible in the dataset itself.

If you set `runSummaryDatasetName`, the same object is also appended as **one row per run** to
that named dataset. On a daily schedule this builds a small health table you can read at a
glance instead of opening each run in turn, and named datasets are exempt from storage
retention, so the history does not expire.

### Webhook / API integration

Like any Apify Actor, you can:

- Trigger runs via the [Apify API](https://docs.apify.com/api/v2) or SDKs.
- Attach a [webhook](https://docs.apify.com/platform/integrations/webhooks) on
  `ACTOR.RUN.SUCCEEDED` to push new data into your own pipeline as soon as a run finishes.
- Use [Apify Integrations](https://apify.com/integrations) (Zapier, Make, Google Sheets,
  etc.) to route the dataset elsewhere without writing code.

### Dataset export

Every run's dataset can be exported from the Apify Console (or API) as JSON, CSV, Excel,
XML, RSS, or HTML — standard Apify dataset export, no special handling needed. For
time-series analysis across many daily runs, exporting to CSV/Parquet and loading into a
warehouse (BigQuery, Snowflake, DuckDB, etc.) keyed on
`(propertyId, checkInDate, roomId, planId, capturedAt)` is recommended.

### Known limitations

- **Child age-bands**: ikyu.com's search supports six child age-bands (`childACount`
  through `childFCount`) with different meal/bed inclusion rules. This Actor's simple
  `children` input maps to band A (the most common default). Precise age-band control is a
  possible future enhancement.
- **Multi-night stays**: the Actor defaults to 1-night stays (matching the day-by-day
  monitoring model this Actor is built around). Multi-night (`lodgingCount > 1`) is
  confirmed to work correctly against the live API (verified via the real booking widget),
  but only the 1-night path is wired into the Actor's Input/crawl loop today.
- **GraphQL query complexity limit**: ikyu's API enforces a complexity budget of 1000 per
  request. With this Actor's full field selection (price, points, coupons, stock,
  cancellation policy, etc. for every room × plan), that caps batching at ~19 dates per
  request — the Actor defaults to 15 (`datesPerRequest`, max 18) to stay safely under that.
  This is the only rate/limit behavior observed for this API; no request-count throttling
  or IP blocking was encountered, but the Actor still applies conservative default
  concurrency (`maxConcurrency: 5`) and Crawlee's built-in retry/backoff regardless.
- **No API-based shortcut, by design**: this Actor deliberately calls the same endpoint
  ikyu.com's own frontend calls, reproducing exactly what a user's browser would see
  (selling price, points, effective price) — rather than a simplified public API that
  cannot represent OTA-funded point campaigns. See `docs/ikyu-research.md` for why.
- **Proxy is effectively required, and the default is Japanese residential**: ikyu.com blocks
  Apify's datacenter IPs. Measured A/B over a full 182-day scan of the same property on the same
  day — US datacenter proxy returned **22 HTTP 403s**, lost an entire batch once its retries were
  exhausted and collected **167 of 182 dates**; Japanese residential proxy returned **zero 403s**,
  12/12 requests and **all 182 dates**. The Actor therefore defaults to
  `{ useApifyProxy: true, apifyProxyGroups: ["RESIDENTIAL"], apifyProxyCountry: "JP" }`.
  Residential proxy is billed per GB, but this Actor moves only ~1 MB per 182-day run, so the
  cost is negligible. Override the default only if you have a specific reason.

### Tips and advanced options

- **Pay for less by collecting less.** Rows are charged individually, so `includeSoldOut: false`
  drops the sold-out rows and their cost with them. Keep it on if you care when a property sells
  out — that is rate data too.
- **Narrow the window for a cheaper daily run.** A regression in date handling or blocking shows
  up in a 30-day scan as clearly as in a 182-day one. Scan the full six months when you need the
  far end of the sale window, not every day.
- **`datesPerRequest` trades requests against complexity.** ikyu's GraphQL API enforces a
  complexity budget, which caps batching at about 19 dates per request; the default of 15 stays
  under it. Raising it reduces requests but risks rejections on properties with many plans.
- **Leave `maxConcurrency` alone unless you have a reason.** The default of 5 is deliberately
  conservative against a live production site.

### FAQ

**Does this need an ikyu.com account or login?**
No. It reads only what an anonymous visitor sees.

**Can I scrape past dates?**
No. ikyu.com does not sell them, and the Actor clamps the range to start no earlier than today
in Japan Standard Time.

**Why is `effectivePrice` not what I would pay at checkout?**
Because ikyu awards points rather than deducting them. `sellingPrice` is what the guest pays;
`effectivePrice` subtracts the points earned, which is the right number for comparing against an
OTA whose discount is deducted immediately. See "Effective price definition" above.

**A run came back with fewer dates than I asked for. How do I tell?**
You do not have to read the rows. Set `runSummaryDatasetName`, then check `complete` and
`totalCollectionFailedDates` in the summary — dates that could not be collected are written with
`availability: null` rather than being dropped, so they are visible in the data too.

**Why does it default to a Japanese residential proxy?**
Because ikyu.com blocks Apify's datacenter IP range. The measured difference is in Known
limitations.

### Ethical scraping, and your responsibilities

This Actor collects **only publicly available information** — the room, plan, price and points
data any visitor to a property's public pages can see. It collects no personal data, no guest
information, and nothing behind a login, and it never makes, changes or cancels a booking.

It is still your responsibility to use the data lawfully. Review ikyu.com's terms of use, keep
your request volume reasonable (the defaults are deliberately conservative), and make sure your
use has a legitimate basis under the law that applies to you — including the GDPR if you are in
the EU or handling EU data. If you plan to republish or commercially redistribute what you
collect, take legal advice first. Apify's
[ethical web scraping](https://blog.apify.com/what-is-ethical-web-scraping-and-how-do-you-do-it/)
guide is a good starting point.

### Support

Found a bug, a property this Actor mishandles, or a field you need that is missing? Open an issue
on the Actor's **Issues** tab. Include the run ID — the run summary in the key-value store has
everything needed to reproduce it.

# Actor input Schema

## `propertyUrls` (type: `array`):

One or more ikyu.com property URLs, e.g. https://www.ikyu.com/00002130/. Any number of properties is supported.

## `monthsAhead` (type: `integer`):

Scan every day from today through this many months ahead. Ignored if both Start date and End date are set. Run this Actor daily to build a rolling 'today + N months' time series.

## `startDate` (type: `string`):

Advanced: explicit range start, YYYY-MM-DD. Leave empty to start from today (recommended for daily monitoring).

## `endDate` (type: `string`):

Advanced: explicit range end, YYYY-MM-DD, inclusive. Leave empty to use Start date + Months ahead.

## `adults` (type: `integer`):

Number of adult guests per room.

## `children` (type: `integer`):

Number of children per room. Mapped to ikyu's default child age-band (elementary-school age, with meal & bed); see README for details.

## `rooms` (type: `integer`):

Number of rooms to search for per booking.

## `includeSoldOut` (type: `boolean`):

Keep rows for dates/rooms with no bookable plan. Recommended ON: knowing when a hotel sells out is itself valuable rate data.

## `datesPerRequest` (type: `integer`):

How many check-in dates to batch into a single GraphQL request. ikyu's API enforces a query complexity limit that this Actor's field selection hits at ~19-20 dates/request, so 18 is the hard maximum; 15 is the default and leaves some headroom.

## `maxConcurrency` (type: `integer`):

Maximum number of batched requests in flight at once, across all properties. Capped at 10; this Actor only needs around a dozen requests per property, and a live production site should not be hit harder than that.

## `runSummaryDatasetName` (type: `string`):

Optional. Name of a dataset to append this run's summary to, as a single row per run. Useful on a schedule: consecutive daily runs build one small table you can read at a glance to confirm each run collected every date, instead of aggregating tens of thousands of rate rows. Named datasets are retained indefinitely. The summary is always written to this run's key-value store as OUTPUT regardless of this setting.

## `proxyConfiguration` (type: `object`):

Defaults to Apify Proxy on the Japanese RESIDENTIAL group, which is effectively required: ikyu.com blocks Apify datacenter IPs. Measured A/B over a 182-day scan - US datacenter proxy returned 22 HTTP 403s and silently lost 15 dates; JP residential returned none and collected all 182. Residential is billed per GB, but this Actor moves only ~1 MB per run. Override only if you have a reason to.

## Actor input object example

```json
{
  "propertyUrls": [
    "https://www.ikyu.com/00002130/"
  ],
  "monthsAhead": 6,
  "adults": 2,
  "children": 0,
  "rooms": 1,
  "includeSoldOut": true,
  "datesPerRequest": 15,
  "maxConcurrency": 5,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "JP"
  }
}
```

# Actor output Schema

## `results` (type: `string`):

Normalized ikyu.com hotel rate records: one row per property x check-in date x room x plan that's bookable, plus one row per property x check-in date x room that's sold out.

## `runSummary` (type: `string`):

One JSON object reporting whether this run collected everything it was asked for: dates requested vs collected, any dates left as availability=null / COLLECTION\_FAILED, the check-in date range actually present in the data, HTTP errors by status, and validation-flag counts.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "propertyUrls": [
        "https://www.ikyu.com/00002130/"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "JP"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("cobocus/ikyu-hotel-rate-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "propertyUrls": ["https://www.ikyu.com/00002130/"],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "JP",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("cobocus/ikyu-hotel-rate-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "propertyUrls": [
    "https://www.ikyu.com/00002130/"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "JP"
  }
}' |
apify call cobocus/ikyu-hotel-rate-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,cobocus/ikyu-hotel-rate-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/QWZcXzTFpavfBNZhL/builds/WGCH15GkSZqhmDtXc/openapi.json
