# Klook All-in-One API (`romy/klook-all-in-one-api`) Actor

Unofficial always-on REST API for live Klook data: Home feed, category tree, search across every vertical, activity detail/reviews, car rentals, airport transfers, eSIM, dining, deals, destination guides & recently-viewed — in any currency. No account needed.

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

## Pricing

from $15.60 / 1,000 searches

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

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#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 All-in-One API do?

**Klook All-in-One API** is a REST endpoint for Klook's live travel data — the Home tab's content feed (Recommended, Nearby, seasonal rails), the full nested category tree, search with the app's **entire real filter panel** across every vertical (not just "Things to do"), activity detail/reviews/similar, **car rental search**, **airport transfer flight lookup**, **eSIM plans**, **Food & dining city info**, and **Sale-tab deals**. All from a single always-on API powered by [Apify Standby](https://docs.apify.com/platform/actors/development/programming-interface/standby).

It talks directly to the same internal APIs 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 — call the endpoint, get JSON back.

### Why use Klook All-in-One API?

- **Every vertical, not just activities** — confirmed live: the same signing scheme covers car rentals, airport transfers, eSIM, dining, and deals, each a genuinely separate backend subsystem, not a re-skin of the activity search
- **Real car rental search, not a stub** — a full search-then-poll flow (matching the app's own real behavior) returning real, bookable cars with real suppliers and prices
- **The whole Home tab** — Recommended, Nearby (location-based), and whatever seasonal rail Klook is currently running, discoverable live via `/home/feed/tabs` rather than hardcoded
- **The complete category tree** — `/categories/tree` mirrors the app's "All" screen exactly, all the way down (e.g. Transport's rail passes split into Japan/Europe/Metro, not merged into one)
- **Every real search filter** — main category (own filter set per tab, confirmed live), branch category, dates, guided language, services, departure time, duration, minimum review score, price range, and more — every one confirmed live by tapping the actual control in the app
- **Real, working pagination** — confirmed live on the Home feed, search, and reviews, with real result counts
- **Always-on** — Standby mode means no cold start, responds in milliseconds
- **No account needed** — every endpoint works fully anonymously
- **Use cases:** travel content aggregation, price/rating monitoring, activity discovery bots, review analysis, car rental price comparison

### Endpoints

