Building permit data API: find homes by permit history

Photo of Alex Wilkinson
Alex Wilkinson
CEO of Houski
2026-11-13

A building permit records work done on a home: a new roof, a furnace, a basement suite, an addition. A building permit data API lets you search for homes by that work. With the Houski API, permit history is attached to the property it belongs to, so one request can answer a question such as "which homes in this city had roof work since 2024".

This post covers how to request permit rows, the fields each row carries, how to filter by date and by a text pattern, and the inexpensive way to test whether a home has any permits at all.

Who uses building permit data

  • Contractors find homes whose roof, furnace or deck is due for work, and avoid homes that just had it done.
  • Insurers check whether the roof or the wiring of a home was replaced before they price a policy. The insurance page has more on that use.
  • Building material suppliers measure where renovation work is happening and how its value moves from year to year.

Start with a basic property search

Permits attach to properties, so a permit request starts as a property search. The block below is a live call against the real API. It asks for houses in Calgary with three or more bedrooms. The request code and the JSON answer are generated each time this page loads.

API request
TypeScript code
const houski_data = async (): Promise<PropertiesResponse> => {

    // You must copy the PropertiesResponse type declarations from the 
    // Houski API documentation to strongly type the response

    const url = new URL('https://api.houski.ca/properties');
    url.searchParams.set('api_key', 'YOUR_API_KEY');
    url.searchParams.set('bedroom_gte', '3');
    url.searchParams.set('city', 'calgary');
    url.searchParams.set('country_abbreviation', 'ca');
    url.searchParams.set('exclude', 'inferred');
    url.searchParams.set('page', '1');
    url.searchParams.set('property_type_eq', 'House');
    url.searchParams.set('province_abbreviation', 'ab');
    url.searchParams.set('results_per_page', '3');
    url.searchParams.set('select', 'address,bedroom,den,bathroom_full,bathroom_half,interior_sq_m,estimate_list_price');

    const response = await fetch(url);
    const data = await response.json();

    return data;
}

(async () => {
let data: PropertiesResponse = await houski_data();

// Log the response
console.log(data);
})();
API response
JSON
{
  "cache_hit": true,
  "checked_fields": [
    "bedroom",
    "bathroom_full",
    "bathroom_half",
    "interior_sq_m"
  ],
  "cost_cents": 1.8599999,
  "data": [
    {
      "address": "370 Rowley Way NW",
      "bathroom_full": 2,
      "bathroom_half": 1,
      "bedroom": 3,
      "den": 0,
      "estimate_list_price": 1652197,
      "interior_sq_m": 285.02414,
      "property_id": "132a75dd38341a25"
    },
    {
      "address": "406 Crimson Ridge Place NW",
      "bathroom_full": 3,
      "bathroom_half": 1,
      "bedroom": 3,
      "den": 1,
      "estimate_list_price": 1603968,
      "interior_sq_m": 275.2694,
      "property_id": "15f1300ff5b78de"
    },
    {
      "address": "236 Crimson Ridge Place NW",
      "bathroom_full": 2,
      "bathroom_half": 0,
      "bedroom": 3,
      "den": 0,
      "estimate_list_price": 815704,
      "interior_sq_m": 187.29253,
      "property_id": "16124d237fc5f6b5"
    }
  ],
  "error": "",
  "pagination": {
    "current_page": 1,
    "has_next_page": true,
    "has_previous_page": false,
    "page_total": 14121
  },
  "price_quote": false,
  "result_total": 42361,
  "time_ms": 44,
  "ui_info": {
    "city": "Calgary",
    "city_id": "6ec95b53075d062c",
    "city_link": "ca/ab/calgary",
    "city_slug": "calgary",
    "country": "Canada",
    "country_abbreviation": "CA",
    "country_abbreviation_id": "9ace2b6431b7f1be",
    "country_abbreviation_link": "ca",
    "country_slug": "canada",
    "province": "Alberta",
    "province_abbreviation": "AB",
    "province_abbreviation_id": "aae1f05a0f89d2c7",
    "province_abbreviation_link": "ca/ab",
    "province_slug": "alberta"
  }
}

Filters are a field name, an operator suffix and a value, as in bedroom_gte=3. The select parameter names the fields returned. The /properties documentation lists every option.

Attach permit history with expand=permits

Adding expand=permits to a request attaches the permit rows of each property to its row in the answer. Each permit row carries four fields:

  • expand_permit_application_date, the date the permit was applied for
  • expand_permit_type, the kind of permit
  • expand_permit_content, the text that describes the work
  • expand_permit_value, the stated value of the work

The other values of expand are listings, listings_rent and assessments, and they work the same way. The expand documentation covers all four.

Filter permits by date and by keyword

Permit rows can be filtered like any other field. Two filters do most of the work:

  • expand_permit_application_date_gte=2024-01-01 keeps permits applied for on or after that date.
  • expand_permit_content_regex=(?i)roof keeps permits whose text matches the pattern. The (?i) at the front makes the match ignore case, so "Roof", "ROOF" and "reroofing" all match.

One more parameter matters. Add filter_expand_match=all to keep only the properties that have a matching row.

Put together, this request returns homes in Calgary with a roof permit applied for since the start of 2024:

bash
curl "https://api.houski.ca/properties?api_key=$HOUSKI_API_KEY&country_abbreviation=ca&province_abbreviation=ab&city=calgary&expand=permits&expand_permit_application_date_gte=2024-01-01&expand_permit_content_regex=(?i)roof&filter_expand_match=all&results_per_page=10&page=1&select=address,construction_year,roof_material&exclude=inferred"

The key is read from the HOUSKI_API_KEY environment variable. Do not write it into source code.

The same shape answers other questions by changing the pattern. A pattern of (?i)solar finds solar installations, (?i)furnace finds heating work, and (?i)basement finds basement development. Permit text is written by many different offices, so test a pattern on one city and read the rows before you trust it across a country.

Test for permits inexpensively with has_expand_permits

Sometimes the question is only whether a home has permit history, not what the permits say. The has_expand_permits flag answers that without returning the rows. Add has_expand_permits_eq=true to a request and only properties with at least one permit come back.

This is the inexpensive test. Billing is on the values returned, so a request that filters on the flag and selects two fields costs far less than one that expands every permit row. Use the flag to count and to shortlist, and use expand=permits only when your code needs the rows themselves.

Permit coverage differs by place

Permit history is available in the places that publish permits. Some places publish many years of permits, some publish a few, and some publish none. A home with no permit rows may have had no permitted work, or it may be in a place with no permit data.

Check a city with has_expand_permits_eq=true before you promise coverage to a customer.

What a permit request costs

Most fields cost $0.001 per value returned, and a field with no value for a home is not billed. To see the cost of any request before you send it, add price_quote=true: the API returns the price for free with no data. Pay as you go has a $99 monthly minimum in United States dollars, and every account sets its own maximum monthly spend. The pricing page has the full table.

More than permits on the same row

A permit search is more useful with the facts of the home beside it. The same request can select construction_year, roof_material, interior_sq_m, zoning, estimate_sale_price and the flood and fire scores, from more than 450 fields per property. A contractor can rank roof leads by the age of the home. An insurer can read the roof material and the last roof permit in one answer.

The recipes show more requests of this kind, and the examples repository has runnable code in Python, JavaScript, TypeScript, Rust and curl.

Get started

Get a key and send your first permit request with the quick start guide.