Documentation menu
Docs / Reference / Search occupancy permits

Search occupancy permits

GET/occupancy_permit

Parameters

ParameterTypeDescription
municipalitystringoptionalFilter to one city by its slug, e.g. 'new_westminster', 'toronto', 'north_vancouver_city'.Exact match. This is the city filter for BuildData datasets (permits, development permits, planning applications, inspections); call /{entity_type}/coverage for the list of valid slugs. The 'city' param is accepted as an alias here. (For the US liquor/healthcare/tank datasets, 'city' is instead a distinct filter and 'municipality' does not apply.)
qstringoptionalFull-text search across the indexed fields of the record, including names (business, establishment, vendor, contractor) wherever the dataset carries them, e.g. q=brewery or q="Iron Hill".This is the name search: there is no separate name= parameter, and it works on every plan including free. It matches the BUSINESS or trade name, the address, city, county and licence/permit number. It does NOT match an owner, licensee or applicant's personal name: these datasets carry public business records only and never store an individual's name, so searching for a person returns nothing. Space-separated words require all terms (e.g. "wood panel"). Use OR to match any term ("wood OR panel OR acoustic"), quotes for exact phrases ("supply arrangement"), and a leading minus to exclude ("software -hardware").
permit_typestringoptionalFilter by normalized permit type (exact match): new_construction, renovation, addition, demolition, change_of_use, other.Matches the permit_type_canonical field, not the raw per-city permit_type string.
tradestringoptionalFilter trade sub-permits by trade (exact match): electrical, plumbing, mechanical, gas, sprinkler, sign, trade_other.A trade permit is pulled for work on one system rather than the building, so this is the field for replacement-cycle work: a furnace swap is trade=mechanical, a rewire is trade=electrical. Only cities that publish trade permits separately carry it; combine with permit_type, which every permit has.
contractorstringoptionalFilter permits by the firm that pulled them.A whole name is matched exactly against the normalized contractor name and is indexed; a fragment falls back to a substring across the contractor, company and builder fields. Cities file the same firm under several spellings, so the normalized form is the one that groups them.
units_minintegeroptionalPermits with at least this many dwelling units.Reads whichever of units, dwelling_units, total_units or units_created the city publishes.
floor_area_minnumberoptionalPermits with at least this much floor area, in square metres.Reads floor_area or area_m2, whichever the city publishes. Cities that publish square feet are not converted and are not matched.
storeys_minintegeroptionalPermits on a building of at least this many storeys.
structure_typestringoptionalFilter by structure or building type as the city publishes it (exact, case-insensitive), e.g. 'Single Family Dwelling', 'Hotel'.Call /permit/stats for the values in use.
neighbourhoodstringoptionalFilter by neighbourhood (partial match).Matches whichever of neighborhood, neighbourhood or community the city publishes; Calgary calls it community.
wardstringoptionalFilter by electoral ward as the city publishes it (exact, case-insensitive).
zoningstringoptionalFilter by the zoning code on the record (partial match), e.g. 'R-1'.Present on permits, development permits and assessments.
categorystringoptionalProcurement category: CNST, GD, SRV, SRVTGD.For recalls: product category (e.g. 'Baby products', 'Toys and games'). For healthcare: facility category (acute, long_term, home, behavioral, outpatient, specialty). For storage tanks: record type (tank or release).
statusstringoptionalFilter by status. Contracts/tenders: Active, Expired, Cancelled, Open (exact).Permits: use a status_canonical value instead (issued, in_review, completed, expired, cancelled, unknown).
sectorstringoptionalCanonical sector (major_project): transit, roads_bridges, education, healthcare, residential, energy, industrial, infrastructure, municipal, recreation_culture, commercial, institutional.Spans both official languages and every source inventory.
status_canonicalstringoptionalNormalized permit status: issued, in_review, completed, expired, cancelled, unknown
is_activebooleanoptionalBusiness licences: true returns only currently active licences, false only inactive ones.Most licence records are historical, so this is usually what you want.
is_openingbooleanoptionalBusiness licences: true returns only business openings, meaning the first licence this business ever held in this city.Renewals reuse the name and are excluded. Pair with opened_from to get the businesses that opened in a period.
preceded_by_permitbooleanoptionalBusiness licences: true returns only openings that had a building permit at the same address in the two years before they opened, meaning the fit-out is on record. false returns openings that took space needing no work.
opened_fromstringoptionalBusiness licences: earliest opening date, YYYY-MM-DD.Filters on when the business was first licensed here, not on when this particular licence was issued.
opened_tostringoptionalBusiness licences: latest opening date, YYYY-MM-DD.
citystringoptionalFilter by city. For the US liquor/healthcare/storage-tank datasets this is a distinct filter (exact, case-insensitive).For all other datasets it is an alias for municipality.
value_minnumberoptionalMinimum value, applied to the entity's value field (construction_value for permits, contract_value for contracts, agreement_value for grants, monetary_amount for contributions, total_co2e for emissions, assessed_value for assessments).Ignored for entity types with no monetary field.
value_maxnumberoptionalMaximum value; same per-entity field mapping as value_min.Ignored for entity types with no monetary field.
issued_afterstringoptionalFilter by date >= YYYY-MM-DD (issued_date).Aliases: date_from, date_after
issued_beforestringoptionalFilter by date <= YYYY-MM-DD.Aliases: date_to, date_before
latnumberoptionalLatitude for proximity search.Send with lng.
lngnumberoptionalLongitude for proximity search.Send with lat.
radius_kmnumberoptionalRadius in km, 0.1 to 25.Optional: lat and lng with no radius search 1 km around the point. The radius actually applied is returned as radius_km in the response, so a default is never silent. For every record at one street address rather than everything near it, use /property.
sort_bystringoptionalSort field: published (publication_date, tenders only), date (event date; this is award_date for contracts/awards), value (the entity's value field), closing (closing_date for tenders)
sort_orderstringoptionalSort order: asc or desc
limitintegeroptionalRecords per page, 1 to 500.Free-tier keys are capped lower; the cap applied is the `count` in the response.
offsetintegeroptionalRecords to skip. Fine for jumping to a known page;use `cursor` to walk a whole result set, because offset re-reads everything it skips and shifts when new records arrive.
cursorstringoptionalPagination cursor from next_cursor in a previous response.Use instead of offset for deep pagination.

Request

curl --request GET \
  --url 'https://api.builddata.ca/occupancy_permit' \
  --header 'X-API-Key: YOUR_API_KEY'
import requests

url = "https://api.builddata.ca/occupancy_permit"
headers = {"X-API-Key": "YOUR_API_KEY"}
resp = requests.get(url, headers=headers)
print(resp.json())
const res = await fetch("https://api.builddata.ca/occupancy_permit", {
  headers: {
    "X-API-Key": "YOUR_API_KEY",
  },
});
const data = await res.json();
console.log(data);
<?php
$ch = curl_init("https://api.builddata.ca/occupancy_permit");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    "X-API-Key: YOUR_API_KEY",
]);
echo curl_exec($ch);
require "net/http"
require "uri"

