districts api

This freely-available API tells you which political districts contain a given point in Canada.

Quick Summary



        "source_id": "24047", 
        "uptodate": [2010, 9, 24], 
        "name": "Outremont", 
        "electoral_group": {
            "province": "", 
            "name": "Canada", 
            "level": "Federal"
        "source_id": "62", 
        "uptodate": [2010, 9, 24],
        "name": "Mercier", 
        "electoral_group": {
            "province": "QC", 
            "name": "Québec", 
            "level": "Provincial"

Rate Limits

Calls are rate-limited by IP; at the moment, you get roughly 10 requests per minute. If you exceed your rate limit, the API will return HTTP status code 503. If your needs exceed this limit, please get in touch.

Future Changes

This is a new API, and it will change. (In particular, there is currently no concept of changing district boundaries. That'll need to be added relatively soon.) While we'll certainly aim to maintain backwards compatibility, there's not yet any promise of API stability. And, while current functionality will remain free, the API may at some point require authentication. That's why, if you use this API, you should sign up below for a (very low-traffic, announcement-only) mailing list:

join our mailing list


The base URL for the API is http://api.vote.ca/api/beta/ . "beta" is the current API version number.

Calls return JSON by default. Adding format=xml to the query string will return XML instead, though it's a (kinda ugly) XML serialization of a data structure designed for JSON. JSONP is supported via a callback= parameter.

Currently a single call is available, districts. It takes two required GET parameters, lat and lng. These are the latitude and longitude (in the WGS84 spatial reference system, which is what pretty much any geocoder will return to you) of a point in Canada. Sample URL: http://api.vote.ca/api/beta/districts?lat=45.52&lng=-73.59

The return value is an array of district results. Each district result is a hash with the following keys:

nameThe name of the district, e.g. Toronto Centre.
source_idAn ID string used for the district in the original data file the district shape was loaded from. For federal ridings, this is the standard Electoral District ID.
uptodateThe date on which this district was loaded or verified as current. Represented as an array of [year, month, day].
electoral_groupThe set of political districts this district belongs to—e.g. Quebec Assemblée Nationale districts or Toronto city wards. Represented as a hash with three keys:
  • name — e.g. Québec or Toronto
  • province — as two-letter postal abbreviation, e.g. QC or ON. Note that for federal ridings this value will be empty, because the set of federal ridings includes all provinces.
  • levelFederal, Provincial, or Municipal

When no districts are found for a given point—this should only happen for points outside Canada—the response will be an empty array, but will still be delivered with HTTP status code 200.


At the moment, the API is aware of the following groups of districts:

  • Canada (Federal)
  • Alberta (Provincial)
  • British Columbia (Provincial)
  • Manitoba (Provincial)
  • Newfoundland & Labrador (Provincial)
  • Nova Scotia (Provincial)
  • Ontario (Provincial)
  • Prince Edward Island (Provincial)
  • Québec (Provincial)
  • Saskatchewan (Provincial)
  • Calgary, AB (Municipal)
  • Edmonton, AB (Municipal)
  • Ottawa, ON (Municipal)
  • Toronto, ON (Municipal)

Contact & Data

Contact michael@michaelmulley.com with questions and comments.

New electoral-district data is highly welcome! If you have data, please send it along, and try to ensure it meets the following criteria:

  • In a common geospatial data format: KML, GML, ESRI shapefile
  • Uses the WGS84 spatial reference system, if possible. (All KML files use this.) If the file uses another spatial reference system, it'd be great if you could include its EPSG authority number.
  • Includes a single feature for each political district (multipolygon features are fine, but each political district should have a single row in the attribute table). Attributes should provide a name and, optionally, ID number for each district.
  • Should include no features other than the relevant political districts



This example uses the Google Maps geocoder to match an address with its political districts.

Your address

Here's the code:

<form id="districtsapidemo">
<tr><th>Your address</th><td><input type="text" style="width: 400px"></td></tr>
<tr><td></td><td><input type="submit" value="Go"></td></tr>
<div id="districtsapidemo_message"></div>

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script> 
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script type="text/javascript">

var $message_area = $('#districtsapidemo_message');
$('#districtsapidemo').submit(function (e) {
    // First, find the latitude and longitude for this address
    var geocoder = new google.maps.Geocoder();
    geocoder.geocode({'address': $('#districtsapidemo input[type=text]').val(),
        'region': 'ca'}, function(results, status) {
        // A fuller example would only look at street addresses
        if (results.length > 1) {
            // Resolve multiple possible addresses
            $message_area.append('<p>Which of these looks like your address?</p>');
            $.each(results, function() {
                var result = this;
                $message_area.append($('<p>' + this.formatted_address + '</p>').click(function () {
        else if (results.length == 0) {
            $message_area.append('<p>No match for that address.</p>');
        else {

// Call this function once we have a latitude and longitude
function districts_for_geocoder_result(result) {
    // 'result' is a Google geocoder response object
    // latitude lives in result.geometry.location.lat()
    $message_area.append('<p>' + result.formatted_address + '</p>');
    $.getJSON('http://api.vote.ca/api/beta/districts?callback=?&' + $.param({
        'lat': result.geometry.location.lat(),
        'lng': result.geometry.location.lng()}), function (data) {
            if (data.length == 0) {
                $message_area.append('<p>No districts found. Is that address in Canada?</p>');
            $.each(data, function () {
                $message_area.append('<p>In ' + this.electoral_group.name + ' (' + this.electoral_group.level
                    + '), your district is ' + this.name + '.</p>');