# Mobile Plans Tracker: SIM-Only & Prepaid Plan Prices (`messnomore/mobile-plans-tracker`) Actor

Compare mobile plans, SIM-only and prepaid plans from carriers in Malaysia, Singapore and Australia: normalized monthly prices (also in USD), data in GB, 5G, contracts, and the price and data changes since your last run.

- **URL**: https://apify.com/messnomore/mobile-plans-tracker.md
- **Developed by:** [Wong Xian Qi](https://apify.com/messnomore) (community)
- **Categories:** E-commerce, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.50 / 1,000 plan rows

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

### What does Mobile Plans Tracker do?

**Mobile Plans Tracker** gives you the current **mobile plans, SIM-only plans and prepaid plans** of 12 carriers in
**Malaysia, Singapore and Australia** as one clean, comparable table, and tells you **what changed since your last
run**: price drops, price rises, new plans, removed plans, data changes and new promotions. Every plan is normalized
to the same fields (monthly price, price in USD, data in GB, 5G, contract, roaming), so telco plans with 28-day,
monthly and yearly cycles can finally be compared side by side.

The data comes from the carriers' own public plan pages, read once a week. Use it in Apify Console with no code, or
as a **telco pricing API** from your own scripts, dashboards and spreadsheets.

### Why use Mobile Plans Tracker?

Comparing phone plans by hand means opening a dozen carrier sites, decoding "$54 Pre-Paid Plan, now $15 for your
first recharge, 75GB on your first three recharges then 35GB", and doing it again every week to spot changes.
Mobile Plans Tracker does it for you:

- 📉 **Price change tracking**: each plan is labeled `price_down`, `price_up`, `new`, `removed`, `data_up`,
  `data_down`, `promo_changed` or `unchanged` compared with your previous run.
- 🧮 **Normalized prices**: the ongoing price per billing cycle, the same price per month (`priceMonthly`) and in USD,
  plus the sale or introductory price shown now (`promoPrice`) kept apart, so a first-month deal never hides the real
  price.
- 📶 **Comparable data allowances**: ongoing data in GB (1 GB = 1000 MB), bonus data kept apart in `promoDataGB`.
- ✅ **Checked against the page**: an AI model reads each page, then every plan is verified against the page text
  (the price with its currency sign, the data amount and the plan name). Plans that can't be verified are dropped
  instead of guessed.
- 🔔 **Only the changes**: switch on **Only output changed plans** and schedule a weekly run to get a short list of
  what moved in the market.
- ➕ **Your own carriers**: add up to 10 public plan pages of other carriers as custom URLs.
- 🤖 **Apify platform features**: run it on a schedule, call it by API, or send the results to Slack, email, Google
  Sheets, Make or Zapier with [integrations](https://docs.apify.com/platform/integrations).

Who uses it: **telco pricing and product teams** watching competitors, **comparison sites and affiliate publishers**
keeping plan tables current, **analysts and journalists** following mobile prices, and **people choosing a SIM-only
plan** who want one table instead of twelve tabs.

### Which carriers are covered?

| Country      | Carriers                                                       | Plan pages read                                     |
| ------------ | -------------------------------------------------------------- | --------------------------------------------------- |
| 🇲🇾 Malaysia  | Tune Talk, redONE, U Mobile                                    | prepaid and postpaid (U Mobile: prepaid data plans) |
| 🇸🇬 Singapore | SIMBA, Circles.Life, Zero1                                     | SIM-only and prepaid                                |
| 🇦🇺 Australia | amaysim, Coles Mobile, Telstra, Vodafone, Felix Mobile, Lebara | SIM-only and prepaid                                |

About 120 plans in total. Each carrier was checked before it was added: its robots.txt allows the plan pages, its
website terms don't forbid automated reading, and the pages are read with an honest bot User-Agent, once a week.
Carriers whose terms forbid automated access are not included, and their sites are refused as custom URLs too.

### What data does Mobile Plans Tracker return?

| Field                                                             | Description                                                                            |
| ----------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
| `country`, `carrier`, `carrierId`                                 | Where the plan is sold and by whom                                                     |
| `planName`, `type`                                                | The carrier's plan name; `postpaid`, `prepaid` or `sim_only`                           |
| `price`, `billingCycle`                                           | Ongoing price for one cycle, for example `58` per `1 month` or `30` per `28 days`      |
| `promoPrice`                                                      | Sale or introductory price shown now, if any                                           |
| `priceMonthly`, `priceMonthlyUsd`                                 | Ongoing price for one month (30.44 days), in the plan's currency and in USD            |
| `currency`                                                        | ISO code: MYR, SGD, AUD                                                                |
| `dataGB`, `promoDataGB`, `dataUnlimited`                          | Ongoing full-speed data per cycle, data shown now including bonus data, unlimited data |
| `unlimitedCalls`, `fiveG`, `contractMonths`                       | Unlimited national calls, 5G access, minimum contract (0 = none)                       |
| `roaming`, `promo`                                                | Short summaries of included roaming and the current promotion                          |
| `change`, `previousPrice`, `previousPromoPrice`, `previousDataGB` | What changed since `comparedTo` and the previous values                                |
| `comparedTo`, `comparedToDate`                                    | `previous_run` (your last run) or `weekly_snapshot` (the previous weekly check)        |
| `sourceUrl`, `scrapedAt`                                          | The carrier page and when it was last read successfully                                |

Also included: `planKey` (a stable plan ID), `source` (`preset` or `custom`) and `carrierStatus` (`stale` when a
carrier page couldn't be read at the last check, so its plans come from an earlier check; for custom URLs, from your
previous run).

### How to compare mobile plans with Mobile Plans Tracker

1. Click **Try for free** and open the **Input** tab.
2. Pick **Countries** (or specific **Carriers**) and the **Plan types** you care about.
3. Keep **Compare with my previous run** on. Switch on **Only output changed plans** if you only want the changes.
4. Click **Start**. A run takes well under a minute for the preset carriers.
5. Open the **Output** tab: the **Overview** view shows prices and changes, **Compare plans** shows the features.
   Export to CSV or Excel, or schedule the Actor weekly to get every change as it happens.

### How much does it cost to track mobile plans?

Mobile Plans Tracker uses **pay-per-event pricing**:

- **$0.05 per carrier** with plans in your output, and
- **$0.005 per plan** in the output,
- plus a start fee of $0.00005 per run. There is no charge for compute time.

| Run                                                          | Carriers | Plans | Cost            |
| ------------------------------------------------------------ | -------- | ----- | --------------- |
| Australia, all plans                                         | 6        | ~56   | about **$0.58** |
| All 12 carriers, all plans                                   | 12       | ~122  | about **$1.21** |
| All 12 carriers, only changed plans (10 changes, 4 carriers) | 4        | 10    | about **$0.25** |
| All 12 carriers, only changed plans, nothing changed         | 0        | 0     | **$0.00005**    |

- With **Only output changed plans**, carriers without changes are not charged, so a quiet week costs almost nothing.
- **Maximum plans in the output** caps the number of rows, and Apify stops charging when a run reaches the maximum
  cost you set. Carriers are charged one at a time together with their plans, so a low maximum cost never pays for a
  carrier without at least one of its plans.
- Custom URLs that can't be read are not charged. A run that fails before delivering anything charges nothing; if it
  fails midway, only the rows already delivered were charged.

Apify's free plan includes monthly platform credit, which covers several runs.

### Input

See the **Input** tab for every option. Example input:

```json
{
    "countries": ["MY", "SG"],
    "planTypes": ["prepaid", "sim_only"],
    "compareWithPrevious": true,
    "onlyChanged": false,
    "maxPlans": 1000
}
```

- **Carriers** overrides **Countries**: pick carriers to check just those.
- **Custom carrier URLs**: public plan pages of other carriers. One site counts as one carrier (up to 3 pages per
  site). To check only your URLs, empty **Countries**.

### Output

You can download the dataset in various formats such as **JSON, HTML, CSV or Excel**. Example results:

```json
[
    {
        "country": "AU",
        "carrierId": "au-vodafone",
        "carrier": "Vodafone",
        "source": "preset",
        "carrierStatus": "ok",
        "planName": "Small Plan",
        "planKey": "au-vodafone:sim_only:small-plan:30d",
        "type": "sim_only",
        "price": 58,
        "promoPrice": 45,
        "billingCycle": "1 month",
        "priceMonthly": 58,
        "priceMonthlyUsd": 40.47,
        "currency": "AUD",
        "dataGB": 65,
        "promoDataGB": null,
        "dataUnlimited": false,
        "unlimitedCalls": true,
        "fiveG": true,
        "contractMonths": 0,
        "roaming": "Use your included data, calls and texts in over 100 destinations for $5 extra a day.",
        "promo": "Save $13/mth for the first 12 months; $45/mth for first 12 months then $58/mth. New connections only. Ends 29/10/26",
        "change": "unchanged",
        "previousPrice": 58,
        "previousPromoPrice": 45,
        "previousDataGB": 65,
        "comparedTo": "weekly_snapshot",
        "comparedToDate": "2026-10-14T08:33:20.920Z",
        "sourceUrl": "https://www.vodafone.com.au/mobile/sim-only-phone-plans",
        "scrapedAt": "2026-10-14T08:39:27.981Z"
    },
    {
        "country": "SG",
        "carrierId": "sg-circles",
        "carrier": "Circles.Life",
        "source": "preset",
        "carrierStatus": "ok",
        "planName": "Circles Plus",
        "planKey": "sg-circles:sim_only:circles-plus:30d",
        "type": "sim_only",
        "price": 18,
        "promoPrice": 10.99,
        "billingCycle": "1 month",
        "priceMonthly": 18,
        "priceMonthlyUsd": 14.06,
        "currency": "SGD",
        "dataGB": 300,
        "promoDataGB": null,
        "dataUnlimited": false,
        "unlimitedCalls": true,
        "fiveG": true,
        "contractMonths": 0,
        "roaming": "Asia roaming (Malaysia, Indonesia, Thailand, Taiwan, Hong Kong, Bangladesh, Sri Lanka)",
        "promo": "Promotion: $10.99 for the first month, reverts to $18 after",
        "change": "unchanged",
        "previousPrice": 18,
        "previousPromoPrice": 10.99,
        "previousDataGB": 300,
        "comparedTo": "weekly_snapshot",
        "comparedToDate": "2026-10-14T08:33:20.920Z",
        "sourceUrl": "https://www.circles.life/sg/plans/",
        "scrapedAt": "2026-10-14T08:39:27.981Z"
    }
]
```

A `SUMMARY` record in the run's key-value store lists every carrier checked, the number of each change type, skipped
custom URLs with the reason, and the carriers charged.

### Use Mobile Plans Tracker as an API

```bash
curl -X POST "https://api.apify.com/v2/acts/messnomore~mobile-plans-tracker/run-sync-get-dataset-items" \
  -H "Authorization: Bearer YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"countries": ["AU"], "planTypes": ["sim_only"], "onlyChanged": true}'
```

See the **API** tab for clients in JavaScript and Python. You can also run it from AI assistants through the
[Apify MCP server](https://docs.apify.com/platform/integrations/mcp).

### Tips for better results

- **Schedule it weekly** (the data is refreshed once a week) with **Only output changed plans** on, and connect an
  integration to get the changes by email or in Slack.
- Compare plans with different cycles by `priceMonthly` or `priceMonthlyUsd`, not by `price`.
- Use `planKey` to join runs or to keep your own history.
- To start the comparison over, delete the key-value store named `mobile-plans-tracker` in your Apify account
  (Storage → Key-value stores). The next run then compares with the previous weekly check.

### Related guides

- [How do you compare and track SIM-only and prepaid plan prices in Malaysia, Singapore and Australia?](https://wong13977-oss.github.io/mobile-plan-price-tracker/)

### FAQ

#### How are the plans extracted and checked?

Once a week, the carriers' public plan pages are read (with a headless browser where a page needs it) and an AI
model (OpenAI) lists the plans on each page. Each plan is then checked against the page text: the price must appear
with its currency sign, the data amount and the plan name must be on the page, and the price must sit near the plan.
Plans that fail these checks are dropped. If a page suddenly shows far fewer plans than before, the previous plans
are kept until the next check confirms it, so a broken page doesn't show up as dozens of removed plans.

#### How does the change detection work?

Your run saves the plans it delivered, per carrier and plan type, in a key-value store named `mobile-plans-tracker`
in your own Apify account. Your next run compares with it. Your first run (or a carrier or plan type you haven't
checked before) compares with the previous weekly check instead, and `comparedTo` tells you which one was used. The
ongoing price is compared first, then the data allowance, then the promotion price and bonus data. A carrier checked
with a filter, for example prepaid only, never hides the changes of its other plan types from your next run, and rows
left out by **Maximum plans** or your maximum cost are not saved, so your next run reports those changes again.

#### What is the difference between `price` and `promoPrice`?

`price` is what the plan costs once any sale or introductory offer ends. `promoPrice` is the lower price the carrier
shows now (for example "$15 for your first recharge"), or null when there is no such offer. `priceMonthly` is based
on `price`. Bonus data works the same way: `dataGB` is the ongoing allowance and `promoDataGB` includes the bonus.

#### How fresh is the data?

The preset carriers are checked once a week. `scrapedAt` shows when each plan's page was last read successfully.
Custom URLs are read live during your run.

#### What about custom carrier URLs?

Custom URLs are read during your run with plain HTTP and extracted with the same AI and checks. They must be public
plan pages that show their plans without JavaScript, and their robots.txt must allow reading them. Login, checkout,
account and private network addresses are refused, and so are sites whose terms forbid automated reading. Up to 10
URLs per run (3 on the Apify free plan), up to 3 pages per site. If one page of a site can't be read, its plans from
your previous run are kept and the carrier is marked `stale`; a site with no readable page is skipped and not
charged.

#### Why did I get fewer plans than expected?

Check the `SUMMARY` record. Common reasons: **Only output changed plans** is on, **Maximum plans** or your maximum
cost per run was reached, a plan type filter is set, or a custom URL was skipped (the reason is listed). Plans that
the page text doesn't support are dropped on purpose.

#### Is my input stored or shared?

Custom URLs and their page text are sent to the OpenAI API only to extract the plans. Like every Apify run, your
input, results and the comparison store stay in your own Apify account, and you can delete them at any time.

#### Is it legal to collect mobile plan data?

The Actor reads only public plan pages that carriers publish for customers, at most once a week for the preset
carriers. It respects robots.txt, never logs in and never opens order, checkout or account pages. Mobile plans are
product and price information, not personal data. Our Actors are ethical and do not extract any private user data,
such as email addresses, gender, or location. They only extract what the user has chosen to share publicly. We
therefore believe that our Actors, when used for ethical purposes by Apify users, are safe. However, you should be
aware that your results could contain personal data. Personal data is protected by the GDPR in the European Union
and by other regulations around the world. You should not scrape personal data unless you have a legitimate reason
to do so. If you're unsure whether your reason is legitimate, consult your lawyers.

#### Known limitations

- U Mobile shows its postpaid prices only after clicks, so only its prepaid data plans are covered.
- Plan names follow the carriers' pages. If a carrier renames a plan, it appears as one removed and one new plan.
- Promotions with conditions (port-in only, first recharge, online only) are summarized in `promo`; read the
  carrier's terms before relying on them.

#### I found wrong or missing data. What can I do?

Open the **Issues** tab and include the carrier and plan. Feedback and requests for more carriers and countries are
welcome there too.

# Actor input Schema

## `countries` (type: `array`):

Check every preset carrier in these countries. Leave empty to check only the carriers or URLs below.

## `carriers` (type: `array`):

Check only these preset carriers (overrides Countries). Leave empty to use Countries.

## `planTypes` (type: `array`):

Postpaid, prepaid and SIM-only (no phone, no lock-in) plans.

## `compareWithPrevious` (type: `boolean`):

Label every plan as new, removed, price up/down, data up/down, promo changed or unchanged since your previous run. Your first run compares with the previous weekly check.

## `onlyChanged` (type: `boolean`):

Skip unchanged plans. You are charged only for the plans in the output, and carriers without changes are not charged.

## `carrierUrls` (type: `array`):

Public plan pages of other carriers, read with AI. One site counts as one carrier (up to 3 pages per site). Up to 10 URLs per run (3 on the free Apify plan). Pages must show their plans without JavaScript. Login, checkout and account pages are not supported.

## `maxPlans` (type: `integer`):

Caps the output (and your cost). The 12 preset carriers have about 120 plans in total.

## Actor input object example

```json
{
  "countries": [
    "MY",
    "SG",
    "AU"
  ],
  "carriers": [],
  "planTypes": [
    "postpaid",
    "prepaid",
    "sim_only"
  ],
  "compareWithPrevious": true,
  "onlyChanged": false,
  "carrierUrls": [],
  "maxPlans": 1000
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `summary` (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 = {
    "carrierUrls": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("messnomore/mobile-plans-tracker").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 = { "carrierUrls": [] }

# Run the Actor and wait for it to finish
run = client.actor("messnomore/mobile-plans-tracker").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 '{
  "carrierUrls": []
}' |
apify call messnomore/mobile-plans-tracker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,messnomore/mobile-plans-tracker"
        }
    }
}
```

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/xadKtr9o9kclRaC1N/builds/lTVLGgAKapUKKnl5e/openapi.json
