Live Turnout
Use Case
Use this endpoint to track voter turnout on polling day in (close to) real time. It is ideal for powering a notification service (for the latest headline number), visualising turnout by seat, or charting turnout over the course of the day. Every figure is split into overall, early and ordinary turnout.
There are two endpoints:
/vto/latest: the newest snapshot for the state, every Parliament seat and every DUN seat. Poll this for a live view./vto: one level (Parliament, DUN or polling district) for one 5-minute slot. Use this to build a time series or to go down to polling district (DM) level.
Data Note
On 14 November 2026, polling day for the 16th Melaka state election, the Election Commission (SPR) published real-time voter turnout via dashboard.spr.gov.my, made possible by the introduction of digital voter registration at polling centres. We launched this endpoint the same day, and will maintain it for as long as SPR continues to publish the data.
Prerequisite: API Key
All requests to this API require a Bearer token. Requests without a token (or with an invalid one) will return a 401 Unauthorized error. Paste yours below—every code snippet and the live tester at the bottom of this page will be pre-filled and ready to run.
Step 1: Get latest snapshot
https://api.electiondata.my/v1/vto/latest| Parameter | Type | Required | Description |
|---|---|---|---|
election | string | Yes | Election identifier in the form <state>-<ge|se><NN>, using the 3-letter state code, e.g. 'mlk-se16'. Only 'mlk-se16' is available for now. |
Responses are cached for 30 seconds (other endpoints cache for 300 seconds), and a new snapshot arrives every 5 minutes, so polling once a minute is plenty.
A successful 200 OK response returns a JSON object with these keys:
| Field | Type | Description |
|---|---|---|
timestamp | string | Time of the snapshot in the Election Commission (SPR) feed, in Malaysia time (MYT), e.g. '2026-11-14 08:25:52'. |
slot | string | The 5-minute slot this snapshot belongs to, as HHMM in MYT (the timestamp rounded down), e.g. '0825'. Pass it as time to /vto to fetch other levels for the same moment. |
state | object | A turnout unit (see below) for the whole state. |
parlimen | object | One turnout unit per Parliament seat, keyed by seat name, e.g. 'P.134 Masjid Tanah'. |
dun | object | One turnout unit per DUN seat, keyed by seat name, e.g. 'N.01 Kuala Linggi'. |
Polling district (DM) figures are not included here, to keep the response small. Use Step 2 for those.
Turnout unit: every state, seat and polling district has the same fields. The abs fields are votes cast, the vto fields are turnout percentages, and the electorate fields are the denominators those percentages are based on.
| Field | Type | Description |
|---|---|---|
overall_abs | integer | Total votes cast: early, ordinary and postal. |
overall_vto | float | Overall turnout as a percentage to 2 decimal places (null if the electorate is 0). |
overall_electorate | integer | All registered voters, from the final voter roll. Includes postal voters, so it is slightly larger than awal_electorate + biasa_electorate. |
awal_abs | integer | Early votes cast (awal = police, army and others who voted before polling day). |
awal_vto | float | Early-voter turnout as a percentage (null if there are no early voters). |
awal_electorate | integer | Registered early voters. 0 for DUNs (and their pseudo-districts) with no early voters; null for regular polling districts. |
biasa_abs | integer | Ordinary votes cast on polling day (biasa = ordinary). |
biasa_vto | float | Ordinary-voter turnout as a percentage (null if there are no ordinary voters). |
biasa_electorate | integer | Registered ordinary voters (null for the early-voting pseudo-districts). |
Example unit for N.25 Rim at 08:25:
{
"overall_abs": 712,
"overall_vto": 4.01,
"overall_electorate": 17742,
"awal_abs": 0,
"awal_vto": null,
"awal_electorate": 0,
"biasa_abs": 712,
"biasa_vto": 4.06,
"biasa_electorate": 17557
}Step 2: Get a time slot
https://api.electiondata.my/v1/vto| Parameter | Type | Required | Description |
|---|---|---|---|
election | string | Yes | Election identifier, e.g. 'mlk-se16'. Same format as in Step 1. |
level | string | Yes | One of 'parlimen', 'dun' or 'dm' (polling district). |
time | string | Yes | The 5-minute slot as HHMM in Malaysia time, from '0800' until '1800' (18:00 MYT, when polls close), e.g. '0805' or '1430'. |
Slot 0800 is the pre-polling baseline: it contains early votes only, with every ordinary vote still at 0. Live slots start at 0805. A slot that hasn’t happened yet returns 404 Not Found. A missing parameter, a level that isn’t one of the three options, or a time that isn’t HHMM in 5-minute steps returns 400 Bad Request.
A successful 200 OK response returns a JSON object with one turnout unit (same fields as in Step 1) per unit at the requested level, keyed by name:
| Field | Type | Description |
|---|---|---|
parlimen | string | Parliament seat name, e.g. 'P.136 Tangga Batu'. Melaka has 6. |
dun | string | DUN seat name, e.g. 'N.11 Sungai Udang'. Melaka has 28. |
dm | string | Polling district as Parliament/DUN/DM number plus name, e.g. '136/11/01 Bukit Terendak'. Melaka returns 235 entries: 211 polling districts plus 24 '/00 Undi Awal' early-voting pseudo-districts. |
Try It Out
Use the Latest tab for the newest snapshot, or switch to By Slot to pick a level and time.