!

Your property data was correct in March

Photo of Alex Wilkinson
Alex Wilkinson
CEO of Houski
2026-07-04

Every property dataset is correct on the day it is built. That is the problem with buying one.

The pitch for a static export is easy to say yes to. You get a file, the file has everything in it, and the price is a number you can put in a budget. Six months later the file is still sitting there and it is still full of data, which is exactly what makes it dangerous. Nothing in a comma-separated values file tells you it has gone stale. It just quietly stops describing the country while your team keeps making decisions from it.

What changes underneath you

Country-scale property data is not a photograph. It is a river.

Houses get built. Whole subdivisions appear. Your file has a field where the address should be.

Permits get pulled. Somebody adds a suite, finishes a basement, replaces a furnace, tears the place down. Every one of those changes what the property is.

Listings come and go. A listing that was active in March is sold, expired, or relisted at a different price. The stale ones are worse than missing ones, because they look live.

Assessments reroll. Municipalities reassess on their own schedules, and the assessed value you have is from whichever roll happened to be current when your file was cut.

Demographic data refreshes, and boundaries move. This is the one that catches engineering teams. It is not just that the numbers change. The shapes underneath them change. The area you joined an address to in March may not exist in the same form now, so even a perfect join goes wrong without anyone touching the code.

Estimates move because the model improves. Values, scores, and derived fields get better as the inputs and the methods get better. A two year old estimate is not just old, it is worse than what the same model would say today.

None of this is dramatic on any given day. That is precisely why it wins. Drift does not announce itself.

The number that decides buy versus build

When teams price building this in-house, they price the build. A few weeks to get the sources, a few more to conform them, some time for the spatial joins, and then it works. Call it a quarter. That number is usually roughly right, and it is the wrong number.

The real cost is that somebody now owns it. Forever. When a source changes its format, they fix it. When boundaries get redrawn, they rerun the joins and reconcile what moved. When a province publishes on a new schedule, they find out because something broke. When the person who wrote it leaves, somebody inherits a pipeline they did not build and do not want.

That is not a quarter of work. That is a standing line item in your engineering budget, forever, to keep a thing correct that is not your product.

The build is the cheap part. The rerun is the bill.

What Houski does instead

19 million-plus Canadian properties, 200-plus fields, cleaned, conformed, and standardised across every province, updated daily.

Not refreshed when we get around to it. Not versioned annually. Daily. The join gets rerun, the sources get reconciled, the estimates get recomputed, and you wake up and it is still right. That is the entire product. The fields are table stakes - anyone can list fields on a marketing page. Keeping 19 million rows correct while the country changes underneath them is the thing that is hard, and it is the thing you are actually paying for.

Do not take our word for it, ask the data

Here is what I would push on if I were evaluating a property data vendor: make them tell you, per field, per row, whether the number is a fact or a guess.

