Ikyu Hotel Rate Scraper
Pricing
from $3.00 / 1,000 date scanneds
Ikyu Hotel Rate Scraper
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.
Pricing
from $3.00 / 1,000 date scanneds
Rating
0.0
(0)
Developer
COBOCUS
Maintained by CommunityActor stats
0
Bookmarked
1
Total users
1
Monthly active users
an hour ago
Last modified
Categories
Share
Turns any 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: falserows, 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
propertyUrlsin one run. - Clean separation of concerns — ikyu-specific extraction is isolated from a
normalized
NormalizedRateoutput schema, keeping extraction, normalization, and output cleanly decoupled. Seesrc/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
- 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/. - Leave Months ahead at
6to scan every date from today through six months out, or set Start date and End date if you want an explicit window. - 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.
- Optional but recommended for monitoring: put a name in Run summary dataset name, so each run appends one health row to the same table.
- Leave Proxy configuration at its default. It is Japanese residential, which this Actor needs — see Known limitations.
- Click Start. A six-month single-property run takes under a minute.
- 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):
{"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):
{"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":
{"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 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?
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:
{"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 or SDKs.
- Attach a webhook on
ACTOR.RUN.SUCCEEDEDto push new data into your own pipeline as soon as a run finishes. - Use Apify 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 (
childACountthroughchildFCount) with different meal/bed inclusion rules. This Actor's simplechildreninput 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.mdfor 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: falsedrops 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.
datesPerRequesttrades 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
maxConcurrencyalone 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 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.