Every property insurer writing across Canada is fighting the same two-front war. Reinsurance treaties keep tightening on natural catastrophe exposure, and customers keep arriving with a non-renewal letter from a competitor and a demand to be covered. Winning that war means pricing each address on what actually threatens it, not on the blunt average of a postal code.
The trouble is that the four perils that drive Canadian catastrophe losses do not move together. A condo on the Vancouver waterfront has almost no tornado exposure and almost no hurricane exposure, but it sits on one of the most seismically active stretches of the country. A bungalow outside Windsor has the opposite profile. A house on a Calgary floodplain and a house four blocks uphill share a forward sortation area and have nothing in common when the river rises.
Houski carries a separate score for each of the perils that matter in Canada. This post walks through five of them, score_flood, score_fire, score_earthquake, score_hurricane, and score_tornado, how to pull all five for a single address in one request, how to aggregate them across communities, and how to turn the combined picture into a tiered underwriting position.
What the catastrophe scores measure
Each score is an integer from 0 to 10 that lives on every property in the database. Higher is always safer. A 10 means the modelled hazard for that peril is very low at that location. A 2 means the hazard is high and an underwriter should look twice.
The scores describe the area around a property, not a guarantee about the individual structure. A low flood score does not mean this exact lot floods. It means the surrounding area floods readily enough that roads, utilities, services, value, and insurance across the whole area are affected. Treat a low score as a prompt to look closer, not as a verdict.
Because higher is safer, every filter reads the same intuitive way across all five perils. score_earthquake_gte=8 returns the seismically calm end of the distribution. score_flood_lte=3 returns the properties most exposed to flooding. The same operator means the same thing whether the peril is fire or hurricane.
The five perils:
- score_flood. Modelled flood hazard at the property. Low scores cluster along river valleys and coastal lowlands.
- score_fire. Modelled wildfire exposure from the area's fire history and surrounding landscape. Low scores cluster in the interior and the boreal belt.
- score_earthquake. Seismic hazard. Low scores concentrate on the British Columbia coast and the St Lawrence valley.
- score_hurricane. Exposure to tropical storm and post-tropical wind. Low scores sit on the Atlantic coast.
- score_tornado. Exposure to severe convective storms. Low scores run through southern Ontario and the southern Prairies.
Pulling all five perils for one address
Start by resolving a street address to a property_id with the search endpoint. The block below is a live call against the real API. The request and the response are generated every time this page renders, so they always reflect current data:
const houski_data = async (): Promise<SearchResponse> => {
// You must copy the SearchResponse type declarations from the
// Houski API documentation to strongly type the response
const url = new URL('https://api.houski.ca/search');
url.searchParams.set('api_key', 'YOUR_API_KEY');
url.searchParams.set('max_results', '1');
url.searchParams.set('query', '1055+Canada+Pl+Vancouver+BC');
const response = await fetch(url);
const data = await response.json();
return data;
}
(async () => {
let data: SearchResponse = await houski_data();
// Log the response
console.log(data);
})();
{
"cache_hit": true,
"cost_cents": 0.019999999552965164,
"data": [
{
"address": "1055 Canada Place",
"property_id": "61f7c61a0766a092"
}
],
"error": "",
"match_meta": [
{
"match_value": 1.0,
"property_id": "61f7c61a0766a092"
}
],
"price_quote": false,
"result_total": 1,
"time_ms": 1789
}
Then pull all five catastrophe scores for that property_id in a single request:
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('property_id_eq', '61f7c61a0766a092');
url.searchParams.set('select', 'address,city,score_flood,score_fire,score_earthquake,score_hurricane,score_tornado');
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": false,
"cost_cents": 0.5299999713897705,
"data": [
{
"address": "1055 Canada Place",
"city": "Vancouver",
"property_id": "61f7c61a0766a092",
"score_earthquake": 4,
"score_fire": 8,
"score_flood": 5,
"score_hurricane": 10,
"score_tornado": 10
}
],
"error": "",
"pagination": {
"current_page": 1,
"has_next_page": false,
"has_previous_page": false,
"page_total": 1
},
"price_quote": false,
"result_total": 1,
"time_ms": 25,
"ui_info": {
"address": "1055 Canada Place",
"address_link": "ca/bc/vancouver/downtown/1055-canada-place",
"address_slug": "1055-canada-place",
"city": "Vancouver",
"city_id": "3d4b9ab2229768be",
"city_link": "ca/bc/vancouver",
"city_slug": "vancouver",
"community": "Downtown",
"community_id": "e7573c4d7d9f0078",
"community_link": "ca/bc/vancouver/downtown",
"community_slug": "downtown",
"country": "Canada",
"country_abbreviation": "CA",
"country_abbreviation_id": "9ace2b6431b7f1be",
"country_abbreviation_link": "ca",
"country_slug": "canada",
"property_id": "61f7c61a0766a092",
"province": "British Columbia",
"province_abbreviation": "BC",
"province_abbreviation_id": "84b27e64bc119a2",
"province_abbreviation_link": "ca/bc",
"province_slug": "british_columbia"
}
}
One waterfront tower, five very different numbers. The point is the spread between the perils, not any single reading. Whatever each score comes back as, a Pacific coast address will usually show one or two perils that need real pricing attention while the rest read calm. A single forward sortation area model would have averaged all of that into one meaningless figure.
Set select to only the perils you price. Every field you do not request is a field you do not pay for.
Aggregating a single peril across a city
The aggregate endpoint takes the same filters as the property endpoint but returns one number instead of rows. It is how you compare whole markets without paying per property.
Median seismic safety for Victoria, a city built on real seismic hazard:
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('aggregation', 'median');
url.searchParams.set('api_key', 'YOUR_API_KEY');
url.searchParams.set('city', 'Victoria');
url.searchParams.set('country_abbreviation', 'ca');
url.searchParams.set('field', 'score_earthquake');
url.searchParams.set('province_abbreviation', 'bc');
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": false,
"cost_cents": 1.0,
"data": [
{
"aggregation": "median",
"field": "score_earthquake",
"value": "3"
}
],
"error": "",
"price_quote": false,
"time_ms": 48
}
Median hurricane safety for Halifax:
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('aggregation', 'median');
url.searchParams.set('api_key', 'YOUR_API_KEY');
url.searchParams.set('city', 'Halifax');
url.searchParams.set('country_abbreviation', 'ca');
url.searchParams.set('field', 'score_hurricane');
url.searchParams.set('province_abbreviation', 'ns');
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": false,
"cost_cents": 1.0,
"data": [
{
"aggregation": "median",
"field": "score_hurricane",
"value": "6"
}
],
"error": "",
"price_quote": false,
"time_ms": 51
}
Count of high seismic exposure properties (score 3 and below) inside Victoria:
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('aggregation', 'count');
url.searchParams.set('api_key', 'YOUR_API_KEY');
url.searchParams.set('city', 'Victoria');
url.searchParams.set('country_abbreviation', 'ca');
url.searchParams.set('field', 'score_earthquake');
url.searchParams.set('province_abbreviation', 'bc');
url.searchParams.set('score_earthquake_lte', '3');
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": false,
"cost_cents": 1.0,
"data": [
{
"aggregation": "count",
"field": "score_earthquake",
"value": "37010"
}
],
"error": "",
"price_quote": false,
"time_ms": 51
}
One request, one number, no per-property charge for scanning the whole city.
A cross-peril city comparison
Run the median aggregate for each peril across the markets you write in and you get a catastrophe fingerprint per city. Each cell is the indicative median score from 0 to 10 with a plain-language read in brackets so the direction is never ambiguous. (safe) is low modelled hazard, (ok) is moderate, (risky) is the end an underwriter should look at twice. Numbers shift as the model updates and as new construction is added, and higher is always safer:
| City | Province | Flood | Fire | Earthquake | Hurricane | Tornado |
|---|---|---|---|---|---|---|
| Vancouver | BC | 5 (ok) | 8 (safe) | 3 (risky) | 8 (safe) | 8 (safe) |
| Victoria | BC | 8 (safe) | 8 (safe) | 1 (risky) | 8 (safe) | 8 (safe) |
| Halifax | NS | 5 (ok) | 8 (safe) | 8 (safe) | 5 (ok) | 8 (safe) |
| Windsor | ON | 5 (ok) | 8 (safe) | 8 (safe) | 8 (safe) | 5 (ok) |
| Calgary | AB | 5 (ok) | 5 (ok) | 8 (safe) | 8 (safe) | 5 (ok) |
| Kelowna | BC | 5 (ok) | 3 (risky) | 5 (ok) | 8 (safe) | 8 (safe) |
The point of the table is not any single cell. It is that no city is uniformly safe or uniformly exposed. Victoria is calm on four perils and alarming on the fifth. Kelowna is the mirror image. Pricing on a blended catastrophe average leaves money on the table in the calm cells and loss exposure in the alarming ones.
Building a per-peril tiered position
A workable structure for a multi-peril book uses each score independently rather than blending them:
- Preferred. The peril that dominates the region scores 8 and above, and no single peril sits below 5. Standard rates, full limits.
- Standard. The dominant peril scores 5 to 7. Standard rates with an inspection clause on the relevant peril.
- Surcharged. Any peril scores 3 to 4. Surcharge tied to that specific peril, with a mitigation attestation where one exists.
- Restricted. Any peril scores below 3. Bind only with peril-specific mitigation in place, lower limits, higher deductible.
Pull the inputs at quote time with one property request. The cost per quote is a fraction of the loss adjustment expense on a single disputed claim from a peril that should have been priced into the policy.
Combining scores with construction details
The catastrophe scores describe the situation a building sits in, not the building itself. Two properties with the same flood or fire score can carry very different total exposure once construction is factored in. The same request that pulls the scores can pull exterior_finish, roof_material, and construction_year alongside them, so you can layer a building-hardness adjustment on top in your own code. A post-2015 build with a metal roof on a moderate fire lot is a different risk from a 1970s wood-clad house on the identical lot with the identical score.
Limitations to be honest about
State these plainly to anyone relying on the numbers:
- The scores update on a schedule, not in real time. A fire perimeter or a flood event from last season is in. One from last week is not.
- Each score is a relative ranking, not an annual probability. A 4 is more exposed than an 8. It does not tell you the 4 floods once every so many years.
- The scores describe the area, not the individual structure. Site-specific drainage, elevation, or retrofits that are not yet recorded will not move the number.
- Local mitigation programs that materially change real risk may be invisible to a model built on public, government, and crowdsourced data.
For underwriting tiers, portfolio screening, and market comparison those limits are acceptable as long as you state them. For a single high-value placement, pair the scores with an inspection.
A full example: screening a book before renewal season
The workflow carriers ask about most often is the pre-renewal sweep. You have a book spread across several provinces and you want to know which policies carry a peril you have under-priced before the renewal notices go out.
The pattern is one search call per address to resolve a property_id, then one property call per property_id for the five scores. The resolve step is:
from dataclasses import dataclass
from dataclasses_json import dataclass_json
import requests
params = {}
params['api_key'] = 'YOUR_API_KEY'
params['max_results'] = '1'
params['query'] = '1055+Canada+Pl+Vancouver+BC'
response = requests.get('https://api.houski.ca/search', params=params)
if response.status_code == 200:
json_data = response.json()
print(json_data)
# You must copy the SearchResponse type declarations from the
# Houski API documentation to strongly type the response
typed_response = SearchResponse.from_dict(json_data)
# Log the response
print(typed_response)
else:
print(f'Failed to get data: {response}')
{
"cache_hit": true,
"cost_cents": 0.019999999552965164,
"data": [
{
"address": "1055 Canada Place",
"property_id": "61f7c61a0766a092"
}
],
"error": "",
"match_meta": [
{
"match_value": 1.0,
"property_id": "61f7c61a0766a092"
}
],
"price_quote": false,
"result_total": 1,
"time_ms": 1789
}
And the score pull against the returned property_id is:
from dataclasses import dataclass
from dataclasses_json import dataclass_json
import requests
params = {}
params['api_key'] = 'YOUR_API_KEY'
params['property_id_eq'] = '61f7c61a0766a092'
params['select'] = 'score_flood,score_fire,score_earthquake,score_hurricane,score_tornado'
response = requests.get('https://api.houski.ca/properties', params=params)
if response.status_code == 200:
json_data = response.json()
print(json_data)
# You must copy the PropertiesResponse type declarations from the
# Houski API documentation to strongly type the response
typed_response = PropertiesResponse.from_dict(json_data)
# Log the response
print(typed_response)
else:
print(f'Failed to get data: {response}')
{
"cache_hit": false,
"cost_cents": 0.5199999809265137,
"data": [
{
"address": "1055 Canada Place",
"property_id": "61f7c61a0766a092",
"score_earthquake": 4,
"score_fire": 8,
"score_flood": 5,
"score_hurricane": 10,
"score_tornado": 10
}
],
"error": "",
"pagination": {
"current_page": 1,
"has_next_page": false,
"has_previous_page": false,
"page_total": 1
},
"price_quote": false,
"result_total": 1,
"time_ms": 47,
"ui_info": {
"address": "1055 Canada Place",
"address_link": "ca/bc/vancouver/downtown/1055-canada-place",
"address_slug": "1055-canada-place",
"city": "Vancouver",
"city_id": "3d4b9ab2229768be",
"city_link": "ca/bc/vancouver",
"city_slug": "vancouver",
"community": "Downtown",
"community_id": "e7573c4d7d9f0078",
"community_link": "ca/bc/vancouver/downtown",
"community_slug": "downtown",
"country": "Canada",
"country_abbreviation": "CA",
"country_abbreviation_id": "9ace2b6431b7f1be",
"country_abbreviation_link": "ca",
"country_slug": "canada",
"property_id": "61f7c61a0766a092",
"province": "British Columbia",
"province_abbreviation": "BC",
"province_abbreviation_id": "84b27e64bc119a2",
"province_abbreviation_link": "ca/bc",
"province_slug": "british_columbia"
}
}
In your own code, flag any policy where any single peril falls below your restricted threshold, sort by the worst peril ascending, and walk the list from the top. The first rows are the policies most likely to surprise you on renewal. Re-tier them, request mitigation evidence, or non-renew with enough notice to be defensible.
What to build next
Whether you want to re-tier an existing book, price a new market before you enter it, or wire catastrophe scores into a quoting flow, all three start with one property call or one aggregate call. The fields are documented, the filters compose cleanly across every peril, and scanning a whole Canadian city for a one-time analysis costs a coffee.
Get started at /api-documentation/quick-start. Pricing starts at $99 a month, and you can have a multi-peril comparison running in your terminal in under five minutes.
