Unmapped Labs

Docs

Getting the data

Every dataset is in the portal to explore and download, and in the API for your own systems and spreadsheets. Each record names its source, and each release says what changed.

Accounts are open by invitation during early access. Join early access to get one.

Your first call

  1. Sign in at app.unmappedlabs.com, switch on the datasets you need, and create a key under API keys.
  2. Ask for the latest exchange rates:
curl https://api.unmappedlabs.com/v1/datasets/reserve-bank-series/latest \
  -H "Authorization: Bearer um_live_your_key"

The answer is JSON: the dataset, its release version, and the records, each with its source.

Keys

Send your key in the Authorization header: Bearer um_live_…. Keys read the datasets your account has switched on, and you can have up to ten, so use one per system and revoke any that leak. For CSV only, a key can go in the address instead (?key=), because spreadsheets can’t send headers; make a separate key for those links.

Without code

In the portal, open a dataset’s Explore and download: filter it, see it as a table, download a CSV for Excel, or make a spreadsheet link that refreshes itself. See spreadsheets below.

Records and paging

GET /v1/datasets/{id}/records returns records a page at a time: 100 as JSON, up to 1,000 as CSV (?limit= asks for fewer). When there are more, the answer has a next link (and a Link header); follow it until next is null.

{
  "dataset": "dam-levels",
  "version": "2026-10-26",
  "count": 227,
  "offset": 0,
  "returned": 100,
  "records": [ … ],
  "next": "https://api.unmappedlabs.com/v1/datasets/dam-levels/records?cursor=…"
}

A next link stays on the release it started on, so a page-through never mixes two releases, even if new data arrives halfway. Time series come newest first.

Filters

Each dataset has its own filters (listed under datasets), and every one takes from and to dates. Combine them freely:

/v1/datasets/reserve-bank-series/records?currency=USD&from=2026-01-01
/v1/datasets/dam-levels/records?province=ZA-WC
/v1/datasets/electricity-tariffs/records?distributor=CPT&category=residential

An unknown parameter is an error that names the filters that work, so a typo never quietly returns everything.

Latest values

GET /v1/datasets/{id}/latest returns the newest record for each currency, rate, dam or tariff: often all a website, dashboard or quote needs. It takes the same filters.

CSV

Add .csv to records or latest (or ?format=csv). Nested values become columns: source.publisher, source.url. Where a record holds a list, such as a tariff’s charges, there’s one row per item, with the record’s own fields repeated. Files are UTF-8 with a byte-order mark, so Excel opens them correctly.

Spreadsheets and Power BI

Build the CSV address with your filters and a key, for example:

https://api.unmappedlabs.com/v1/datasets/reserve-bank-series/records.csv?currency=USD&from=2026-01-01&key=um_live_your_spreadsheet_key

The portal’s explorer makes these links for you. Every refresh counts towards your daily records, so filter to what you need.

Releases and changes

Data arrives in releases. GET /v1/datasets/{id}/releases lists them, newest first, with how many records each added, changed and removed. Datasets kept current by our daily jobs (rates, exchange rates, dam levels) release when their source has something new; others release as they’re built and checked.

Our promise for v1: changes only add things. We don’t rename or remove a field, endpoint or filter without telling key holders by email at least 90 days before and listing it here. The API is also described for tools at /v1/openapi.json (OpenAPI 3.1), which Postman and code generators read.

Limits

Errors

StatusErrorWhat to do
400bad_parameter, bad_limit, bad_cursorFix the parameter the message names.
401unauthorizedSend a valid key.
402payment_requiredThe dataset is paid; buy it in the portal.
403not_enabled, terms_requiredSwitch the dataset on, or accept the terms, in the portal.
409not_availableThe dataset is still being built.
429rate_limited, daily_limitSlow down, or wait for the daily reset.

Every error is JSON: {"error": "…", "message": "…"}.

Code examples

Python, every US dollar rate this year:

import requests

url = "https://api.unmappedlabs.com/v1/datasets/reserve-bank-series/records"
params = {"currency": "USD", "from": "2026-01-01"}
headers = {"Authorization": "Bearer um_live_your_key"}
rows = []
while url:
    page = requests.get(url, params=params, headers=headers, timeout=30).json()
    rows += page["records"]
    url, params = page["next"], None  # the next link carries the filters