Most will not, because most cannot. We ship it in the response.

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('city', 'calgary');
    url.searchParams.set('country_abbreviation', 'ca');
    url.searchParams.set('province_abbreviation', 'ab');
    url.searchParams.set('results_per_page', '5');
    url.searchParams.set('select', 'address,construction_year,construction_year_source,land_area_sq_m,land_area_sq_m_source,interior_sq_m,interior_sq_m_source');

    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,
  "cost_cents": 1.75,
  "data": [
    {
      "address": "31 Hawkside Park NW",
      "construction_year": 1988,
      "construction_year_source": "Document",
      "interior_sq_m": 126.34708404541016,
      "interior_sq_m_source": "Estimate",
      "land_area_sq_m": 13583.0,
      "land_area_sq_m_source": "Document",
      "property_id": "10000f97f5cb7b9f"
    },
    {
      "address": "6 1744 7 Street SW",
      "construction_year": 2006,
      "construction_year_source": "Estimate",
      "interior_sq_m": 125.882568359375,
      "interior_sq_m_source": "Estimate",
      "land_area_sq_m": 512.0,
      "land_area_sq_m_source": "Estimate",
      "property_id": "10004f7afe0c1946"
    },
    {
      "address": "384 Copperpond Landng SE",
      "construction_year": 2006,
      "construction_year_source": "Estimate",
      "interior_sq_m": 125.882568359375,
      "interior_sq_m_source": "Estimate",
      "land_area_sq_m": 512.0,
      "land_area_sq_m_source": "Estimate",
      "property_id": "10007f9761f49940"
    },
    {
      "address": "239 Dalhurst Way NW",
      "construction_year": 1971,
      "construction_year_source": "Document",
      "interior_sq_m": 108.46339416503906,
      "interior_sq_m_source": "Estimate",
      "land_area_sq_m": 724.0999755859375,
      "land_area_sq_m_source": "Document",
      "property_id": "100086f6bc064d3f"
    },
    {
      "address": "52 Cedargrove Way SW",
      "construction_year": 1984,
      "construction_year_source": "Document",
      "interior_sq_m": 136.4734344482422,
      "interior_sq_m_source": "Estimate",
      "land_area_sq_m": 430.2999877929687,
      "land_area_sq_m_source": "Document",
      "property_id": "1000c277cd905d3b"
    }
  ],
  "error": "",
  "pagination": {
    "current_page": 1,
    "has_next_page": true,
    "has_previous_page": false,
    "page_total": 136188
  },
  "price_quote": false,
  "result_total": 680936,
  "time_ms": 76,
  "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"
  }
}

Every field with a matching source field tells you where that specific value came from. Document means it is backed by a real record. Estimate means we modelled it. Not for the dataset as a whole. For that field, on that property.

Run that query and you will see construction_year come back as Document on most rows and Estimate on some, because for some properties the record exists and for others it does not, and pretending otherwise would be a lie that is hard to detect and expensive to believe. You will see interior_sq_m come back as Estimate a lot, because floor area is genuinely hard to source and we would rather say so.

That is the honesty check. A vendor that hands you one blended number per field, with no way to tell which rows are real, is asking you to trust an average of facts and guesses. You cannot audit that, you cannot weight it in a model, and you cannot tell your underwriters which values to lean on.

The fields carrying source information today are construction_year, interior_sq_m, land_area_sq_m, zoning, community, postal_code, and the residential, commercial, and farmland flags. The fields reference also marks which fields are estimates by nature - roof_material_install_year is modelled for everyone, and says so.

Try any of that with a file you bought once.

Fresh does not have to mean an API call

The freshness argument sounds like it is an argument for querying live. It is not.

Plenty of work genuinely wants a file - training a model, loading a warehouse, running an analysis, cutting a mail campaign. You should use a file for those. The point is that the file should be regenerated, not archived.

That is how the datasets page works. Around 94 pre-built datasets across 15 categories, each one a filter someone already wrote, exported to comma-separated values or JSON, dropping straight into Snowflake, BigQuery, Databricks, Power BI, or a notebook. They update daily. The list you pull for the spring campaign is not last year's list with the movers still on it.

So the real choice is not file versus API. It is maintained versus abandoned. Both of our delivery shapes are maintained. A file you bought once is abandoned the moment it lands on your disk, and it starts rotting immediately, and nothing about it will ever tell you.

For which shape fits which job, we wrote that up separately in bulk datasets versus the API.

The test to run on any vendor

  1. Ask when each field was last updated. Not the dataset. The field. If the answer is a sentence rather than a value in the response, that is your answer.
  2. Ask what happens when boundaries move. If they do not know what you mean, they have not hit it yet, which means they have not been doing this long.
  3. Ask which fields are measured and which are modelled. A vendor that will not distinguish is hoping you do not ask.
  4. Ask what a rebuild costs you. With a file, it is a new purchase and a new integration. That is the cost you will pay again and again, and it is not on the quote.

Getting started

  1. Sign up for API access
  2. Pull the same query twice, a month apart, and diff it. That is the demo that matters.
  3. Log the date fields so freshness lives in your monitoring rather than in your assumptions
  4. Pick your shape - live queries, daily datasets, or a custom dataset on your own schedule

Data does not fail loudly. It fails by staying plausible while the world moves. The only defence is a rerun you do not have to own. Explore the API or browse the datasets.