| Method | Path                             | Description                                              |
| ------ | -------------------------------- | ----------------------------------------------------------- |
| `GET`  | `/categories/tree`                | Full nested category tree (the app's "All" screen)          |
| `GET`  | `/categories`                     | Home-tab quick-link categories ("More to explore")          |
| `GET`  | `/home/feed/tabs`                 | Live list of Home-tab content-feed tabs                     |
| `GET`  | `/home/feed`                      | Content for one Home-tab feed rail (Recommended, Nearby, ...) |
| `GET`  | `/home/nearby`                    | Home tab's "Nearby gems" widget                              |
| `GET`  | `/home/nearby/more`               | Full nearby-activities list behind /home/nearby's "See more" |
| `GET`  | `/home/where-to-next`             | Home tab's "Where to next?" destination suggestions          |
| `GET`  | `/trips/inspiration`              | Trips tab's destination inspiration carousel                  |
| `GET`  | `/recently-viewed`                | Home tab's "Recently viewed" rail (device-scoped, self-populating) |
| `GET`  | `/destinations/{id}`              | A city's full destination guide                                |
| `GET`  | `/destinations/{id}/trending-sights` | A city's trending points of interest, with full descriptions |
| `GET`  | `/search/init`                    | Search screen's init state (top searches, trending, new)     |
| `GET`  | `/search/suggest`                 | As-you-type search suggestions for a partial query            |
| `GET`  | `/search`                         | Full-text search with the app's full filter panel — every vertical |
| `GET`  | `/search/filters`                 | Live filter schema (ids + labels) for a search query          |
| `GET`  | `/activities/{id}`                | Full activity/experience detail                              |
| `GET`  | `/activities/{id}/similar`        | Recommended/similar activities                               |
| `GET`  | `/activities/{id}/reviews`        | Customer reviews for one activity, with real filters          |
| `GET`  | `/activities/{id}/esim-packages`  | eSIM data plan options for an eSIM activity                   |
| `GET`  | `/esim/destinations`              | eSIM homepage: popular destinations                           |
| `GET`  | `/transport/car-rentals/countries`| Countries/cities available for car rentals                    |
| `GET`  | `/transport/car-rentals/locations`| Real pickup/drop-off locations for a city                     |
| `GET`  | `/transport/car-rentals/search-locations` | Free-text location search for car rentals              |
| `GET`  | `/transport/car-rentals/search`   | Search car rentals (real results, not a stub)                 |
| `GET`  | `/transport/airport-transfers/search` | Look up a flight for airport transfer booking              |
| `GET`  | `/dining/home`                    | Food & dining city info                                       |
| `GET`  | `/deals`                          | Sale-tab deals page for a country                              |
| `GET`  | `/deals/more`                     | Real pagination for the Deals page's promotions grid           |

### How to use Klook All-in-One API

1. Open this Actor's Standby API URL (shown on the Actor's page, under the API tab).
2. Call any endpoint — for example:
   ```bash
   curl "https://romy--klook-all-in-one-api.apify.actor/categories/tree"
   curl "https://romy--klook-all-in-one-api.apify.actor/home/feed/tabs"
   curl "https://romy--klook-all-in-one-api.apify.actor/search/suggest?query=osa"
   curl "https://romy--klook-all-in-one-api.apify.actor/search?query=tokyo&main_category=1&sort=price"
   curl "https://romy--klook-all-in-one-api.apify.actor/activities/93901"
   curl "https://romy--klook-all-in-one-api.apify.actor/activities/93901?currency=USD"
   curl "https://romy--klook-all-in-one-api.apify.actor/activities/93901/reviews?star_num=5"
   curl "https://romy--klook-all-in-one-api.apify.actor/esim/destinations"
   curl "https://romy--klook-all-in-one-api.apify.actor/deals?country_id=1012"
   curl "https://romy--klook-all-in-one-api.apify.actor/deals/more?country_id=1012&page=2"
   curl "https://romy--klook-all-in-one-api.apify.actor/trips/inspiration"
   curl "https://romy--klook-all-in-one-api.apify.actor/home/nearby/more?lat=-6.2884&lng=106.7183"
   curl "https://romy--klook-all-in-one-api.apify.actor/destinations/28"
   curl "https://romy--klook-all-in-one-api.apify.actor/destinations/28/trending-sights"
   curl "https://romy--klook-all-in-one-api.apify.actor/recently-viewed"
   # Car rentals: resolve a location first, then search
   curl "https://romy--klook-all-in-one-api.apify.actor/transport/car-rentals/locations?city_id=28"
   curl "https://romy--klook-all-in-one-api.apify.actor/transport/car-rentals/search-locations?q=Osaka"
   curl "https://romy--klook-all-in-one-api.apify.actor/transport/car-rentals/search?pickup_country_code=JP&pickup_location_code=HND&pickup_location_id=50036510&pickup_location_name=Tokyo%20Haneda%20International%20Airport&pickup_city_id=28&pickup_city_name=Tokyo&pickup_lng_lat=139.7783379,35.5495626&pickup_date_time=2026-08-27%2010:00&return_date_time=2026-08-30%2010:00"
   ```
   Every call needs your Apify API token, either as `Authorization: Bearer <token>` or `?token=<token>` — the Actor's page API tab has a ready-to-copy version with your token filled in.
3. Read the JSON response — no setup required.

### Parameters

**`GET /home/feed`** — `tab` required (get valid values from `/home/feed/tabs`, e.g. `1`=Recommended, `6`=Nearby).

