Houski fills nearly every field on nearly every property. Where a public record gives a value, you get the record's value. Where no record exists, a model estimates it. For screening, ranking and triage across a whole market, that is exactly what you want: one uniform table with no holes.
Some analysis needs the opposite. A credit risk model that should not learn from another model's output, a validation set for your own estimates, a research paper whose methods section has to say where every number came from. For that work you want the recorded values and nothing else.
That is one request parameter: exclude.
Who needs record-only data
- Lenders and credit risk teams checking collateral facts and building scorecards, where an estimated input feeding an estimated output compounds error.
- Insurers and underwriting teams rating on year built, floor area and construction material, who want to know which inputs a reviewer can trace.
- Appraisal review teams who cite characteristics in a report and cannot cite a modelled number as a fact.
- Government and academic researchers measuring housing stock, where a published figure has to rest on records.
- Data science teams validating models, measuring bias, or comparing their predictions against ground truth.
- Proptech companies auditing their own estimates against public records, or showing a recorded badge in their product.
Every value already says where it came from
The main building facts, the assessment value, the property taxes, the zoning and the parcel number each carry a source column, named after the field with _source added. It holds one of four things:
- A web address: the value was read from the public record published on that page. A public record is one a government body keeps under law and publishes.
- Observed: the value was seen stated for this specific home, but not in a public record.
- Inferred: Houski worked the value out.
- User: a logged in Houski user entered it.
Here is a plain request in Calgary with two of those source columns selected:
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,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);
})();
{
"cache_hit": true,
"cost_cents": 1.1999998,
"data": [
{
"address": "518 Rochester Terrace NW",
"construction_year": 2023,
"construction_year_source": "https://data.calgary.ca/Government/Current-Year-Property-Assessments-Parcel-/4bsw-nn7w/about_data",
"interior_sq_m": 174.65627,
"interior_sq_m_source": "Inferred",
"property_id": "103f92f6cf7bbdd9"
},
{
"address": "36 Crimson Ridge Heights NW",
"construction_year": 2024,
"construction_year_source": "Inferred",
"interior_sq_m": 174.65627,
"interior_sq_m_source": "Inferred",
"property_id": "104fb85a9c002e5d"
},
{
"address": "54 Rowmont Link NW",
"construction_year": 2023,
"construction_year_source": "https://data.calgary.ca/Government/Current-Year-Property-Assessments-Parcel-/4bsw-nn7w/about_data",
"interior_sq_m": 174.65627,
"interior_sq_m_source": "Inferred",
"property_id": "1055f97323e53f25"
},
{
"address": "499 Rochester Terrace NW",
"construction_year": 2023,
"construction_year_source": "https://data.calgary.ca/Government/Current-Year-Property-Assessments-Parcel-/4bsw-nn7w/about_data",
"interior_sq_m": 174.65627,
"interior_sq_m_source": "Inferred",
"property_id": "1106094a42c3757d"
},
{
"address": "304 30 Rochester View NW",
"construction_year": 1800,
"construction_year_source": "https://data.calgary.ca/Government/Current-Year-Property-Assessments-Parcel-/4bsw-nn7w/about_data",
"interior_sq_m": 121.70197,
"interior_sq_m_source": "Inferred",
"property_id": "1198b011e799062c"
}
],
"error": "",
"pagination": {
"current_page": 1,
"has_next_page": true,
"has_previous_page": false,
"page_total": 132137
},
"price_quote": false,
"result_total": 660683,
"time_ms": 33,
"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"
}
}
The source changes from field to field on the same home, because a record that gives the year built does not always give the floor area. The fields page shows which fields carry a source column.
exclude=inferred: recorded or observed
Add exclude=inferred and every value Houski worked out comes back empty. A home whose checked values were all inferred is not returned at all.
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('exclude', 'inferred');
url.searchParams.set('province_abbreviation', 'ab');
url.searchParams.set('results_per_page', '5');
url.searchParams.set('select', 'address,construction_year,construction_year_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);
})();
{
"cache_hit": true,
"checked_fields": [
"construction_year",
"construction_year_source",
"interior_sq_m",
"interior_sq_m_source"
],
"cost_cents": 0.65000004,
"data": [
{
"address": "518 Rochester Terrace NW",
"construction_year": 2023,
"construction_year_source": "https://data.calgary.ca/Government/Current-Year-Property-Assessments-Parcel-/4bsw-nn7w/about_data",
"property_id": "103f92f6cf7bbdd9"
},
{
"address": "54 Rowmont Link NW",
"construction_year": 2023,
"construction_year_source": "https://data.calgary.ca/Government/Current-Year-Property-Assessments-Parcel-/4bsw-nn7w/about_data",
"property_id": "1055f97323e53f25"
},
{
"address": "499 Rochester Terrace NW",
"construction_year": 2023,
"construction_year_source": "https://data.calgary.ca/Government/Current-Year-Property-Assessments-Parcel-/4bsw-nn7w/about_data",
"property_id": "1106094a42c3757d"
},
{
"address": "304 30 Rochester View NW",
"construction_year": 1800,
"construction_year_source": "https://data.calgary.ca/Government/Current-Year-Property-Assessments-Parcel-/4bsw-nn7w/about_data",
"property_id": "1198b011e799062c"
},
{
"address": "95 Royston He NW",
"construction_year": 2023,
"construction_year_source": "https://data.calgary.ca/Government/Current-Year-Property-Assessments-Parcel-/4bsw-nn7w/about_data",
"property_id": "11aa161875771a70"
}
],
"error": "",
"pagination": {
"current_page": 1,
"has_next_page": true,
"has_previous_page": false,
"page_total": 104752
},
"price_quote": false,
"result_total": 523759,
"time_ms": 50,
"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"
}
}
The response lists the fields exclude checked in checked_fields. Fields without a source column, such as scores and area figures, pass through untouched, so you can still pull them on the same row.
exclude=inferred,observed: public records only
The strict form keeps only the values read from a public record, and each one carries the link to its record in its source column. This pulls Houston houses with at least three bedrooms, with bedrooms, bathrooms, floor area, year built and assessment value from public records only:
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', 'houston');
url.searchParams.set('country_abbreviation', 'us');
url.searchParams.set('exclude', 'inferred,observed');
url.searchParams.set('property_type_eq', 'House');
url.searchParams.set('province_abbreviation', 'tx');
url.searchParams.set('results_per_page', '5');
url.searchParams.set('select', 'address,bedroom,bedroom_source,bathroom_full,bathroom_half,interior_sq_m,construction_year,assessment_value,assessment_value_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);
})();
{
"cache_hit": true,
"checked_fields": [
"bedroom",
"bedroom_source",
"bathroom_full",
"bathroom_half",
"interior_sq_m",
"construction_year",
"assessment_value",
"assessment_value_source"
],
"cost_cents": 3.1999998,
"data": [
{
"address": "10922 Gloria Drive",
"assessment_value": 271816,
"assessment_value_source": "https://hcad.org/pdata/pdata-property-downloads.html",
"bathroom_full": 3,
"bathroom_half": 0,
"bedroom": 5,
"bedroom_source": "https://hcad.org/pdata/pdata-property-downloads.html",
"construction_year": 1950,
"interior_sq_m": 212.65506,
"property_id": "11fae10a6503e1a8"
},
{
"address": "4010 San Pedro Street",
"assessment_value": 192634,
"assessment_value_source": "https://hcad.org/pdata/pdata-property-downloads.html",
"bathroom_full": 2,
"bathroom_half": 0,
"bedroom": 4,
"bedroom_source": "https://hcad.org/pdata/pdata-property-downloads.html",
"construction_year": 1971,
"interior_sq_m": 120.402336,
"property_id": "122da2a6e061524f"
},
{
"address": "4320 Banner Drive",
"assessment_value": 215869,
"assessment_value_source": "https://hcad.org/pdata/pdata-property-downloads.html",
"bathroom_full": 2,
"bathroom_half": 0,
"bedroom": 4,
"bedroom_source": "https://hcad.org/pdata/pdata-property-downloads.html",
"construction_year": 1948,
"interior_sq_m": 163.50935,
"property_id": "1269934b76f238eb"
},
{
"address": "11026 Penelope Drive",
"assessment_value": 149617,
"assessment_value_source": "https://hcad.org/pdata/pdata-property-downloads.html",
"bathroom_full": 2,
"bathroom_half": 0,
"bedroom": 3,
"bedroom_source": "https://hcad.org/pdata/pdata-property-downloads.html",
"construction_year": 1950,
"interior_sq_m": 112.59849,
"property_id": "134381e5635a319c"
},
{
"address": "3606 San Pedro Street",
"assessment_value": 181375,
"assessment_value_source": "https://hcad.org/pdata/pdata-property-downloads.html",
"bathroom_full": 2,
"bathroom_half": 0,
"bedroom": 4,
"bedroom_source": "https://hcad.org/pdata/pdata-property-downloads.html",
"construction_year": 1955,
"interior_sq_m": 180.97513,
"property_id": "140a34d4c4551714"
}
],
"error": "",
"pagination": {
"current_page": 1,
"has_next_page": true,
"has_previous_page": false,
"page_total": 118849
},
"price_quote": false,
"result_total": 594245,
"time_ms": 77,
"ui_info": {
"city": "Houston",
"city_id": "e0af0bcd1feddfcf",
"city_link": "us/tx/houston",
"city_slug": "houston",
"country": "United States",
"country_abbreviation": "US",
"country_abbreviation_id": "c148472ce4bd39d",
"country_abbreviation_link": "us",
"country_slug": "united_states",
"province": "Texas",
"province_abbreviation": "TX",
"province_abbreviation_id": "ccdfc6d473cdc27a",
"province_abbreviation_link": "us/tx",
"province_slug": "texas"
}
}
Two details matter for analysis. First, a filter on a checked field matches only the values exclude keeps, so a home kept by bedroom_gte=3 always shows a recorded bedroom count. Your filter never quietly matches an inferred value that the response then hides. Second, related fields share a source column: bathroom_full and bathroom_half share bathroom_total_source, and the assessment's land and building parts and its year share assessment_value_source.
The same works outside North America. This pulls Manchester homes with their floor area and main heating from public records only:
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', 'manchester');
url.searchParams.set('country_abbreviation', 'gb');
url.searchParams.set('exclude', 'inferred,observed');
url.searchParams.set('province_abbreviation', 'eng');
url.searchParams.set('results_per_page', '5');
url.searchParams.set('select', 'address,interior_sq_m,interior_sq_m_source,heating_type_first,heating_type_first_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);
})();
{
"cache_hit": true,
"checked_fields": [
"interior_sq_m",
"interior_sq_m_source",
"heating_type_first",
"heating_type_first_source"
],
"cost_cents": 1.1999998,
"data": [
{
"address": "22 Scotland Hall Road",
"heating_type_first": "Hot water",
"heating_type_first_source": "https://get-energy-performance-data.communities.gov.uk/",
"interior_sq_m": 76.0,
"interior_sq_m_source": "https://get-energy-performance-data.communities.gov.uk/",
"property_id": "10009495e0a17ff3"
},
{
"address": "73 Meadow Court Hackness Road",
"heating_type_first": "In-floor",
"heating_type_first_source": "https://get-energy-performance-data.communities.gov.uk/",
"interior_sq_m": 45.0,
"interior_sq_m_source": "https://get-energy-performance-data.communities.gov.uk/",
"property_id": "1000de93a847e754"
},
{
"address": "31 Cundiff Road",
"heating_type_first": "Hot water",
"heating_type_first_source": "https://get-energy-performance-data.communities.gov.uk/",
"interior_sq_m": 72.0,
"interior_sq_m_source": "https://get-energy-performance-data.communities.gov.uk/",
"property_id": "10017063a86368f9"
},
{
"address": "28 Radnor Street",
"heating_type_first": "Hot water",
"heating_type_first_source": "https://get-energy-performance-data.communities.gov.uk/",
"interior_sq_m": 77.0,
"interior_sq_m_source": "https://get-energy-performance-data.communities.gov.uk/",
"property_id": "1002404f05e9ef25"
},
{
"address": "9 114 Boundary Lane",
"heating_type_first": "Radiant",
"heating_type_first_source": "https://get-energy-performance-data.communities.gov.uk/",
"interior_sq_m": 92.0,
"interior_sq_m_source": "https://get-energy-performance-data.communities.gov.uk/",
"property_id": "10030a28ead3c2e6"
}
],
"error": "",
"pagination": {
"current_page": 1,
"has_next_page": true,
"has_previous_page": false,
"page_total": 41906
},
"price_quote": false,
"result_total": 209529,
"time_ms": 28,
"ui_info": {
"city": "Manchester",
"city_id": "311dc539c85e1c6b",
"city_link": "gb/eng/manchester",
"city_slug": "manchester",
"country": "United Kingdom",
"country_abbreviation": "GB",
"country_abbreviation_id": "90882893598cc98f",
"country_abbreviation_link": "gb",
"country_slug": "united_kingdom",
"province": "England",
"province_abbreviation": "ENG",
"province_abbreviation_id": "5336aa935fab8e24",
"province_abbreviation_link": "gb/eng",
"province_slug": "england"
}
}
Summary statistics from records alone
The aggregate endpoint takes exclude too, once per aggregation in a batch. This puts the median floor area of Houston houses from every value next to the median from public record values only:
const houski_data = async (): Promise<AggregateResponse> => {
// You must copy the AggregateResponse type declarations from the
// Houski API documentation to strongly type the response
const url = new URL('https://api.houski.ca/aggregate');
url.searchParams.set('agg0_aggregation', 'median');
url.searchParams.set('agg0_city', 'houston');
url.searchParams.set('agg0_country_abbreviation', 'us');
url.searchParams.set('agg0_field', 'interior_sq_m');
url.searchParams.set('agg0_property_type_eq', 'House');
url.searchParams.set('agg0_province_abbreviation', 'tx');
url.searchParams.set('agg1_aggregation', 'median');
url.searchParams.set('agg1_city', 'houston');
url.searchParams.set('agg1_country_abbreviation', 'us');
url.searchParams.set('agg1_exclude', 'inferred,observed');
url.searchParams.set('agg1_field', 'interior_sq_m');
url.searchParams.set('agg1_property_type_eq', 'House');
url.searchParams.set('agg1_province_abbreviation', 'tx');
url.searchParams.set('api_key', 'YOUR_API_KEY');
const response = await fetch(url);
const data = await response.json();
return data;
}
(async () => {
let data: AggregateResponse = await houski_data();
// Log the response
console.log(data);
})();
{
"cache_hit": true,
"checked_fields": [
"interior_sq_m"
],
"cost_cents": 2.0,
"data": [
{
"aggregation": "median",
"field": "interior_sq_m",
"value": "169.455"
},
{
"aggregation": "median",
"field": "interior_sq_m",
"value": "173.914"
}
],
"error": "",
"price_quote": false,
"time_ms": 56,
"values_used": [
773896,
664211
]
}
values_used says how many values each figure was worked out from. Report it beside the figure. A median from a handful of records is not a median of the city.
Record-only data is not a random sample
This is the part to put in your methods section. Records exist where an authority publishes them, so the homes that keep a recorded value are not a random draw from the market. Some places publish floor area and others do not, and some publish a field for one kind of property and not another.
Before you train or test on a record-only subset, compare it with the full table:
- Pull the same fields with and without exclude and compare the distributions by city and property type.
- Use values_used on the aggregate endpoint to see how much of each area the records cover.
- Where coverage is thin, weight the records, or keep the inferred values and carry the source column into your model as a feature, so the model learns how much to trust each input.
The third option is often the best one. A model that knows which inputs were recorded uses the inferred values where they help and leans on the records where they exist.
You pay only for what comes back
A value that exclude leaves out is not billed, and neither is a home whose checked values are all left out. Add price_quote=true to see the cost of a strict request before you run it:
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', 'houston');
url.searchParams.set('country_abbreviation', 'us');
url.searchParams.set('exclude', 'inferred,observed');
url.searchParams.set('price_quote', 'true');
url.searchParams.set('property_type_eq', 'House');
url.searchParams.set('province_abbreviation', 'tx');
url.searchParams.set('results_per_page', '5');
url.searchParams.set('select', 'address,bedroom,bedroom_source,bathroom_full,bathroom_half,interior_sq_m,construction_year,assessment_value,assessment_value_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);
})();
{
"cache_hit": true,
"checked_fields": [
"bedroom",
"bedroom_source",
"bathroom_full",
"bathroom_half",
"interior_sq_m",
"construction_year",
"assessment_value",
"assessment_value_source"
],
"cost_cents": 3.1999998,
"data": [],
"error": "",
"pagination": {
"current_page": 1,
"has_next_page": true,
"has_previous_page": false,
"page_total": 118849
},
"price_quote": true,
"result_total": 594245,
"time_ms": 77,
"ui_info": {}
}
A note on training
Houski's terms of service allow training a model on Houski data only when the data was bought for that purpose. For a training set, ask for a custom dataset and say you want recorded values only, with their source columns. Everyday API pulls are for analysis, validation and your product.
Getting started
- Get an API key
- Read how exclude works, including the rules for filters, sorting and the checked fields
- Check the fields page for which fields carry a source column
- Pull one city twice, with and without exclude, and compare before you build on the subset
Estimates make the table complete. Records make the analysis defensible. With one parameter you choose which one each request needs. Get an API key.
