# Klook Car Rentals Scraper (`romy/klook-car-rentals-scraper`) Actor

Get real car rental search results — a niche no other Klook scraper covers from Klook. Talks directly to Klook's own mobile app API, no login needed. Split from the mature, published klook-all-in-one-api for a focused, single-purpose workflow.

- **URL**: https://apify.com/romy/klook-car-rentals-scraper.md
- **Developed by:** [Romy](https://apify.com/romy) (community)
- **Categories:** Travel
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $7.80 / 1,000 car rental search chargeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

### What does Klook Car Rentals Scraper do?

**Klook Car Rentals Scraper** runs real car rental search on Klook — real bookable cars with real suppliers and prices — plus the location-resolution endpoints the real app requires before it will search: countries/cities available, real pickup/drop-off locations for a city, and free-text location lookup.

It talks directly to the same internal API the official [Klook](https://www.klook.com/) Android app uses, reverse-engineered by disassembling the app's native request-signing library and validated against live traffic captured from a real device. No Klook account, no API key. This is the car-rentals slice of [Klook All-in-One API](https://apify.com/romy/klook-all-in-one-api), split out as its own focused Actor.

### Why use Klook Car Rentals Scraper?

- **A real search-then-poll flow, not a stub** — the app itself kicks off an async search (returns a job id) then polls for results repeatedly (confirmed live by the parent Actor: ~10 polls before real results appear); this Actor does that polling server-side so callers get one clean response with real results
- **A genuinely separate subsystem** — confirmed live by the parent Actor to use the exact same signing scheme as the activities API, but talking to a completely different backend (`transfercarrentalapisrv`)
- **Real, working pagination** — confirmed live by the parent Actor: distinct `page` values return distinct car ids
- **No account needed** — every mode works fully anonymously

### How it connects to the parent

This Actor is a narrow, batch-run spin-off of [klook-all-in-one-api](https://apify.com/romy/klook-all-in-one-api) (an always-on Standby REST API exposing every Klook endpoint). It reuses the parent's exact, proven request-signing logic (`klook-client.ts`, copied verbatim, including the same GET/POST signed-request machinery) but only wraps `/transport/car-rentals/countries`, `/transport/car-rentals/locations`, `/transport/car-rentals/search-locations`, and `/transport/car-rentals/search`, and runs as a normal input-in/dataset-out Actor instead of a Standby API.

### The real two-step flow

The app itself resolves a typed pickup location to a full location object (country code, IATA-style location code, poi id/name, lng/lat) **before** it will call search at all — a free-text string alone isn't enough. This Actor mirrors that exactly via `mode`:

1. Run with `mode: "resolveByCity"` (given a `cityId` from `mode: "listCountries"`) or `mode: "resolveBySearch"` (given free text like `"Osaka"`) to get real location fields.
2. Run again with `mode: "search"`, plugging the resolved `country_code`/`iata_code`/`poi_id`/`poi_name`/`lng,lat` into the flat `pickup*` input fields.

### Input

| Field                                                                                                                                                          | Type             | Description                                                                                  |
| -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | -------------------------------------------------------------------------------------------- |
| `mode`                                                                                                                                                         | string           | `listCountries` | `resolveByCity` | `resolveBySearch` | `search` (default)                |
| `cityId`                                                                                                                                                       | string           | Required for `resolveByCity`. From `listCountries`.                                          |
| `searchWord` / `sourceCountryCode`                                                                                                                             | string           | Required for `resolveBySearch` (`sourceCountryCode` default `GB`).                           |
| `pickupCountryCode` / `pickupLocationCode` / `pickupLocationId` / `pickupLocationName` / `pickupCityId` / `pickupCityName` / `pickupLngLat` / `pickupDateTime` | string           | Required for `search` — resolved via step 1 above.                                           |
| `return*` (same shape as `pickup*`)                                                                                                                            | string           | Optional for `search` — omit all to return to the pickup location.                           |
| `age` / `licenseCountryCode`                                                                                                                                   | integer / string | Driver age (default `30`), license country (default `GB`).                                   |
| `page` / `pageSize` / `maxPolls`                                                                                                                               | integer          | Result page (default `1`), results per page (default `10`), internal poll cap (default `8`). |
| `currency`                                                                                                                                                     | string           | ISO code, default `GBP`                                                                      |

Example (step 1, resolve):

```json
{ "mode": "resolveByCity", "cityId": "28" }
```

Example (step 2, search):

```json
{
    "mode": "search",
    "pickupCountryCode": "JP",
    "pickupLocationCode": "HND",
    "pickupLocationId": "50036510",
    "pickupLocationName": "Tokyo Haneda International Airport",
    "pickupCityId": "28",
    "pickupCityName": "Tokyo",
    "pickupLngLat": "139.7783379,35.5495626",
    "pickupDateTime": "2026-09-15 10:00",
    "returnDateTime": "2026-09-18 10:00"
}
```

### Output

One row per call. Real example (from the parent Actor's own live-confirmed capture of a Tokyo Haneda search, trimmed):

```json
{
    "mode": "search",
    "data": {
        "success": true,
        "results": {
            "result": {
                "car_count": 214,
                "car_card_list": [
                    {
                        "car_brand_name": "Toyota",
                        "car_name": "Vitz",
                        "car_group_type": "Compact car",
                        "seat": 5,
                        "luggage": 2
                    }
                ]
            }
        }
    }
}
```

### Known limitations

- **Requires Apify's RESIDENTIAL proxy group.** Klook's edge blocks non-residential IPs with a DataDome bot-challenge page — confirmed directly from this development sandbox (a request byte-for-byte identical to a working one, sent without the proxy, came back as a DataDome JS-challenge HTML page, not JSON). All outbound requests route through `Actor.createProxyConfiguration({ groups: ['RESIDENTIAL'] })`.
- **Local dev smoke testing outside the Apify platform needs "Proxy external access" enabled on the account.** This Actor's code was verified to be a byte-for-byte match against the parent's already-published, working `klook-client.ts`, and the local smoke test run during development failed exactly at the proxy-URL-acquisition step (`ProxyConfiguration: The "Proxy external access" feature isn't enabled for your account`) — before any request to Klook was even attempted. That's an account-tier restriction on pulling a proxy URL from *outside* a real platform Actor run, not a defect in this code; once deployed and actually run on Apify's platform, proxy access works through the normal SDK path.
- **`mode=search` re-runs the full search-then-poll flow for every `page`.** There is no way to page an already-completed search without starting a new one — each call is a genuinely fresh search, which costs more time/compute than the other modes.
- This is an unofficial, reverse-engineered integration, not affiliated with or endorsed by Klook Travel Technology Limited.

### Pricing

Pay per event. Charged **$0.012 per `car-rental-search`** (FREE tier) — one event per row pushed to the dataset. See the Actor's Pricing tab for rates across all tiers.

# Actor input Schema

## `mode` (type: `string`):

listCountries = countries/cities available. resolveByCity = real pickup/return locations for a city id. resolveBySearch = free-text location lookup. search = real car rental search (default) — needs resolved pickup fields, run resolveByCity or resolveBySearch first.

## `cityId` (type: `string`):

Required for mode=resolveByCity. From mode=listCountries.

## `searchWord` (type: `string`):

Required for mode=resolveBySearch. Free text, e.g. "Osaka".

## `sourceCountryCode` (type: `string`):

For mode=resolveBySearch.

## `pickupCountryCode` (type: `string`):

Required for mode=search. From a resolved location's country\_code, e.g. "JP".

## `pickupLocationCode` (type: `string`):

From a resolved location's iata\_code, e.g. "HND".

## `pickupLocationId` (type: `string`):

From a resolved location's poi\_id.

## `pickupLocationName` (type: `string`):

From a resolved location's poi\_name.

## `pickupCityId` (type: `string`):

Pickup city id, from the car-rental locations lookup.

## `pickupCityName` (type: `string`):

Pickup city name.

## `pickupLngLat` (type: `string`):

"longitude,latitude" from a resolved location.

## `pickupDateTime` (type: `string`):

"YYYY-MM-DD HH:mm".

## `returnCountryCode` (type: `string`):

Drop-off country code, if different from pickup.

## `returnLocationCode` (type: `string`):

Drop-off location code, if different from pickup.

## `returnLocationId` (type: `string`):

Drop-off location id, if different from pickup.

## `returnLocationName` (type: `string`):

Drop-off location name, if different from pickup.

## `returnCityId` (type: `string`):

Drop-off city id, if different from pickup.

## `returnCityName` (type: `string`):

Drop-off city name, if different from pickup.

## `returnLngLat` (type: `string`):

Drop-off coordinates as "lng,lat", if different from pickup.

## `returnDateTime` (type: `string`):

"YYYY-MM-DD HH:mm". Omit all return\* fields to return to the pickup location.

## `age` (type: `integer`):

Driver's age, used for eligibility/pricing rules.

## `licenseCountryCode` (type: `string`):

Driver's license country code.

## `page` (type: `integer`):

1-indexed result page. Each page re-runs the full search-then-poll flow.

## `pageSize` (type: `integer`):

Number of items to fetch per page.

## `maxPolls` (type: `integer`):

Internal poll attempts before returning whatever's available.

## `currency` (type: `string`):

ISO 4217 currency code for returned prices.

## Actor input object example

```json
{
  "mode": "search",
  "cityId": "28",
  "sourceCountryCode": "GB",
  "age": 30,
  "licenseCountryCode": "GB",
  "page": 1,
  "pageSize": 10,
  "maxPolls": 8,
  "currency": "GBP"
}
```

# Actor output Schema

## `dataset` (type: `string`):

No description

# 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 = {
    "cityId": "28"
};

// Run the Actor and wait for it to finish
const run = await client.actor("romy/klook-car-rentals-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 = { "cityId": "28" }

# Run the Actor and wait for it to finish
run = client.actor("romy/klook-car-rentals-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 '{
  "cityId": "28"
}' |
apify call romy/klook-car-rentals-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,romy/klook-car-rentals-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/lAbQyNvz1yOik5B6i/builds/5lqboFAg2R1iY4PBW/openapi.json