uri = URI("https://api.builddata.ca/occupancy_permit")
req = Net::HTTP::Get.new(uri)
req["X-API-Key"] = "YOUR_API_KEY"
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts res.body
package main

import (
    "fmt"
    "io"
    "net/http"
)

func main() {
    req, _ := http.NewRequest("GET", "https://api.builddata.ca/occupancy_permit", nil)
    req.Header.Add("X-API-Key", "YOUR_API_KEY")
    res, _ := http.DefaultClient.Do(req)
    defer res.Body.Close()
    body, _ := io.ReadAll(res.Body)
    fmt.Println(string(body))
}
import java.net.URI;
import java.net.http.*;

HttpRequest req = HttpRequest.newBuilder()
    .uri(URI.create("https://api.builddata.ca/occupancy_permit"))
    .header("X-API-Key", "YOUR_API_KEY")
    .build();
HttpResponse<String> res = HttpClient.newHttpClient()
    .send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());

Example response

{
  "entity_type": "permit",
  "count": 1,
  "offset": 0,
  "results": [
    {
      "record_id": "24 223023 PLB:00",
      "municipality": "toronto",
      "lat": 43.65401,
      "lng": -79.42376,
      "work": "Building Permit Related(PS)",
      "status": "Permit Issued",
      "address": "865 COLLEGE ST",
      "description": "Plumbing - INTERIOR ALTERNATION OF 3RD FLOOR SUITE",
      "issued_date": "2025-01-29",
      "permit_type": "Plumbing(PS)",
      "application_date": "2024-10-07",
      "construction_value": null
    }
  ]
}

Response fields

One occupancy_permit record. Measured over 200 live records sampled across municipalities. Percentages say how often each field is populated: sources publish different columns, so a field missing from a given record is normal rather than an error.

FieldTypeDescription
addressstringAlways present.
application_datestringAlways present.
business_namestringAlways present.
fetched_atstringWhen Nimbus last ingested this record. Bookkeeping, not source data. Always present.
issued_datestringAlways present.
latnumberAlways present.
lngnumberAlways present.
municipalitystringCity slug. Call /{entity_type}/coverage for every slug in this dataset. Always present.
neighbourhoodstringAlways present.
normalized_addressstringAddress normalized for joining the same property across datasets. Always present.
permit_typestringAlways present.
pipeline_stagestringAlways present.
proposed_usestringPopulated in 99% of sampled records.
provincestringAlways present.
quadrantstringAlways present.
record_idstringStable identifier for this record. Pass it to the /{entity_type}/{record_id} endpoint. Always present.
record_numberstringAlways present.
statusstringAlways present.
status_canonicalstringStatus mapped onto a shared vocabulary. This is what the status_canonical filter matches. Always present.
wardstringAlways present.
5 more fields published by only one or two cities

Present on under 10% of records. Useful when you are working with a specific city, not something to rely on across the dataset.

FieldTypePopulated
building_addressstring0.5%
completion_datestring0.5%
inspection_datestring0.5%
normalized_business_namestring0.0%
sticker_numberstring0.5%

Errors

Every error returns {"detail": "…"}, a sentence naming what was wrong and, where there is one, the fix.

StatusWhen
400The request is understood but cannot be served as asked, e.g. offset above 9500, or cursor combined with sort_by=value. The message names the fix.
403Missing or invalid API key.
404No such endpoint, or no record with that id.
422A parameter is invalid: an unknown city slug, an impossible date, a limit above 500, an unrecognised sort field, or a misspelled parameter name. The message names the parameter and, where there is one, the nearest valid value.
429Rate limit or plan quota exceeded.
500Unexpected server error.
504The query took too long. Narrow it with a municipality or a date range.
Ready to build?Create a free key and start calling BuildData in minutes. No card required.
Get your API key →