# Traveloka Ground Transport (Bus & Shuttle) Monitor (`fanndev/traveloka-ground-transport-monitor`) Actor

Monitor Indonesian intercity bus and shuttle routes on Traveloka: operator, fare and promo price, departure and arrival times, duration, seats left, pickup and drop-off points with coordinates, and facilities.

- **URL**: https://apify.com/fanndev/traveloka-ground-transport-monitor.md
- **Developed by:** [Faisal Ahdan naufal](https://apify.com/fanndev) (community)
- **Security:** Limited permissions
- **Stats:** 3 total users, 2 monthly users, 83.3% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.30 / 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 Ground Transport (Bus & Shuttle) Monitor

Intercity bus and shuttle departures from Traveloka as structured JSON — the
Southeast Asian ground-transport market that global scrapers tend to skip.

Give it a city pair and a date range; get every departure with operator, fare,
times, seats left and boarding points with coordinates.

### What you get

**Per departure** (`recordType: DEPARTURE`):

| Group | Fields |
| --- | --- |
| Operator | `operatorName`, `operatorId`, `fleetName`, `busType`, `productType` (BUS or SHUTTLE), `operatorRating`, `operatorReviewCount` |
| Fare | `fare`, `originalFare`, `discountPercent`, `childFare`, `infantFare`, `promoLabel`, `currency` |
| Schedule | `departureDate`, `departureTime`, `arrivalDate`, `arrivalTime`, `durationMinutes`, `timeZone` |
| Capacity | `seatCapacity`, `seatsAvailable`, `seatClass`, `seatLayout`, `availabilityStatus` |
| Boarding | `pickupPointName` + address + city + **latitude/longitude**, and the same for drop-off |
| Comfort | `facilities` (air conditioning, USB power, reclining seats, …) |

**Per route and date** (`recordType: ROUTE_SUMMARY`): `minFare`, `maxFare`,
`averageFare`, `operatorCount`, `totalSeatsAvailable`, `departuresOnPromo`, and
an `operators[]` breakdown with each operator's departure count, fare range and
rating. That one row is usually all a pricing analyst needs.

`originalFare` with `discountPercent` is the genuine promo signal: it only
appears when the operator is discounting off a published fare.

### The de-duplication that matters

Traveloka expands **every pickup point by every drop-off point**. A single
Jakarta–Bandung day returns around **9,500 inventory rows** and roughly 38 MB of
JSON, mostly the same physical departures repeated per boarding location.

`dedupeBy` handles this:

- **`trip`** (default) — one row per physical departure. This is what you want.
- **`none`** — every boarding option, if you are mapping pickup coverage.
- **`operator`** — one row per operator, time and class.

`maxDeparturesPerSearch` caps what is kept per route per date; the default of 200
comfortably covers a busy corridor after trip de-duplication.

### Routes

Write routes as `Jakarta-Bandung`. City names resolve to Traveloka's city codes,
which cover every terminal in that city — better for monitoring than pinning one
terminal. Terminal and landmark names work too if you want a specific boarding
point. Anything unresolvable is reported as `NOT_FOUND` rather than dropped.

Set `daysToMonitor` to sweep consecutive travel dates; one search per route per
day. Schedule the actor daily and the dataset becomes a fare and capacity history.

### What this actor does not cover

**Train is not included.** Traveloka's train inventory endpoint
(`/v2/train/search/inventoryv2`, domain `train`) is reachable and answers HTTP
200, but it returns "Kereta Tidak Tersedia" for every route, date, station-or-city
code and provider (`KAI` and `KCIC`) tested on 2026-09-20 — including Jakarta–
Yogyakarta, which plainly has daily service. Rather than ship a train mode that
silently returns nothing, the endpoint and its exact payload are documented in
`src/constants.py` so it can be switched on if Traveloka opens it.

One detail recorded there because it is easy to lose: the train API's date object
uses `date`, not `day` — the opposite of every other Traveloka endpoint.

**Airport transfer is not included** either; it uses a different search contract
(`/v2/pps/search/multileg`) built around location objects rather than city codes.

### Cost

One autocomplete lookup per distinct place name (cached across dates), then one
search per route per date. Bus responses are large, so the default delay between
requests is 0.6 seconds; raise it for long sweeps.

The bus API answers datacenter IPs normally, so the default Apify proxy is fine.

### Output record types

- `DEPARTURE` — one trip.
- `ROUTE_SUMMARY` — fares and operators for one route on one date.
- `NO_SERVICE` — Traveloka has no bus matching that route and date.
- `NOT_FOUND` — a place name could not be resolved.
- `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. Keep request rates modest and scrape only what you
need.

# Actor input Schema

## `routes` (type: `array`):

One route per line as ORIGIN-DESTINATION, using city names as Traveloka knows them, for example Jakarta-Bandung. Accepted separators: -, ->, > or the word 'to'.

## `origin` (type: `string`):

Single-route alternative to the Routes list: departure city, terminal or landmark.

## `destination` (type: `string`):

Single-route alternative to the Routes list: arrival city, terminal or landmark.

## `departureDate` (type: `string`):

First travel date to check, in YYYY-MM-DD. Defaults to seven days from today.

## `daysToMonitor` (type: `integer`):

How many consecutive travel dates to check per route, starting from the first departure date. One search per route per day.

## `adults` (type: `integer`):

Adult passengers. Affects which inventory Traveloka shows and the seat counts returned.

## `maxDeparturesPerSearch` (type: `integer`):

Cap on departures kept per route per date. Traveloka expands every pickup point by drop-off point combination, so one busy city pair can return several thousand rows; 200 is usually plenty.

## `dedupeBy` (type: `string`):

The same trip is listed once per pickup point. 'Trip' keeps one row per physical departure and is what you normally want; 'None' keeps every boarding option; 'Operator' keeps one row per operator, time and class.

## `productTypes` (type: `array`):

Keep only these product types, for example BUS or SHUTTLE. Leave empty for both.

## `includeDepartures` (type: `boolean`):

Emit one DEPARTURE row per trip with operator, fare, times, pickup and drop-off points and facilities.

## `includeRouteSummary` (type: `boolean`):

Emit one ROUTE_SUMMARY row per route and date with fare range, operator count and a per-operator breakdown.

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

Traveloka locale, which drives the language of facility and point labels.

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

Currency for all fares in the output.

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

Seconds to pause between searches. Bus responses are large, so keep this above half a second on long runs.

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

Proxy settings. Residential is the default: Traveloka's AWS WAF answers a captcha to the session bootstrap on most Apify datacenter IPs. If you pick a datacenter group, the actor tries it first and falls back to residential sessions when it is challenged.

## Actor input object example

```json
{
  "routes": [
    "Jakarta-Bandung"
  ],
  "daysToMonitor": 1,
  "adults": 1,
  "maxDeparturesPerSearch": 200,
  "dedupeBy": "trip",
  "includeDepartures": true,
  "includeRouteSummary": true,
  "locale": "en-id",
  "currency": "IDR",
  "requestDelaySeconds": "0.6",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `departuresView` (type: `string`):

No description

## `routesView` (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 = {
    "routes": [
        "Jakarta-Bandung"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("fanndev/traveloka-ground-transport-monitor").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 = {
    "routes": ["Jakarta-Bandung"],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("fanndev/traveloka-ground-transport-monitor").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 '{
  "routes": [
    "Jakarta-Bandung"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call fanndev/traveloka-ground-transport-monitor --silent --output-dataset

```

## MCP server setup

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

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/LklGk3eAvE2j4PGYW/builds/4lkDgZPFau4weoDf3/openapi.json
