Ikyu Hotel Rate Scraper avatar

Ikyu Hotel Rate Scraper

Pricing

from $3.00 / 1,000 date scanneds

Go to Apify Store
Ikyu Hotel Rate Scraper

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

COBOCUS

Maintained by Community

Actor stats

0

Bookmarked

1

Total users

1

Monthly active users

an hour ago

Last modified

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: 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:

FieldTypeWhat it is
checkInDatestringThe stay date, YYYY-MM-DD. The axis of the time series.
capturedAtstringWhen this observation was made, so repeated runs stack into history.
availabilityboolean | nulltrue bookable, false confirmed not bookable, null not collected — never conflated.
roomName / planNamestringThe room type and the plan sold on it.
basePricenumberThe list price for the stay, before discounts.
sellingPricenumberWhat a guest pays.
discountAmountnumberbasePrice − sellingPrice.
pointAmountnumberPoints earned on the stay.
instantPointAmountnumberThe subset of those points usable immediately.
effectivePricenumbersellingPrice − instantly usable points. See "Effective price definition".
couponName / couponDiscountAmountstring / numberWhich coupon applied, and for how much.
remainingRoomCountnumber | nullikyu's own remaining-inventory signal, when shown.
cancelPolicy*variousThe cancellation ladder attached to the plan.
validationFlagsstring[]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

FieldTypeDefaultDescription
propertyUrlsstring[]— (required)ikyu.com property URLs, e.g. https://www.ikyu.com/00002130/
monthsAheadinteger6Scan today → today + N months. Ignored if startDate+endDate are both set.
startDate / endDatestring (YYYY-MM-DD)—Advanced: explicit date range instead of "today + N months".
adultsinteger2Adults per room.
childreninteger0Children per room (mapped to ikyu's default child age-band; see Known limitations).
roomsinteger1Rooms per booking.
includeSoldOutbooleantrueKeep rows for dates/rooms with no bookable plan.
datesPerRequestinteger15Advanced: dates batched per GraphQL request (max 18; see Known limitations).
maxConcurrencyinteger5Advanced: max parallel requests.
proxyConfigurationobject{ 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.

ChargePriceWhat it covers
Date scanned$0.003One (property × check-in date), available or sold out. A date whose request permanently fails is not charged.
Room × plan row$0.0001Each 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.01Once per property per run.
Actor start$0.006Once 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:

FieldMeaning
basePriceThe true list price ikyu holds for the plan, before any campaign discount.
originalPricebasePrice 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.
sellingPriceThe price actually displayed as the price on the page, before any point usage — after any further/instant discount on top of originalPrice.
discountAmountbasePrice - sellingPrice (the combined effect of both discount layers).
pointRate / pointAmountThe percentage and yen-equivalent value of ikyu points earned on this booking.
instantPointRate / instantPointAmountPoints 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).
bonusPointAmountExtra "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 / pointBurdenOtherRateWho 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).
remainingRoomCountRemaining bookable rooms for this exact room × plan × date (ikyu's own inventory field — verified against the "残りあと1室" urgency badge shown on ikyu.com itself).
cancelPolicyRulesJsonThe 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.
effectivePriceThe 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:

FlagMeaning
SELLING_PRICE_EXCEEDS_BASE_PRICE, EFFECTIVE_PRICE_EXCEEDS_SELLING_PRICE, NEGATIVE_SELLING_PRICEThe usual price relationships don't hold — either a data anomaly or a genuinely unusual campaign worth a closer look.
PLANS_TRUNCATEDThis 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_FAILEDThis 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:

ValueMeaning
trueBookable. Price fields are populated.
falseConfirmed sold out for that date/room/guest combination.
nullNot 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.SUCCEEDED to 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 (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 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.