Docs
Staying current
Every dataset is built to be read by people and by systems: free tool pages for everyone, and an API, CSV files and spreadsheet links behind them. Each record names its source, and each numbered release says what changed.
Open now: the free tools and their spreadsheet templates, with no sign-up. Not open yet: accounts and API keys, while we finish the datasets. These docs show what every call returns. Nothing is for sale.
Email alerts
Needs a key (not open yet)
Under Alerts in the portal, choose what to watch on a dataset you’ve switched on, and we email you when it happens:
- Dam levels: a dam falls below or rises above a level (% full), or changes; or any new weekly reading.
- Reference rates: the prescribed rate of interest or a National Credit Act maximum rate changes.
- Electricity tariffs: a distributor’s tariffs are added or changed; or any new release.
A crossing is told once, when it happens, not again every week the value stays past it. Each alert sends at most one email in 12 hours, and one email covers every alert that fired on the same release. Alerts pause while their dataset is switched off. You can have up to 25.
Webhooks
Needs a key (not open yet)
A webhook tells your own system the moment a release lands, so it can fetch just what changed instead of checking on a timer. Add one under Alerts in the portal: a public https:// address, and the datasets it’s for (or all you’ve switched on). We show its signing secret once; keep it with your endpoint. Send a test posts a ping to check it’s working.
For every release, we POST JSON like this:
{
"id": "evt_1042",
"type": "dataset.released",
"created_at": "2026-11-02T04:01:12.519Z",
"data": {
"dataset": "dam-levels",
"name": "Dam levels with full history",
"version": "2026-11-02",
"previous_version": "2026-10-26",
"added": 227, "changed": 0, "removed": 0,
"record_count": 454,
"changed_ids": ["A2R001-2026-11-02", "…"],
"removed_ids": [],
"ids_truncated": false,
"changes_url": "https://api.unmapped.co.za/v1/datasets/dam-levels/records?changed_in=2026-11-02",
"releases_url": "https://api.unmapped.co.za/v1/datasets/dam-levels/releases"
}
}
The id lists hold up to 1,000 each; changes_url always has everything. Each request carries these headers:
Unmapped-Event:dataset.released, orpingfor a test.Unmapped-Event-Id: the event’s id. A retry repeats it, so use it to ignore a delivery you’ve already handled.Unmapped-Signature:t=1761962472,v1=5f2b…, the time it was sent and an HMAC-SHA256 oft, a full stop, and the raw body, keyed with your secret.
Check the signature before trusting a delivery: work out the HMAC of the raw body exactly as received, compare it with v1 in constant time, and turn away anything older than five minutes. In Node:
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(rawBody, header, secret) {
const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
return v1.length === expected.length && timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
}
In Python:
import hmac, hashlib, time
def verify(raw_body: bytes, header: str, secret: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
if abs(time.time() - int(parts["t"])) > 300:
return False
expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts["v1"])
Answer with any 2xx within 10 seconds, then do the work. Anything else is retried after 1, 10 and 30 minutes, then 2, 6, 12 and 24 hours: about two days in all. Redirects aren’t followed. If three releases in a row fail every try, the webhook is switched off and we email you; switch it back on in the portal once it’s fixed. Each webhook’s recent deliveries, and what your endpoint answered, are listed under it in the portal.
AI assistants (MCP)
Needs a key (not open yet)
Assistants that support the Model Context Protocol (Claude, ChatGPT, Cursor, VS Code and others) can read your datasets directly and answer questions such as “How full is Gariep Dam, and how does that compare with last year?” with the source of each figure. The server is:
https://api.unmapped.co.za/mcp
It uses your API key, the datasets you’ve switched on, and your daily records, like any other call. Set it up for your app:
Claude Code:
claude mcp add --transport http unmapped https://api.unmapped.co.za/mcp \
--header "Authorization: Bearer um_live_your_key"
Cursor, in mcp.json:
{
"mcpServers": {
"unmapped": {
"url": "https://api.unmapped.co.za/mcp",
"headers": { "Authorization": "Bearer um_live_your_key" }
}
}
}
VS Code, in .vscode/mcp.json:
{
"servers": {
"unmapped": {
"type": "http",
"url": "https://api.unmapped.co.za/mcp",
"headers": { "Authorization": "Bearer um_live_your_key" }
}
}
}
Apps that only take an address, such as custom connectors in Claude and ChatGPT. The key is part of the address, so make a key just for this and revoke it if the address is ever shared:
https://api.unmapped.co.za/mcp?key=um_live_your_key
The tools, all read-only:
| Tool | What it does |
|---|---|
list_datasets | The datasets with data, their latest release, and which you’ve switched on. |
describe_dataset | A dataset’s fields, units, filters and what “latest” means for it. |
get_latest | The newest value for each dam, currency, rate or tariff, with filters. |
query_records | Records filtered by the dataset’s filters and dates, a page at a time. |
list_releases | A dataset’s releases and what each changed. |
lookup_location | The ward, municipality, district and province at a point (coordinates). |
calculate_bill | A month’s electricity bill on a tariff, or each of a distributor’s tariffs, with or without solar (as /v1/bill). |
search | The catalogue, including datasets still planned, and your switched-on records. |
The server speaks MCP 2026-07-28 and the earlier versions from 2025-03-26, over Streamable HTTP.