Seats
Use Case
Use these endpoints to build a seat history feature, similar to the one on our Seats dashboard. A user selects a seat by name, and gets back every election result for that seat.
The endpoint covers all 222 parliamentary seats and 600 state seats that exist today. To maintain downstream discipline, we deliberately do not provide a query-based API for defunct seats. However, beyond the programmatic access available via the data catalogue which enables users to implement their own notions of a seat’s evolution (both their name and boundaries), this endpoint allows users to trace the complete geospatial lineage of any current seat:
- Default (
lineage=false): results only for elections where the seat held its current name and boundaries. Administratively clean; useful when you want like-for-like comparisons, especially against official classifications. - With lineage (
lineage=true): results extend back through redelineations and renames, with boundary-change events interspersed to explain the transitions. Use this to recreate the full seat history shown on our site.
Just follow the 2 steps below and you’ll be up and running!
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 list of seats
To let users pick a seat, first fetch the full list of current seats and their slugs.
https://api.electiondata.my/v1/seats/dropdownThis endpoint takes no parameters. The list is only updated when electoral boundaries are changed, so we recommend fetching it once and caching it for the duration of a session (or potentially bundling it at build time for static pages).
A successful 200 OK response returns an object with a seats array. Each entry contains:
| Field | Type | Description |
|---|---|---|
seat | string | Full seat name including number and state (e.g. 'P.001 Padang Besar, Perlis'). |
slug | string | URL-safe unique identifier for the seat. Use this in Step 2. |
type | string | 'parlimen' for parliamentary seats or 'dun' for state seats. |
Step 2: Get single Seat
Pass the slug from the dropdown to retrieve the electoral history for that seat.
https://api.electiondata.my/v1/seats/results| Parameter | Type | Required | Description |
|---|---|---|---|
slug | string | Yes | Unique seat identifier from the dropdown (e.g. 'p001-padang-besar-perlis'). |
lineage | boolean | No | Set to 'true' to include ancestral results and boundary-change events. Defaults to false. |
The slug parameter is required. Omitting it returns 400 Bad Request; an unrecognised slug returns 404 Not Found.
A successful 200 OK response returns an object with a results array ordered from most recent to oldest. Without lineage, every object in the array has the same shape:
| Field | Type | Description |
|---|---|---|
election_name | string | Human-readable election name (e.g. 'GE-15' for parliament, 'SE-15' for state). |
seat | string | Seat name as it appeared on the ballot for that election. |
state | string | State the seat belongs to. |
date | date | Polling day in ISO 8601 format (YYYY-MM-DD). |
party | string | Acronym of the winning party (e.g. 'PAS'). |
party_uid | string | Unique party identifier (e.g. '004-PAS'). |
coalition | string | Winning party's coalition, if any (e.g. 'PN'). |
coalition_uid | number | Unique coalition identifier, if any. |
name | string | Name of the winning candidate. |
majority | integer | Winning majority in votes. |
majority_perc | float | Majority as a percentage of valid votes. |
voter_turnout | integer | Total votes cast. |
voter_turnout_perc | float | Turnout as a percentage of registered voters. |
Using lineage
Pass lineage=true to extend the history back through boundary changes and renames:
With lineage=true, the results array mixes two object shapes. Election results have the same fields as above. Interspersed between them are boundary-change events that mark where the seat transitioned to its dominant ancestor:
| Field | Type | Description |
|---|---|---|
date | date | Rough date of the redelineation event (YYYY-MM-DD); the dates are not exact (i.e. they do not represent the exact date of approval in the Dewan Rakyat), but are sufficiently precise to capture whether a redelineation came into effect before or after an election held in the same year. |
change_en | string | English description of the boundary change (e.g. 'Padang Besar was created from Kangar in the 1994 redelineation'). |
change_ms | string | Bahasa Malaysia description of the same event (e.g. 'Padang Bsar diwujudkan daripada Kangar dalam persempadanan semula 1994'). |
You can distinguish the two shapes by checking for the presence of election_name: if it exists, the entry is a result; if it is absent, it is a boundary-change event.
When to use each mode:
- Use
lineage=falsewhen you want administratively clean data—i.e. comparing results across elections for a seat that has not changed or been renamed. - Use
lineage=truewhen you want to trace a seat’s full history back to 1955, following its dominant ancestor through every redelineation. This is what our Seats page uses to show an unbroken history even when seats were renamed or had their boundaries changed.
Try It Out
Pick a seat, choose your lineage preference, and fire a live request.