JavaScript, the latest level of every Western Cape dam:

const res = await fetch(
  "https://api.unmappedlabs.com/v1/datasets/dam-levels/latest?province=ZA-WC",
  { headers: { Authorization: "Bearer um_live_your_key" } }
);
const { records } = await res.json();
for (const d of records) console.log(d.dam, d.percent_full);

Datasets

The datasets with data so far. Every dataset on the plan is in the catalogue.

UM-001 Municipal electricity tariffs

One record per tariff, per distributor, per financial year. Updated annual. NERSA approves municipal tariffs from about April to June each year and they take effect on 1 July. Eskom's own tariffs take effect on 1 April. Coverage: Every tariff in NERSA’s FY2026/27 decisions for City of Cape Town (18 tariffs) and eThekwini (15 tariffs). Next: the other six metros (Johannesburg City Power, Tshwane, Ekurhuleni, Nelson Mandela Bay, Buffalo City, Mangaung Centlec), then the next 12 largest distributors, then the rest and Eskom.

/v1/datasets/electricity-tariffs/records · /latest gives the newest financial year of every tariff, for each distributor

FilterExampleMeaning
distributorCPTMunicipal Demarcation Board code of the distributor
categoryresidentialTariff category: residential, commercial, large_power, embedded_generation, wheeling, generator or public_lighting
financial_year2026/27Municipal financial year
from, to2026-01-01Dates, on effective_from
Fields (13)
FieldTypeMeaning
idstringStable id: country, distributor, tariff and financial year
distributorstringLicensed distributor: Eskom or the municipality
distributor_codestringMunicipal Demarcation Board code of the municipality (CPT, ETH)
tariffstringTariff name as the decision gives it
categoryenumresidential, commercial, large_power, agricultural, embedded_generation, wheeling, generator, public_lighting or other
financial_yearstringMunicipal financial year, July to June
effective_fromdateDate the tariff takes effect
statusenumWhere the tariff is in NERSA's approval process
chargesarrayEach charge: name, season (high_demand is winter, usually June to August), time-of-use period, meter, kWh block, unit, the approved value, last year's approved value (previous) and, where the decision shows it, what was actually charged last year (previous_charged)
vatenumWhether the charges include VAT, as the source states it
sourceobjectPublisher, document, decision date, page and link
flagsarrayMachine-readable notes from checking: summary_rate_differs, suspect_value_in_source, obsolete_tariff, unit_not_stated
notesarrayWhat each flag means for this record, in words
curl "https://api.unmappedlabs.com/v1/datasets/electricity-tariffs/records?distributor=CPT" \
  -H "Authorization: Bearer um_live_your_key"

UM-028 Dam levels with full history

One record per dam, per weekly State of Dams report. Kept current automatically. Weekly State of Dams: checked daily at 04:00 UTC, released when DWS publishes a new report (Mondays). Coverage: All 227 dams in the Department of Water and Sanitation's weekly State of Dams: 224 in South Africa, Katse and Mohale in Lesotho and Maguga in Eswatini, from the report of 26 October 2026, each with last week's and last year's level. History grows week by week; earlier years from DWS's verified daily data come next.

/v1/datasets/dam-levels/records · /latest gives the latest weekly reading of every dam

