# Traveloka Review & Sentiment Extractor (`fanndev/traveloka-review-extractor`) Actor

Download Traveloka reviews in bulk from a hotel or Xperience URL: review text (original and translated), score, date, reviewer, photos, and aspect sentiment tags with counts. Ready for NLP. HTTP-only, no browser.

- **URL**: https://apify.com/fanndev/traveloka-review-extractor.md
- **Developed by:** [Faisal Ahdan naufal](https://apify.com/fanndev) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.40 / 1,000 results

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?

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

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

## How to integrate an Actor?

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

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

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

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

# README

## Traveloka Review & Sentiment Extractor

Give it a Traveloka URL, get back every review as structured JSON — ready to pipe
straight into sentiment analysis, a reputation dashboard, or a spreadsheet.

Works across Traveloka's product families from the same input box: hotels,
Xperience activities, bus operators, airport transfers and car rentals. The
product is detected from the URL path, so you can mix them in one run.

### What you get

**Per review** (`recordType: REVIEW`):

| Field | Notes |
| --- | --- |
| `text` | Review in your chosen locale |
| `originalText` | What the guest actually typed, before machine translation |
| `isMachineTranslated` | True when `text` came from Google Translate, not the guest |
| `score`, `scoreScale` | Guest score, normally out of 10 |
| `reviewedAt` | ISO-8601 UTC, parsed from Traveloka's millisecond timestamp |
| `reviewerName`, `reviewerStatus` | Partly masked by Traveloka; `ANONYMOUS` is common |
| `photoUrls`, `videoUrls`, `mediaCount` | Full-size guest media |
| `subRatings` | Per-aspect scores when the reviewer gave them |
| `helpfulCount` | How many people marked it helpful |
| `propertyReplyText` | The operator's public reply, when there is one |

Keeping `originalText` separate from `text` matters for NLP: scoring a machine
translation measures the translator as much as the guest.

**Per object** (`recordType: OBJECT_SUMMARY`), one row holding `objectName`,
`rating`, `ratingLabel`, `totalReviewCount`, `reviewsCollected`, and `ratingTags`
— Traveloka's aspect tags with counts, split into `positiveTagIds` and
`negativeTagIds`, which is the closest thing to sentiment the platform publishes
itself. Filter the dataset on `recordType` to separate the two shapes.

### Filtering and sorting

- **Sort** newest first, most helpful, or by rating in either direction. Lowest
  rating first is the fast way to do a complaint audit.
- **Rating bands** — exceptional (9+), very good (8+), pleasant (6+).
- **Format** — text only, media only, or both. Useful for dropping score-only
  ratings before NLP.
- **Aspect tags** — run once with no tag filter, read `ratingTags` off the
  summary record, then re-run filtered to `BAD_CLEANLINESS` or similar to read
  only the reviews behind one complaint.

### A note about `sort`

Traveloka's review API takes `sort` as a **bare string**. If you send it as an
object (`{"sortType": ...}`) the API answers HTTP 200 and quietly ignores it, so
the data looks sorted but is not. This actor always sends the string form; the
trap is documented here because it bites anyone rolling their own client.

### Xperience reviews work even though Xperience search does not

Traveloka's activity *search* and *detail* endpoints sit behind an AWS WAF
CAPTCHA and cannot be read headlessly. The **review** service does not — so if
you have an activity URL, this actor reads its reviews normally. What it cannot
do is discover activities for you; bring the URLs.

### Cost

Per object: 1 aggregate + 1 aspect-tag call + 1 count + one request per page of
reviews (20 per page by default). Pulling 200 reviews is about 13 requests.

`maxReviewsPerObject` is the stopping point; Traveloka pages until `hasNext` goes
false, and the actor de-duplicates by `reviewId` so an overlapping page cannot
inflate your counts.

### Output record types

- `REVIEW` — one guest review.
- `OBJECT_SUMMARY` — rating, totals and aspect tags for one object.
- `NOT_FOUND` — no reviews and no rating summary; usually the product type does
  not match the URL.
- `GATED` — an endpoint answered with an AWS WAF challenge.
- `ERROR` — a request failed; see `error`.

### A note on robots.txt

Traveloka's `robots.txt` disallows `/api/`, which is where this actor reads from.
Decide for yourself whether that fits your use of the data, your jurisdiction and
your agreement with Traveloka. Reviews are personal content written by real
people — keep request rates modest, and mind your obligations when you store or
republish them.

# Actor input Schema

## `startUrls` (type: `array`):

Traveloka hotel, Xperience activity, bus, airport-transfer or car-rental page URLs. The product family is detected from the URL path, so you can mix them freely.

## `objectIds` (type: `array`):

Bare numeric Traveloka object IDs. These use the Product type setting below, since there is no URL to infer it from.

## `productType` (type: `string`):

Which Traveloka product the IDs belong to. Only used for bare Object IDs and for URLs whose path cannot be classified.

## `maxReviewsPerObject` (type: `integer`):

How many reviews to download for each property or activity. Reviews arrive in pages, so this is also the stopping point for pagination.

## `sortBy` (type: `string`):

Order Traveloka returns reviews in. Newest first is best for monitoring; lowest rating first is best for a complaint audit.

## `ratingFilter` (type: `string`):

Keep only reviews in a rating band. Traveloka exposes bands, not exact score cut-offs.

## `formatFilter` (type: `string`):

Keep only reviews that have text, media, or both. Useful when you need photos, or when you want to drop score-only ratings before running NLP.

## `ratingTagIds` (type: `array`):

Keep only reviews carrying these aspect tags, for example GOOD\_STAFF\_FRIENDLINESS or BAD\_CLEANLINESS. Run once without this to see which tags an object has, listed in the summary record under ratingTags.

## `includeSummaryRecord` (type: `boolean`):

Emit one OBJECT\_SUMMARY row per property or activity holding the overall rating, total review count and aspect tags, alongside the individual reviews.

## `includeRatingTags` (type: `boolean`):

Collect the aspect tag aggregate (cleanliness, staff, location and so on) with counts, split into positive and negative tag IDs.

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

How many reviews to ask for per API call. Twenty matches what Traveloka's own web client asks for.

## `locale` (type: `string`):

Traveloka locale. This affects the language of aspect tag labels and rating labels, and which translation of a review text you receive.

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

Currency context for the session. Reviews carry no prices, so this rarely matters.

## `requestDelaySeconds` (type: `string`):

Seconds to pause between API calls. Raise it if you see repeated 429 or 5xx responses on large pulls.

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

Proxy settings. The review API answers datacenter IPs normally, so the default is fine.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.traveloka.com/id-id/hotel/indonesia/hotel-indonesia-kempinski-jakarta-2000000113168"
    }
  ],
  "productType": "hotel",
  "maxReviewsPerObject": 200,
  "sortBy": "SORT_CREATED_DESCENDING",
  "ratingFilter": "RATING_VALUE_ALL",
  "formatFilter": "FORMAT_VALUE_ALL",
  "includeSummaryRecord": true,
  "includeRatingTags": true,
  "pageSize": 20,
  "locale": "en-id",
  "currency": "IDR",
  "requestDelaySeconds": "0.4",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `reviewsView` (type: `string`):

No description

## `summariesView` (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 = {
    "startUrls": [
        {
            "url": "https://www.traveloka.com/id-id/hotel/indonesia/hotel-indonesia-kempinski-jakarta-2000000113168"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("fanndev/traveloka-review-extractor").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 = { "startUrls": [{ "url": "https://www.traveloka.com/id-id/hotel/indonesia/hotel-indonesia-kempinski-jakarta-2000000113168" }] }

# Run the Actor and wait for it to finish
run = client.actor("fanndev/traveloka-review-extractor").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 '{
  "startUrls": [
    {
      "url": "https://www.traveloka.com/id-id/hotel/indonesia/hotel-indonesia-kempinski-jakarta-2000000113168"
    }
  ]
}' |
apify call fanndev/traveloka-review-extractor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,fanndev/traveloka-review-extractor"
        }
    }
}
```

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/fy6aeq4HxJMuJRQ4k/builds/z3aVY123DKr7G7t3e/openapi.json
