Bank Negara Malaysia
BNM OpenAPI — Malaysia's OPR, ringgit rates, KLIBOR and reserves — a documented JSON API with no key
For Malaysian monetary data from the source — the Overnight Policy Rate, exchange rates, KLIBOR, interest rates, international reserves — with a clean, documented API.
Reach for it when…
For Malaysian monetary data from the source — the Overnight Policy Rate, exchange rates, KLIBOR, interest rates, international reserves — with a clean, documented API.
Not for: Malaysian CPI and GDP (the Department of Statistics Malaysia, OpenDOSM), Bursa Malaysia prices, regional comparisons (World Bank, BIS).
Units, revisions, traps
Units. Rates in percent; exchange rates in ringgit per unit of foreign currency (buying and selling, at 09:00, 12:00 and 17:00 sessions); reserves in US$ billions.
Revisions. Not revised.
- Three daily sessions for exchange rates — pick one (1700 is the closing session).
- The OPR endpoint returns only changes; a year with no decision returns an empty list.
- The versioned Accept header is mandatory.
Classic mistake: Averaging the buying and selling rates and calling it the mid-market rate for a specific time.
Three recipes
- Recipe 1 · OPR decisions by year
- Recipe 2 · Ringgit per dollar, closing session
- Recipe 3 · International reserves
Each recipe: Python · Excel · Sheets, with how to read the result. Python recipes run under pandas; recipe 1 is re-run by the weekly check where the source allows it.
How to use it in your own work
Copy the snippet, change the series id, read the result the way the footer says. Replace YOUR_…_KEY with your own key where one is needed.
Recipe 1OPR decisions by year
import requests, pandas as pd
H = {"Accept": "application/vnd.BNM.API.v1+json"}
rows = []
for y in range(2020, 2027):
rows += requests.get(f"https://api.bnm.gov.my/public/opr/year/{y}", headers=H, timeout=30).json().get("data", [])
df = pd.DataFrame(rows)
print(df[["date", "change_in_opr", "new_opr_level"]])- Data → From Web → Advanced → add the Accept header → expand 'data'.
- No native JSON import — use the Python recipe.
Recipe 2Ringgit per dollar, closing session
import requests, pandas as pd
H = {"Accept": "application/vnd.BNM.API.v1+json"}
u = "https://api.bnm.gov.my/public/exchange-rate/USD/year/2026?session=1700"e=rm"
df = pd.DataFrame(requests.get(u, headers=H, timeout=30).json()["data"])
df = pd.json_normalize(df.to_dict("records"))
print(df[["date", "rate.buying_rate", "rate.selling_rate"]].tail())- From Web with the Accept header.
- Use the Python recipe.
Recipe 3International reserves
import requests, pandas as pd
H = {"Accept": "application/vnd.BNM.API.v1+json"}
u = "https://api.bnm.gov.my/public/msb/1.3?year=2026" # Monthly Statistical Bulletin table 1.3
j = requests.get(u, headers=H, timeout=30).json()
print(pd.DataFrame(j["data"]).tail())- From Web with the Accept header.
- Use the Python recipe.
Series → question map
The ids we use from BNM OpenAPI, each with the question it answers. The catalog's compare view reads the concept tags behind these rows.
| Series | Answers | Region | Concept |
|---|---|---|---|
| opr | What is Malaysia's policy rate? | Malaysia | policy_rate |
| exchange-rate/USD | Ringgit per dollar, daily | Malaysia | fx_usd |
| klibor | What is KLIBOR? | Malaysia | interbank |
| msb/1.3 | How large are Malaysia's reserves? | Malaysia | reserves |
Compare with
Same question, different source: HKMA API, SingStat Table Builder API, BIS Data Portal. The compare view lines up coverage, frequency, history and access side by side and lists what the combination makes possible.
Open compare: BNM OpenAPI · HKMA API · SingStat Table Builder API →
Questions readers ask
Why does BNM return 'not acceptable'?
The Accept header is missing or unversioned. Send Accept: application/vnd.BNM.API.v1+json.
Where is Malaysian CPI?
OpenDOSM (open.dosm.gov.my) has a free API for CPI, GDP and labour data.
Is there history before 2004?
Not through the API; the Monthly Statistical Bulletin PDFs go further back.
From the paper
Educational only — we explain, we never advise · snippet licence: public domain · corrections to [email protected], fixed within a day and logged in the changelog.