FilterExampleMeaning
stationD3R002DWS station code of a dam
provinceZA-WCProvince code, ISO 3166-2
countryZAZA, or LS and SZ for the Lesotho and Eswatini dams
riverOrange RiverRiver, as DWS names it
from, to2026-01-01Dates, on report_date
Fields (14)
FieldTypeMeaning
idstringStation code and report date (D3R002-2026-10-26)
stationstringThe Department of Water and Sanitation's station code for the dam; stable from week to week
damstringDam name as DWS gives it
riverstringRiver the dam is on
countrystringZA, or LS and SZ for the Lesotho and Eswatini dams that supply South Africa
provincesarrayProvince codes, ISO 3166-2 (ZA-FS); two where a dam sits on a provincial border
report_datedateDate of the weekly report
full_supply_capacitynumberFull supply capacity, in million cubic metres
percent_fullnumberStorage as a % of full supply capacity this week; above 100 when the dam is over full supply
percent_full_last_weeknumberThe same, a week earlier
percent_full_last_yearnumberThe same, a year earlier
volumenumberWater stored, in million cubic metres: capacity × % full, worked out by us
flagsarraynot_this_week (DWS shows its latest available reading, not this week's) and above_full_supply
sourceobjectPublisher, report and the DWS page the figures come from
curl "https://api.unmappedlabs.com/v1/datasets/dam-levels/records?station=D3R002" \
  -H "Authorization: Bearer um_live_your_key"

UM-142 Reference interest rates and legal limits

One record per rate, per date it took a new value. Kept current automatically. Reserve Bank series: checked daily at 04:00 UTC, released when a rate takes a new value (ZARONIA and SABOR do most business days). Coverage: The Reserve Bank's policy rate (repo rate) and prime lending rate with every change since 2001, and the ZARONIA (since August 2022) and SABOR (since August 2006) overnight rates, daily. Still to add: the legal limits that follow from the repo rate (the prescribed rate of interest and the National Credit Act maximum rates), once each rule is checked against the law's text.

/v1/datasets/reference-rates/records · /latest gives the current value of each rate

FilterExampleMeaning
rateprime_lending_ratepolicy_rate (the repo rate), prime_lending_rate, zaronia or sabor
from, to2026-01-01Dates, on effective_from
Fields (10)
FieldTypeMeaning
idstringRate and effective date (policy-rate-2026-09-19)
rateenumpolicy_rate (the repo rate), prime_lending_rate, zaronia or sabor
namestringThe rate's full name
effective_fromdateFirst day the rate had this value, as the Reserve Bank's series shows it
valuenumberThe rate, % a year
previousnumberThe value before this one, % a year
changenumberChange from the previous value, in percentage points
series_codestringThe Reserve Bank's time series code (MMRD002A)
flagsarrayfirst_in_series, and spread_not_3_5 where prime isn't the policy rate plus 3.5 points
sourceobjectPublisher, series and the API address it came from
curl "https://api.unmappedlabs.com/v1/datasets/reference-rates/records?rate=prime_lending_rate" \
  -H "Authorization: Bearer um_live_your_key"

UM-150 Reserve Bank statistical series

One record per currency per business day. Kept current automatically. Reserve Bank series: checked daily at 04:00 UTC, the last 10 business days released when anything is new or changed. Coverage: First release: the Reserve Bank's rand exchange rates, rand per US dollar, British pound, euro and Japanese yen, and the nominal effective exchange rate of the rand (index, 2022 = 100), every business day from 4 January 2016 to 30 October 2026. Still to add: SARB's interest rate, money, credit and balance-of-payments series, a few at a time, each checked the same way.

/v1/datasets/reserve-bank-series/records · /latest gives the latest business day of each currency

FilterExampleMeaning
currencyUSDUSD, GBP, EUR, JPY, or NEER for the nominal effective exchange rate
from, to2026-01-01Dates, on date
Fields (9)
FieldTypeMeaning
idstringCurrency and date (usd-2026-10-30)
currencyenumUSD, GBP, EUR or JPY (ISO 4217), or NEER for the nominal effective exchange rate of the rand
namestringThe currency's or the index's name
datedateBusiness day of the rate, as the Reserve Bank's series shows it
valuenumberRand per one unit of the currency, the weighted average of the banks' rates at about 10:30 (the yen too: rand per one yen, to four decimals); for NEER the index, where higher means a stronger rand
unitstringRand per US dollar, British pound, euro or Japanese yen, or the index and its base year (Index, 2022 = 100)
series_codestringThe Reserve Bank's time series code (EXCX135D)
flagsarraylarge_move (4% or more from the series' previous value), after_gap (no value on the business day before, though the other series have one) and suspect_value_in_source (far from both neighbouring days, published as SARB shows it)
sourceobjectPublisher, series and the API address it came from
curl "https://api.unmappedlabs.com/v1/datasets/reserve-bank-series/records?currency=USD" \
  -H "Authorization: Bearer um_live_your_key"