/ Endpoints

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.

GEThttps://api.electiondata.my/v1/seats/dropdown

This 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:

FieldTypeDescription
seatstringFull seat name including number and state (e.g. 'P.001 Padang Besar, Perlis').
slugstringURL-safe unique identifier for the seat. Use this in Step 2.
typestring'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.

GEThttps://api.electiondata.my/v1/seats/results
ParameterTypeRequiredDescription
slugstringYesUnique seat identifier from the dropdown (e.g. 'p001-padang-besar-perlis').
lineagebooleanNoSet 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:

FieldTypeDescription
election_namestringHuman-readable election name (e.g. 'GE-15' for parliament, 'SE-15' for state).
seatstringSeat name as it appeared on the ballot for that election.
statestringState the seat belongs to.
datedatePolling day in ISO 8601 format (YYYY-MM-DD).
partystringAcronym of the winning party (e.g. 'PAS').
party_uidstringUnique party identifier (e.g. '004-PAS').
coalitionstringWinning party's coalition, if any (e.g. 'PN').
coalition_uidnumberUnique coalition identifier, if any.
namestringName of the winning candidate.
majorityintegerWinning majority in votes.
majority_percfloatMajority as a percentage of valid votes.
voter_turnoutintegerTotal votes cast.
voter_turnout_percfloatTurnout 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:

FieldTypeDescription
datedateRough 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_enstringEnglish description of the boundary change (e.g. 'Padang Besar was created from Kangar in the 1994 redelineation').
change_msstringBahasa 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=false when you want administratively clean data—i.e. comparing results across elections for a seat that has not changed or been renamed.
  • Use lineage=true when 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.