| Param  | Type            | Example       | Meaning                                                                 |
| ------ | --------------- | ------------- | --------------------------------------------------------------------------- |
| `tab`  | string          | `1`           | A tab value from `/home/feed/tabs`                                          |
| `page` | int, default 1  | `1`           | 1-indexed page — real pagination                                            |
| `size` | int, default 10 | `10`          | Results per page                                                             |
| `lat`  | number          | `-6.2884`     | Required for location-dependent tabs like Nearby — confirmed live            |
| `lng`  | number          | `106.7183`    | See `lat`                                                                     |

**`GET /home/nearby`** — `lat`/`lng` required.

| Param   | Type             | Example    | Meaning                                                    |
| ------- | ---------------- | ---------- | -------------------------------------------------------------- |
| `lat`   | number           | `-6.2884`  | Latitude                                                        |
| `lng`   | number           | `106.7183` | Longitude                                                       |
| `limit` | int, default 12  | `12`       | Number of cards to return                                       |

**`GET /home/nearby/more`** — `lat`/`lng` required. Full list, not paginated — one call returns everything (confirmed live: 30 items in a single response).

**`GET /home/where-to-next`** — no required parameters.

| Param   | Type             | Example    | Meaning                                     |
| ------- | ---------------- | ---------- | ------------------------------------------------ |
| `lat`   | number           | `-6.2884`  | Narrows suggestions by location                   |
| `lng`   | number           | `106.7183` | Longitude                                          |
| `limit` | int, default 12  | `12`       | Number of destinations to return                    |

**`GET /search/suggest`** — `query` required (partial text, e.g. `osa`). Distinct from `/search/init`, which is the state before typing anything.

