# Malaysia & Singapore Open Data API (OpenDOSM, BNM, data.gov.sg) (`thriving_adder/sea-open-data`) Actor

Official open data of Malaysia and Singapore as clean JSON: data.gov.my, OpenDOSM (CPI, GDP, labour), Bank Negara Malaysia (exchange rates, OPR) and data.gov.sg, with the licence attribution text on every record.

- **URL**: https://apify.com/thriving_adder/sea-open-data.md
- **Developed by:** [Adrian LHH](https://apify.com/thriving_adder) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.50 / 1,000 records

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## Malaysia & Singapore Open Data API (data.gov.my, OpenDOSM, BNM, data.gov.sg)

**One Actor for the official open data of Malaysia and Singapore.** Pull any dataset from **data.gov.my**, **OpenDOSM** (Department of Statistics Malaysia), **Bank Negara Malaysia OpenAPI** (exchange rates, OPR, interbank rates, Kijang Emas...) and **data.gov.sg** as clean JSON rows, each with the **publisher, licence and the attribution text the licence requires**. **$0.50 per 1,000 records.**

### What does it do?

- Fetches datasets by id with filters, sorting and limits, across four official government APIs, in one run and one schema.
- Every row carries `source`, `datasetId`, `datasetName`, the `record` itself, dataset metadata (last updated, frequency, coverage, publisher) and `licence`, `licenceUrl`, `attribution`, `sourceUrl`.
- An `ATTRIBUTION` record in the key-value store lists the attribution notice for every dataset used, ready to paste into your app, report or dashboard.
- Rate limits of each portal are respected automatically (data.gov.my: 4 requests/minute; data.gov.sg: 4 per 10 s without key).
- Two views: **Records** and **Licence & attribution**.

### Input

| Field | Default | What it does |
|---|---|---|
| `datasets` | required | List of `{ "source", "id", ... }` (max 200). Shorthand strings such as `"opendosm:cpi_headline"` work too. |
| `maxRecordsPerDataset` | `100` | Per dataset unless the item sets its own `limit` (max 100,000). |
| `flattenNested` | `true` | Flatten nested objects (e.g. BNM rate tables) into `a_b` columns. |
| `excludePersonalDataColumns` | `true` | Drop columns that look personal (names, IC/NRIC, e-mail, phone, address of persons); listed in `droppedColumns`. |
| `dataGovSgApiKey` | – | Optional data.gov.sg API key (secret) for higher rate limits. |

Per source:

- **`data.gov.my`** and **`opendosm`**: `id` from the catalogue (e.g. `fuelprice`, `population_malaysia`, `cpi_headline`, `gdp_qtr_real`, `lfs_month`); optional `filter`, `ifilter`, `contains`, `icontains`, `range`, `sort` (e.g. `-date`), `date_start`, `date_end`, `include`, `exclude`.
- **`bnm`**: one of `exchange-rate`, `opr`, `interest-rate`, `interest-volume`, `islamic-interbank-rate`, `interbank-swap`, `kijang-emas`, `usd-interbank-intraday-rate`, `kl-usd-reference-rate`, `fx-turn-over`; optional `path` (`date/2026-10-01`, `year/2026/month/9`) and `query` (`{"session":"1200","quote":"rm"}`).
- **`data.gov.sg`**: dataset id like `d_8b84c4ee58e3cfc0ece0d773c8ca6abc`; optional `q`, `filters` object, `sort` (e.g. `month desc`).

```json
{
  "datasets": [
    { "source": "opendosm", "id": "cpi_headline", "filter": "overall@division", "sort": "-date", "limit": 12 },
    { "source": "bnm", "id": "exchange-rate" },
    { "source": "data.gov.my", "id": "fuelprice", "sort": "-date", "limit": 10 },
    { "source": "data.gov.sg", "id": "d_8b84c4ee58e3cfc0ece0d773c8ca6abc", "sort": "month desc", "limit": 50 }
  ]
}
```

### Output

Example rows (shortened):

```json
{
  "source": "bnm", "datasetId": "opr", "datasetName": "BNM opr",
  "record": { "year": 2026, "date": "2026-09-03", "change_in_opr": 0, "new_opr_level": 2.75 },
  "lastUpdated": "2026-09-03 15:03:15", "publisher": "Bank Negara Malaysia",
  "licence": "Terms of Use for BNM Datasets", "licenceUrl": "https://www.bnm.gov.my/terms-of-use-bnm-datasets",
  "attribution": "Source: Bank Negara Malaysia. Data and information are subject to Terms of Use for BNM Datasets (https://www.bnm.gov.my/terms-of-use-bnm-datasets). Retrieved 2026-10-27.",
  "sourceUrl": "https://api.bnm.gov.my/public/opr"
}
```

```json
{
  "source": "data.gov.sg", "datasetId": "d_8b84c4ee58e3cfc0ece0d773c8ca6abc",
  "datasetName": "Resale flat prices based on registration date from Jan-2017 onwards",
  "record": { "month": "2026-10", "town": "ANG MO KIO", "flat_type": "2 ROOM", "storey_range": "04 TO 06", "floor_area_sqm": "44.00", "resale_price": "290000" },
  "publisher": "Housing & Development Board", "licence": "Singapore Open Data Licence v1.0",
  "attribution": "Contains information from Resale flat prices based on registration date from Jan-2017 onwards accessed on 2026-10-27 from data.gov.sg which is made available under the terms of the Singapore Open Data Licence version 1.0 (https://data.gov.sg/open-data-licence)."
}
```

Datasets that fail are listed with `error` / `errorCode` (`INVALID_DATASET_SPEC`, `DATASET_NOT_FOUND`, `NO_RECORDS`, `RATE_LIMITED`, `TIMEOUT`, `HTTP_5xx`) and are **not charged**.

### How much does it cost?

| Event | Price |
|---|---|
| Record | $0.0005 |

- 1,000 records ≈ **$0.50**. A daily BNM exchange-rate snapshot ≈ $0.01.
- Apify's tiny run-start fee applies (512 MB default). Set **Maximum cost per run** to cap spending.
- Large data.gov.my/OpenDOSM pulls are limited by the portal's 4 requests per minute (each request returns many rows).

### Licences and attribution (please read)

The data is not ours. Each portal's licence applies to your use, and the `attribution` field gives you the required notice.
| Source | Licence | What you must do |
|---|---|---|
| data.gov.my / OpenDOSM | [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) | Credit the agency (e.g. "Source: Department of Statistics Malaysia, OpenDOSM"), link the licence and say the data was reformatted. |
| Bank Negara Malaysia | [Terms of Use for BNM Datasets](https://www.bnm.gov.my/terms-of-use-bnm-datasets) | State "Source: Bank Negara Malaysia" and "Data and information are subject to Terms of Use for BNM Datasets"; do not imply BNM endorsement; do not use BNM's logo. |
| data.gov.sg | [Singapore Open Data Licence v1.0](https://data.gov.sg/open-data-licence) | Show a notice with the dataset name, access date and licence link (the `attribution` text); do not imply endorsement. |

**This Actor is independent and not affiliated with or endorsed by the Government of Malaysia, DOSM, Bank Negara Malaysia, GovTech Singapore or any agency.** Records are passed through as published (numbers are not modified); the Actor only reshapes them into rows. BNM datasets that contain third-party bank rates (`base-rate`) or named entities (`consumer-alert`) are not offered.

### Personal data

Government open data is aggregate, but some tables can contain names or contact details. By default, columns that look personal (names of persons, IC/NRIC, e-mail, phone, personal address) are **dropped** and listed in `droppedColumns`; contact e-mails in dataset metadata are never returned. Switch off `excludePersonalDataColumns` only if you have a lawful basis under PDPA.

### Use it from code or an AI agent

```bash
curl -X POST "https://api.apify.com/v2/acts/thriving_adder~sea-open-data/run-sync-get-dataset-items" \
  -H "Authorization: Bearer YOUR_APIFY_TOKEN" -H 'Content-Type: application/json' \
  -d '{"datasets":["bnm:exchange-rate"]}'
```

Also available as a tool in **Apify's MCP server**.

### Use cases

- **Dashboards:** Malaysian CPI, inflation, GDP, fuel prices or the ringgit exchange rate in Looker Studio, Power BI or Google Sheets on a schedule.
- **Fintech and pricing apps:** BNM exchange rates, OPR and interbank rates with the required attribution text included.
- **Property research:** Singapore HDB resale prices and other data.gov.sg datasets.
- **AI agents:** let an agent fetch official statistics instead of guessing numbers.
- **Research and journalism** with licence-correct attribution for every figure.

### FAQ

**Where do I find dataset ids?** In the catalogue URL: data.gov.my/data-catalogue/`fuelprice`, open.dosm.gov.my/data-catalogue/`cpi_headline`, data.gov.sg/datasets/`d_...`.

**Problems or ideas (more countries: Indonesia, Thailand, Philippines)?** Open an issue in the Issues tab. We answer within 2 business days.

### Related Actors

By the same publisher, built on the same safe, pay-per-result design:

- [SEC EDGAR Filings & XBRL Financials](https://apify.com/thriving_adder/sec-edgar-filings-xbrl): US company filings and XBRL financials.
- [Academic Paper Search (OpenAlex, Crossref, arXiv)](https://apify.com/thriving_adder/academic-research-papers): research papers that use these statistics.
- [Web Page to Markdown & llms.txt Generator](https://apify.com/thriving_adder/web-to-markdown-llms-txt): convert public reports and pages into Markdown.

# Changelog

This Actor's version history is a separate document: https://apify.com/thriving_adder/sea-open-data/changelog.md

# Actor input Schema

## `datasets` (type: `array`):

Array of {"source", "id", ...}. Sources: "data.gov.my" (id from the data.gov.my catalogue, e.g. fuelprice, population_malaysia, exchangerates), "opendosm" (e.g. cpi_headline, cpi_core, gdp_qtr_real, lfs_month), "bnm" (exchange-rate, opr, interest-rate, interest-volume, islamic-interbank-rate, interbank-swap, kijang-emas, usd-interbank-intraday-rate, kl-usd-reference-rate, fx-turn-over; optional "path" such as "date/2026-10-01" or "year/2026/month/9", optional "query" {"session": "1200", "quote": "rm"}), "data.gov.sg" (id like d_8b84c4ee58e3cfc0ece0d773c8ca6abc; optional "q", "filters" object, "sort" e.g. "month desc"). Optional "limit" per dataset. data.gov.my/OpenDOSM also accept filter, ifilter, contains, icontains, range, sort (e.g. -date), date_start, date_end, include, exclude. Shorthand strings like "opendosm:cpi_headline" work too. Max 200 per run.

## `maxRecordsPerDataset` (type: `integer`):

Default cap per dataset when an entry has no "limit". Charged per record.

## `flattenNested` (type: `boolean`):

Turn nested objects into flat keys (BNM rate.buying_rate -> rate_buying_rate), which is easier for spreadsheets and agents.

## `excludePersonalDataColumns` (type: `boolean`):

Drop any column whose name looks like personal data (email, phone, NRIC/IC, passport, contact person, names, date of birth). Government open data is normally aggregated; this is a safety net. Dropped columns are listed per row and in SUMMARY.

## `dataGovSgApiKey` (type: `string`):

Your own free data.gov.sg API key for higher rate limits (keyless: 4 dataset calls per 10 s). Sent only to data.gov.sg.

## Actor input object example

```json
{
  "datasets": [
    {
      "source": "opendosm",
      "id": "cpi_headline",
      "filter": "overall@division",
      "sort": "-date",
      "limit": 6
    },
    {
      "source": "bnm",
      "id": "exchange-rate",
      "limit": 5
    },
    {
      "source": "data.gov.sg",
      "id": "d_8b84c4ee58e3cfc0ece0d773c8ca6abc",
      "sort": "month desc",
      "limit": 5
    }
  ],
  "maxRecordsPerDataset": 100,
  "flattenNested": true,
  "excludePersonalDataColumns": true
}
```

# Actor output Schema

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

One row per data record with source, dataset, the record itself, licence and attribution.

## `summary` (type: `string`):

Counts, error codes, dropped personal-data columns and request statistics.

# 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 = {
    "datasets": [
        {
            "source": "opendosm",
            "id": "cpi_headline",
            "filter": "overall@division",
            "sort": "-date",
            "limit": 6
        },
        {
            "source": "bnm",
            "id": "exchange-rate",
            "limit": 5
        },
        {
            "source": "data.gov.sg",
            "id": "d_8b84c4ee58e3cfc0ece0d773c8ca6abc",
            "sort": "month desc",
            "limit": 5
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("thriving_adder/sea-open-data").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 = { "datasets": [
        {
            "source": "opendosm",
            "id": "cpi_headline",
            "filter": "overall@division",
            "sort": "-date",
            "limit": 6,
        },
        {
            "source": "bnm",
            "id": "exchange-rate",
            "limit": 5,
        },
        {
            "source": "data.gov.sg",
            "id": "d_8b84c4ee58e3cfc0ece0d773c8ca6abc",
            "sort": "month desc",
            "limit": 5,
        },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("thriving_adder/sea-open-data").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 '{
  "datasets": [
    {
      "source": "opendosm",
      "id": "cpi_headline",
      "filter": "overall@division",
      "sort": "-date",
      "limit": 6
    },
    {
      "source": "bnm",
      "id": "exchange-rate",
      "limit": 5
    },
    {
      "source": "data.gov.sg",
      "id": "d_8b84c4ee58e3cfc0ece0d773c8ca6abc",
      "sort": "month desc",
      "limit": 5
    }
  ]
}' |
apify call thriving_adder/sea-open-data --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,thriving_adder/sea-open-data"
        }
    }
}
```

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/cOsorPnOdX1EsGF04/builds/iTvwKbSQtEazaOa8X/openapi.json