**`GET /search`** — `query` required. Every filter param below is real and was confirmed live — see [Data notes](#data-notes).

| Param             | Type            | Example          | Meaning                                                                             |
| ----------------- | --------------- | ----------------- | -------------------------------------------------------------------------------------- |
| `query`           | string          | `tokyo`            | Search text                                                                          |
| `start`           | int, default 1  | `1`                | 1-indexed result offset — real pagination                                            |
| `size`            | int, default 10 | `10`               | Results per page                                                                      |
| `sort`            | enum            | `price`            | `most_relevant` | `price` | `participants` | `publish_time` | `review_score`      |
| `date`            | string (date)   | `2026-08-28`       | A single date — confirmed live this is one day, not a real range                     |
| `main_category`   | string          | `1`                | `1`=Tours & experiences, `2`=Attraction tickets, `30`=Transport, `54`=Hotels, `70`=More |
| `branch_category` | string          | `14`               | Sub-category id — see `/categories/tree` or `/search/filters`                        |
| `guided_language` | string          | `3029`             | Tour guide language id (`3029`=English guided)                                        |
| `services`        | comma-separated | `3044,3013`        | Small group, hotel pick-up, free cancellation, guaranteed departure, ...              |
| `departure_time`  | comma-separated | `54883`            | Evening/afternoon departure                                                          |
| `duration`        | comma-separated | `7959`             | 0-3 hours, 3-5 hours, full day, 2+ days, ...                                          |
| `review_score`    | string          | `4.5`              | Minimum score — `3`, `3.5`, `4`, or `4.5`                                            |
| `price_min`       | number          | `0`                | Minimum price                                                                        |
| `price_max`       | number          | `100`              | Maximum price                                                                        |
| `other`           | comma-separated | `other_instant_key`| `other_instant_key`=Instant confirmation, `36822`=Halal-friendly                     |
| `location`        | string          | `1012`             | Narrow results to a location id (`1012`=Japan)                                       |
| `currency`        | string, default `GBP` | `USD`        | ISO currency code for every result's price — confirmed live, see [Data notes](#data-notes) |

**`GET /search/filters`** — `query` required.

| Param           | Type   | Example | Meaning                                                                                  |
| --------------- | ------ | ------- | -------------------------------------------------------------------------------------------- |
| `query`         | string | `tokyo` | Same search text as `/search`                                                                |
| `main_category` | string | `1`     | Get the filter set for one tab instead of the merged All-tab set — the set genuinely differs |

**`GET /activities/{id}`**, **`GET /activities/{id}/esim-packages`** — path param `id` required. Both also take `currency` (string, default `GBP`) — confirmed live to change every returned price.

**`GET /activities/{id}/similar`**

| Param      | Type                   | Example | Meaning                                       |
| ---------- | ---------------------- | ------- | ---------------------------------------------- |
| `length`   | int, default 8         | `8`     | Number of recommendations to return            |
| `currency` | string, default `GBP`  | `USD`   | ISO currency code — confirmed live to change each card's price |

**`GET /activities/{id}/reviews`**

| Param        | Type            | Example | Meaning                                                    |
| ------------ | --------------- | ------- | -------------------------------------------------------------- |
| `page`       | int, default 1  | `1`     | 1-indexed page — real pagination                                |
| `limit`      | int, default 10 | `10`    | Reviews per page                                                |
| `star_num`   | int 1-5         | `5`     | Filter to one star rating — confirmed real                      |
| `only_image` | boolean         | `true`  | Only reviews with a photo — confirmed real                      |

**`GET /transport/car-rentals/locations`** — `city_id` required (from `/transport/car-rentals/countries`).

**`GET /transport/car-rentals/search-locations`** — `q` required (free-text, e.g. `Osaka`); `source_country_code` optional, default `GB`.

**`GET /transport/car-rentals/search`** — every `pickup_*` field required, resolved from `/transport/car-rentals/locations`.

| Param                  | Type   | Example                              | Meaning                                              |
| ---------------------- | ------ | ------------------------------------- | --------------------------------------------------------- |
| `pickup_country_code`  | string | `JP`                                   | From a location's `country_code`                          |
| `pickup_location_code` | string | `HND`                                  | From a location's `iata_code`                              |
| `pickup_location_id`   | string | `50036510`                             | From a location's `poi_id`                                 |
| `pickup_location_name` | string | `Tokyo Haneda International Airport`   | From a location's `poi_name`                                |
| `pickup_city_id`       | string | `28`                                   |                                                             |
| `pickup_city_name`     | string | `Tokyo`                                |                                                             |
| `pickup_lng_lat`       | string | `139.7783379,35.5495626`               | `"longitude,latitude"` from a location                     |
| `pickup_date_time`     | string | `2026-08-27 10:00`                     | `"YYYY-MM-DD HH:mm"`                                        |
| `return_date_time`     | string | `2026-08-30 10:00`                     |                                                             |
| `return_*`             |        |                                        | Optional — omit all to return to the same pickup location   |
| `age`                  | int, default 30 | `30`                          | Driver's age                                                |
| `license_country_code` | string, default `GB` | `GB`                     |                                                             |
| `page`                 | int, default 1 | `2`                            | Real pagination — confirmed live: distinct pages return distinct cars |
| `page_size`            | int, default 10 | `10`                          |                                                             |
| `currency`             | string, default `GBP` | `USD`                  | ISO currency code — confirmed live to change `filter_group.currency` and every listed car's price |

**`GET /transport/airport-transfers/search`** — `flight_number`/`departure_date` required.

| Param            | Type              | Example        | Meaning                    |
| ---------------- | ----------------- | --------------- | -------------------------------- |
| `flight_number`  | string             | `CX123`          |                                   |
| `departure_date` | string (date)      | `2026-08-27`      |                                   |
| `direction`      | `arrival`|`departure`, default `arrival` | `arrival` |                    |

**`GET /dining/home`** — `city_id` required.

**`GET /deals`** — `country_id` required. `currency` optional (string, default `GBP`) — confirmed live to change coupons' `discount_currency` and related price fields.

**`GET /deals/more`** — `country_id` required. Real pagination — confirmed live: distinct `page` values return distinct activities.

| Param           | Type              | Example | Meaning                                       |
| --------------- | ----------------- | ------- | ---------------------------------------------------- |
| `page`          | int, default 1    | `2`     | 1-indexed page — real pagination, confirmed live       |
| `limit`         | int, default 10   | `10`    | Results per page                                       |
| `category_id`   | string, default `0` | `0`   |                                                        |
| `category_type` | string             |         |                                                        |
| `currency`      | string, default `GBP` | `USD` | ISO currency code — confirmed live to change each card's `display_price` |

**`GET /destinations/{id}`**, **`GET /destinations/{id}/trending-sights`** — `id` required (path param): a city id, same as `city_id` elsewhere in this API (e.g. `28`=Tokyo, `29`=Osaka).

**`GET /recently-viewed`** — no required parameters. `limit` optional (int, default 50). `currency` optional (string, default `GBP`) — confirmed live to change each card's price.

**`GET /categories/tree`**, **`GET /categories`**, **`GET /home/feed/tabs`**, **`GET /esim/destinations`**, **`GET /transport/car-rentals/countries`**, **`GET /search/init`**, **`GET /trips/inspiration`** — no parameters.

### Output

Every response is returned directly over HTTP — this Actor does not write to an Apify dataset. Responses pass through Klook's own real (deeply nested) JSON data. Real responses (trimmed for readability):

`GET /transport/car-rentals/search?...` (Tokyo Haneda, 27–30 Aug):

```json
{
    "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 }
            ]
        }
    }
}
```

`GET /transport/airport-transfers/search?flight_number=CX123&departure_date=2026-08-27`:

```json
{ "success": true, "results": { "result": { "found": false, "flights": null } } }
```

`GET /esim/destinations` (trimmed):

```json
{ "success": true, "destinations": { "result": { "regions": [{ "name": "Japan", "activity_id": 109393 }] } } }
```

`GET /categories/tree` (trimmed to two sections):

```json
{
    "success": true,
    "tree": {
        "result": {
            "category": {
                "menus": [
                    {
                        "title": "Things to do",
                        "sub_menu_list": [
                            {
                                "title": "Tours & experiences",
                                "sub_menu_list": [{ "title": "Tours", "business_name": "9_Tours" }]
                            }
                        ]
                    },
                    {
                        "title": "Transport",
                        "sub_menu_list": [{ "title": "Japan rail passes", "business_name": "35_Japan trains" }]
                    }
                ]
            }
        }
    }
}
```

`GET /home/nearby?lat=-6.2884&lng=106.7183&limit=1`:

```json
{
    "success": true,
    "nearby": {
        "result": {
            "title": "Nearby gems in South Tangerang",
            "recommend_cards": [{ "data": { "title": "Up at Thamrin Nine Observatory Deck in Jakarta" } }]
        }
    }
}
```

`GET /home/where-to-next`:

```json
{
    "success": true,
    "next": {
        "result": {
            "title": "Where to next?",
            "items": [{ "data": { "title": "Tokyo", "sub_title": "1191 activities" } }]
        }
    }
}
```

`GET /search?query=tokyo&main_category=1&sort=price&size=1`:

```json
{
    "success": true,
    "results": {
        "result": {
            "search_result": {
                "total": 835,
                "cards": [
                    {
                        "data": {
                            "city_id": 28,
                            "title": "Tokyo Chopstick Making Experience in Ginza",
                            "deep_link": "https://www.klook.com/en-GB/activity/183178-tokyo-chopstick-making-experience-in-ginza/"
                        }
                    }
                ]
            }
        }
    }
}
```

`GET /activities/93901/reviews?star_num=1&limit=1`:

```json
{
    "success": true,
    "reviews": {
        "result": {
            "show_total": 33758,
            "score": 4.8,
            "item": [{ "author": "Klook User", "rating": 20, "content": "..." }]
        }
    }
}
```

### Data notes

- **Every endpoint requires Klook's own request signature (`x-signature`)**, computed with a from-scratch reimplementation of the algorithm the app's native signing library uses — reverse-engineered and validated byte-for-byte against real signatures captured from the live app. This Actor computes it itself, entirely server-side; nothing here depends on a live device or the native library at runtime.
- **The signing scheme covers far more than the activities API** — confirmed live across real car rental, airport transfer, eSIM, dining, and deals flows, watching the same HMAC construction apply to each. These are genuinely separate backend subsystems, not a re-skin of one API.
- **Car rental search mirrors the app's real two-step flow internally.** The app itself kicks off an async search (returns a job id) then polls for results repeatedly (confirmed live: ~10 polls before real results appear) — `/transport/car-rentals/search` does that polling server-side so callers get one clean response with real results, not a job id to manage.
- **`/categories/tree` matches the app's own "All" screen exactly**, verified section by section against a real screenshot. It only works with an empty request query string — replaying the app's own captured params gets rejected for reasons unrelated to the request signature (which is separately fully solved); the empty-param response is identical to what the app gets either way.
- **`/search/filters`'s `branch_category` options are query-scoped** — a narrow query silently omits categories with zero matching results (confirmed live: `query=tokyo` drops 3 of Attraction tickets' 12 categories). For category ids independent of any query, use `/categories/tree` instead.
- **Every `/search` filter param was confirmed live**, not guessed, by tapping the actual control in the app's Filters dialog and reading the resulting request off the wire. The filter set itself was confirmed to differ by `main_category` — Tours & experiences exposes 10 filter dimensions, Attraction tickets only 5.
- **`/search` genuinely covers every vertical** — confirmed live: `main_category=30` (Transport) returns real Tokyo subway/Shinkansen tickets, `main_category=54` (Hotels) returns real bookable listings, through the exact same endpoint as activities.
- **Pagination is real everywhere it's offered** — `/home/feed`'s page/size, `/search`'s start/size, `/activities/{id}/reviews`'s page, `/transport/car-rentals/search`'s page/page\_size, and `/deals/more`'s page/limit all confirmed by comparing results and observing genuinely different items (zero id overlap across pages, in every case tested).
- **Every other list-shaped endpoint was audited for pagination and confirmed to return everything in one call**, not paginated: `/activities/{id}/similar` (its `length` param already returns up to `total` in one shot — `length=20` against a `total=12` activity returned all 12), `/esim/destinations`, `/activities/{id}/esim-packages`, `/dining/home`, `/trips/inspiration` (fixed `total=5`), `/transport/car-rentals/countries` (every country, ~230, in one response), `/transport/car-rentals/locations`, and `/transport/car-rentals/search-locations`. `/home/nearby`'s own `limit` param is a one-shot size cap, not a page — its actual "see more" is the separate `/home/nearby/more` endpoint documented above. `/home/where-to-next`'s "See more" opens a plain klook.com webpage, not an API call at all.
- **Device identity is self-generated per Actor run**, matching how the real app behaves on first install — it's not validated against any device registry (confirmed: a freshly generated id works immediately with no prior registration).
- **`/search/suggest` and `/transport/car-rentals/search-locations` were both confirmed live** by typing a partial query into the app's real search box / pickup-location field and capturing the resulting request — `/search/suggest` returns "Osaka" with the matched substring highlighted for query `osa`; `/transport/car-rentals/search-locations` resolves `Osaka` straight to Kansai International Airport, same as what the app itself resolves to.
- **`/trips/inspiration` was confirmed live by opening the app's Trips tab**, a screen not otherwise reachable through Home or Search — the response includes a "Recently browsed" tag as one server-assigned tag among several (alongside e.g. "Maritime Heritage", "Island Gateway"), not a client-side label.
- **`/destinations/{id}` was confirmed live by tapping a city from `/search/init`'s Trending destinations rail** — it opens a dedicated city-guide screen, not `/search` with a city filter. Confirmed with two different cities: `dest_id=28` and `dest_id=29` round-trip to Tokyo and Osaka respectively, the same ids used as `city_id` everywhere else in this API.
- **`/destinations/{id}/trending-sights` is a separate, richer endpoint from `/destinations/{id}`**, confirmed live by tapping into a city's "Trending sights" card — a different backend subsystem (`cardinfocenterservicesrv`) from every other destination/home endpoint here, returning a full description per point of interest rather than just a title/image.
- **`/recently-viewed` is self-populating, confirmed live with a device identity that had never made a single request before**: calling `GET /activities/{id}` once was enough to make that exact activity show up in `/recently-viewed` on the very next call. There is no separate "mark as viewed" endpoint — viewing an activity's detail through this API is itself what registers it.
- **Leaf categories under `GET /categories/tree`'s "All" screen were spot-checked and confirmed to browse through the app's own internal search**, carrying the same category ids already exposed by `GET /search`'s `branchCategory`/`mainCategory` params — no separate landing page per category, so no new endpoint was needed here.
- **`currency` is a real, caller-controlled parameter on every price-returning endpoint, not just a display label.** It was hardcoded to `GBP` in every request this Actor makes; confirmed live it's read by Klook's backend and genuinely changes the returned price (not just a symbol swap) — `GET /activities/93901` returns `selling_price: "35.99"`/`currency: "GBP"` with the default, `"49.09"`/`"USD"` with `currency=USD`, and `"822994"`/`"IDR"` with `currency=IDR`. It isn't part of the request signature (only `path`+`query`/body is signed), so passing any ISO code is always safe. Exposed on: `/search`, `/activities/{id}`, `/activities/{id}/similar`, `/activities/{id}/esim-packages`, `/transport/car-rentals/search`, `/deals`, `/deals/more`, `/recently-viewed`. Every other endpoint (`/dining/home`, `/destinations/{id}`, `/transport/airport-transfers/search`, etc.) was checked and confirmed to carry no price field at all, so `currency` was deliberately left off rather than added as dead weight.
- **All outbound requests route through Apify's residential proxy pool, not the Actor container's own IP.** Confirmed live: a request byte-for-byte identical to one that succeeds from a non-datacenter network got back a bare, empty-body HTTP 403 when made directly from the deployed Standby container — an IP-reputation block at Klook's edge, not a signing or logic issue. One proxy session per device identity keeps a stable IP for the run, same as a real device would have.

### Pricing

Pay-per-event, billed on each successful call. See the Actor's Pricing tab for current rates.

### Known limitations

- **No booking/pricing-calendar endpoint for activities.** Klook protects activity schedule/availability and package-pricing endpoints with an additional bot-detection layer (DataDome) that returns a JS challenge page instead of JSON, even with a fully valid request signature — confirmed by testing repeatedly with correct signatures, across several related internal endpoints. `/activities/{id}` still returns the activity's base price and discount info from its own detail payload.
- \~~No dedicated "recently viewed" endpoint found.~~ **Correction: found in a later pass.** An earlier version of this README reported no such endpoint after investigating multiple screens without triggering the right request. It was found by actually viewing an item in the app first, then tapping "See more" — see `GET /recently-viewed` above. `/trips/inspiration`'s "Recently browsed" tag (one tag among several on a generic recommendation carousel) is unrelated and still not a reliable per-user history signal on its own.
- **Several Account-tab and cart features require a logged-in session and were not built**, confirmed live by testing each unauthenticated: Wishlist, My bookings, price-drop alerts (return a generic server error with no session), the referral-program details endpoint, and the shopping cart. The notification bell redirects straight to the login screen. This Actor only wraps guest-accessible endpoints — no login flow is implemented or planned, by design.
- **Help Centre, currency, and language settings aren't wrapped.** Currency/language are bundled client-side data with no API call behind them (confirmed live: switching currency fires no signed request). Help Centre's FAQ content loads with no signed request in the capture either, suggesting a separate (likely third-party) support backend outside this Actor's cracked signing scheme — same category as the WebView-based Events & shows gap above.
- **No dedicated restaurant search/listing endpoint — root-caused to DataDome, not the request signature.** `/dining/home` (city info) is confirmed working with no session state at all. The app's own restaurant-listing call is protected by the same bot-detection layer as the blocked activity endpoints above — the real app's requests to it carry a session token and cookie sourced from a periodic device-check call this Actor doesn't replicate, and replaying the exact captured token/cookie pair standalone still gets rejected (these tokens are evidently bound to the originating device/network, not just present-or-absent). Solving it would mean implementing that bot-detection layer's own device-fingerprint challenge-response protocol — a separate reverse-engineering effort from the request-signing scheme this Actor already cracked.
- **No dedicated hotel detail/booking endpoint — confirmed to be a wholly separate network stack, not just a different signature.** `/search`'s Hotels results (`main_category=54`) are real, but the dedicated hotel search-and-book flow (star ratings, distance-from-station, per-night rates, room selection) never calls any function this Actor's cracked signing scheme covers — confirmed live by monitoring every native signing function this Actor's cracked scheme covers and watching zero requests fire while using that flow. A deeper pass confirmed the hotel flow runs on a wholly separate HTTP engine from every other verified vertical, and doesn't merely sign differently — it bypasses this Actor's own traffic-capture setup entirely, consistent with a separately configured network stack talking directly to its own backend. Hotel images come from a well-known third-party hotel-booking CDN, so this is very likely a separate white-labeled partner integration with its own authentication and its own network stack — cracking it would be a second reverse-engineering effort on the scale of the original, not a variation on the signing scheme already solved here.
- **"Events & shows" (concerts, festivals, exhibitions) isn't covered.** Tapping it in the app opens an embedded WebView loading the public klook.com website rather than calling a signed app API — confirmed live, no API request fires at all. That's a fundamentally different integration (scraping a public webpage vs. wrapping the app's own API), so it's out of scope here.
- **Transport sub-verticals shown in the app's own UI as separate search forms** (trains, buses, rail passes, metro passes, car charters, ferries, cruises) turned out, on inspection, to just be pre-filtered activity listings — already fully covered by `/search` with the right `main_category`/`branch_category`, not separate systems needing their own endpoints. Only car rentals and airport transfers turned out to be genuinely separate booking flows, and both are covered above.
- **The Trips tab's chatbot trip-planner (beyond the inspiration carousel) requires login** — the only guest-reachable part is `/trips/inspiration`; typing a preference/question into a planner UI wasn't reachable without logging in. A related feature-flag endpoint (an in-destination AI chat entry point) was found and confirmed live, but returned `show_entrance: false` for this session — a real endpoint currently gated off, not wrapped since it returns no usable content in that state.
- **Home feed cards were spot-checked for non-activity content and found to be activities only** — no separate article/blog-detail endpoint exists; every card on every rail already covered by `/home/feed` is a bookable activity, confirmed live while scrolling multiple rails.
- This is an unofficial, reverse-engineered integration, not affiliated with or endorsed by Klook Travel Technology Limited. Behavior may change if Klook changes its API or signing scheme.

Found a bug or have a feature request? Use the Issues tab on this Actor's page.

# Actor input Schema

## Actor input object example

```json
{}
```

# Actor output Schema

## `api` (type: `string`):

This Actor doesn't write to a dataset — every response is returned directly over HTTP by its Standby web server. See the README / web server OpenAPI schema (webServerSchema) for the full endpoint list and response shapes (GET /search).

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("romy/klook-all-in-one-api").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("romy/klook-all-in-one-api").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 '{}' |
apify call romy/klook-all-in-one-api --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,romy/klook-all-in-one-api"
        }
    }
}

```

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/2fCdoJK7gsldWSP6L/builds/wQgXFdC1xVfQxLaH9/openapi.json
