Returns property pins and clusters for interactive maps.
Also generates heatmap data as a grid of cells whose intensity reflects property concentration. Filtering supports list price, sale price, for sale status, bedrooms, and more.
Supports standard filtering and sorting.
Clusters and heatmaps cost $0.00001 per cluster or cell returned. A default heatmap of 1000 cells is $0.01, the same as a single value from the aggregate endpoint, which is the fair comparison because a heatmap is an aggregation laid out over space.
You are billed for what comes back, never for how many properties were searched to build it. A heatmap of a whole province costs the same as a heatmap of one neighbourhood at the same cell limit, so a user panning and zooming your map does not run up a bill.
Property mode is different. When the map returns actual property records it is billed per field per row exactly like the properties endpoint, so keep the select parameter tight.
There is no cap on how many properties a request may match, so a map of the whole country is a normal request. The polygon parameter is capped at 100 points, because every point is tested against every property the request reaches. Simplify a hand-drawn shape before sending it, or use a bounding box instead. Set abort_over yourself if you want a request to give up past a certain number of matches.
| Name | Required | Type | Description |
|---|---|---|---|
| api_key | Yes | UUID v4 | Your API key for authorization |
| cluster | No | Boolean (default: false) | Manual control of whether the data returned should be in pins or clusters. |
| cluster_over | No | Integer | Automatically return clusters instead of pins when there are more results than this. |
| heatmap | No | Boolean (default: false) | Return heatmap data instead of pins. |
| heatmap_cell_limit | No | Integer | The max number of heatmap cells returned (default: 1000, max: 10000) |
| heatmap_aggregation_field | No | String | Field to use for heatmap intensity calculation. When not provided, density of properties is used. |
| heatmap_aggregation_type | No | String (default: 'mean') | aggregation method for heatmap values. Possible values: 'mean', 'median', 'sum' |
| heatmap_aggregation_invert_intensity | No | Boolean (default: false) | When true, inverts the intensity values (1.0 - intensity) to make lower values appear as higher intensity |
| abort_over | No | Integer | Abort request if number of results exceeds this value (cost control). |
| polygon | No | Polygon filter string | Get results inside a polygon. Maximum 100 points. Pins and clusters accept a polygon, but a heatmap needs the bounding box. |
| bbox_ne_lat | No | Float | Northeast latitude for bounding box. A heatmap needs all four bounding box values, and a polygon alone is refused. |
| bbox_ne_lng | No | Float | Northeast longitude for bounding box. |
| bbox_sw_lat | No | Float | Southwest latitude for bounding box. |
| bbox_sw_lng | No | Float | Southwest longitude for bounding box. |
| select | No | String - see selecting | The fields to return for each pin. Defaults to latitude, longitude, and property_id |
| results_per_page | No | Integer | Number of results per page (default: 6, max: 1000). Ignored when clusters or a heatmap are returned. A pins response can hold at most 10,000 values, counted as rows times selected fields. |
| page | No | Integer | The page number to retrieve results from, starting at 1 |
Heatmaps can visualize property density or numeric attributes.
Set heatmap=true without heatmap_aggregation_field to get a density heatmap where each cell's intensity is its property concentration.
Pair heatmap=true with heatmap_aggregation_field to map numeric values like price, square footage, or bedrooms.
Choose how cell values aggregate:
By default higher values mean higher intensity. Set heatmap_aggregation_invert_intensity=true to flip this - useful for affordability views where low values are the target.
Type declarations are available at the bottom of this page.
| Name | Type | Description |
|---|---|---|
| aborted | Boolean | Indicates if the request was aborted due to exceeding the abort_over limit |
| cache_hit | Boolean | Indicates if the data was retrieved from the cache |
| cost_cents | Float | Cost of the API call in cents |
| data | Object | Contains the map data in 'clusters', 'properties', or 'heatmap' arrays depending on the request type |
| error | String | Details about the error. Empty if no error |
| is_properties | Boolean | Indicates if the returned data contains individual properties |
| is_clusters | Boolean | Indicates if the returned data contains clustered properties |
| is_heatmap | Boolean | Indicates if the returned data contains heatmap cells |
| is_heatmap_aggregation | Boolean (nullable) | When present, indicates if the heatmap uses a field for aggregation (true) or just property density (false) |
| is_heatmap_aggregation_invert_intensity | Boolean (nullable) | When present, indicates if heatmap intensity values are inverted (1.0 - value) |
| heatmap_aggregation_type | String (nullable) | When present, indicates the type of aggregation used: 'mean', 'median', or 'sum' |
| heatmap_aggregation_max_value | Float (nullable) | When present, the highest cell value in the heatmap, before normalization |
| heatmap_aggregation_min_value | Float (nullable) | When present, the lowest cell value in the heatmap, before normalization |
| heatmap_aggregation_field | String (nullable) | When present, the field name used for heatmap aggregation |
| pagination | Object | Pagination information |
| price_quote | Boolean | Indicates if this is a price quote request |
| result_total | Integer | Total number of results |
| time_ms | Integer | Time taken for the request to complete in milliseconds |
The data field varies by request type:
| Name | Type | Description |
|---|---|---|
| properties | Array<Property> | Array of individual property objects when is_properties is true |
| clusters | Array<Cluster> | Array of cluster objects when is_clusters is true |
| heatmap | Array<HeatPoint> | Array of heatmap points when is_heatmap is true |
Each property contains fields chosen via select. Over 400 fields available.
See the Fields documentation for the full list.
Grouped properties for map display:
| Name | Type | Description |
|---|---|---|
| latitude | Float | Center latitude coordinate of the cluster |
| longitude | Float | Center longitude coordinate of the cluster |
| number_of_properties | Integer | Number of properties grouped in this cluster |
Cells in a heatmap:
| Name | Type | Description |
|---|---|---|
| latitude | Float | Center latitude coordinate of the heatmap cell |
| longitude | Float | Center longitude coordinate of the heatmap cell |
| intensity | Float | Normalized intensity value (0.0 to 1.0) for heatmap visualization |
| number_of_properties | Integer | Number of properties in this heatmap cell |
| aggregation_value | Float (nullable) | Raw aggregated value before normalization (when using field-based heatmaps) |
Pagination info:
| Name | Type | Description |
|---|---|---|
| current_page | Integer | Current page number, starting at 1 |
| has_next_page | Boolean | Whether there is a page after this one |
| has_previous_page | Boolean | Whether there is a page before this one |
| page_total | Integer | Total number of pages |
Select the programming language you want to display the code examples in.
curl -X GET "http://127.0.0.1:8080/map?abort_over=500000&api_key=YOUR_API_KEY&bbox_ne_lat=51.17345&bbox_ne_lng=-113.96269&bbox_sw_lat=50.87234&bbox_sw_lng=-114.22313&bedroom_gte=4&cluster_over=1000"
const houski_map_data_bounding_box = async (): Promise<MapResponse> => {
// You must copy the MapResponse type declarations from the
// Houski API documentation to strongly type the response
const url = new URL('http://127.0.0.1:8080/map');
url.searchParams.set('abort_over', '500000');
url.searchParams.set('api_key', 'YOUR_API_KEY');
url.searchParams.set('bbox_ne_lat', '51.17345');
url.searchParams.set('bbox_ne_lng', '-113.96269');
url.searchParams.set('bbox_sw_lat', '50.87234');
url.searchParams.set('bbox_sw_lng', '-114.22313');
url.searchParams.set('bedroom_gte', '4');
url.searchParams.set('cluster_over', '1000');
const response = await fetch(url);
const data = await response.json();
return data;
}
(async () => {
let data: MapResponse = await houski_map_data_bounding_box();
// Log the response
console.log(data);
})();
{
"aborted": false,
"cache_hit": true,
"cost_cents": 0.010000000707805157,
"data": {
"clusters": [
{
"latitude": 50.93985748291016,
"longitude": -114.10911560058594,
"number_of_properties": 9914
},
{
"latitude": 51.01362609863281,
"longitude": -114.10770416259766,
"number_of_properties": 9314
},
{
"latitude": 50.89984130859375,
"longitude": -114.04508972167967,
"number_of_properties": 6457
},
{
"latitude": 50.9124641418457,
"longitude": -113.98648834228516,
"number_of_properties": 6512
},
{
"latitude": 51.04230117797851,
"longitude": -114.18905639648438,
"number_of_properties": 12229
},
{
"latitude": 51.07591247558594,
"longitude": -114.09352111816406,
"number_of_properties": 7676
},
{
"latitude": 50.95205307006836,
"longitude": -114.04085540771484,
"number_of_properties": 8198
},
{
"latitude": 51.07578659057617,
"longitude": -113.97901916503906,
"number_of_properties": 5080
},
{
"latitude": 51.126487731933594,
"longitude": -114.16844940185548,
"number_of_properties": 16369
},
{
"latitude": 51.146297454833984,
"longitude": -114.08525848388672,
"number_of_properties": 7698
}
],
"heatmap": [],
"properties": []
},
"error": "",
"heatmap_aggregation_field": null,
"heatmap_aggregation_max_value": null,
"heatmap_aggregation_min_value": null,
"heatmap_aggregation_type": null,
"is_clusters": true,
"is_heatmap": false,
"is_heatmap_aggregation": null,
"is_heatmap_aggregation_invert_intensity": null,
"is_properties": false,
"pagination": {
"current_page": 1,
"has_next_page": false,
"has_previous_page": false,
"page_total": 1
},
"price_quote": false,
"result_total": 10,
"time_ms": 92
}
curl -X GET "http://127.0.0.1:8080/map?api_key=YOUR_API_KEY&bbox_ne_lat=51.17345&bbox_ne_lng=-113.96269&bbox_sw_lat=50.87234&bbox_sw_lng=-114.22313&heatmap=true&heatmap_cell_limit=10"
const houski_map_data_basic_heatmap = async (): Promise<MapResponse> => {
// You must copy the MapResponse type declarations from the
// Houski API documentation to strongly type the response
const url = new URL('http://127.0.0.1:8080/map');
url.searchParams.set('api_key', 'YOUR_API_KEY');
url.searchParams.set('bbox_ne_lat', '51.17345');
url.searchParams.set('bbox_ne_lng', '-113.96269');
url.searchParams.set('bbox_sw_lat', '50.87234');
url.searchParams.set('bbox_sw_lng', '-114.22313');
url.searchParams.set('heatmap', 'true');
url.searchParams.set('heatmap_cell_limit', '10');
const response = await fetch(url);
const data = await response.json();
return data;
}
(async () => {
let data: MapResponse = await houski_map_data_basic_heatmap();
// Log the response
console.log(data);
})();
{
"aborted": false,
"cache_hit": true,
"cost_cents": 0.009000000543892384,
"data": {
"clusters": [],
"heatmap": [
{
"aggregation_value": null,
"intensity": 0.07730590552091599,
"latitude": 50.92252731323242,
"longitude": -114.17294311523438,
"number_of_properties": 10221
},
{
"aggregation_value": null,
"intensity": 0.11552395671606064,
"latitude": 51.12326431274414,
"longitude": -113.97220611572266,
"number_of_properties": 15274
},
{
"aggregation_value": null,
"intensity": 0.2542979121208191,
"latitude": 51.02289581298828,
"longitude": -113.97220611572266,
"number_of_properties": 33622
},
{
"aggregation_value": null,
"intensity": 0.25822335481643677,
"latitude": 50.92252731323242,
"longitude": -113.97220611572266,
"number_of_properties": 34141
},
{
"aggregation_value": null,
"intensity": 0.4809212386608124,
"latitude": 51.02289581298828,
"longitude": -114.17294311523438,
"number_of_properties": 63585
},
{
"aggregation_value": null,
"intensity": 0.4885527491569519,
"latitude": 51.12326431274414,
"longitude": -114.07257843017578,
"number_of_properties": 64594
},
{
"aggregation_value": null,
"intensity": 0.5148054361343384,
"latitude": 51.12326431274414,
"longitude": -114.17294311523438,
"number_of_properties": 68065
},
{
"aggregation_value": null,
"intensity": 0.5639904737472534,
"latitude": 50.92252731323242,
"longitude": -114.07257843017578,
"number_of_properties": 74568
},
{
"aggregation_value": null,
"intensity": 1.0,
"latitude": 51.02289581298828,
"longitude": -114.07257843017578,
"number_of_properties": 132215
}
],
"properties": []
},
"error": "",
"heatmap_aggregation_field": null,
"heatmap_aggregation_max_value": null,
"heatmap_aggregation_min_value": null,
"heatmap_aggregation_type": "mean",
"is_clusters": false,
"is_heatmap": true,
"is_heatmap_aggregation": false,
"is_heatmap_aggregation_invert_intensity": false,
"is_properties": false,
"pagination": {
"current_page": 1,
"has_next_page": false,
"has_previous_page": false,
"page_total": 1
},
"price_quote": false,
"result_total": 9,
"time_ms": 60
}
curl -X GET "http://127.0.0.1:8080/map?api_key=YOUR_API_KEY&bbox_ne_lat=51.17345&bbox_ne_lng=-113.96269&bbox_sw_lat=50.87234&bbox_sw_lng=-114.22313&heatmap=true&heatmap_aggregation_field=estimate_list_price&heatmap_aggregation_type=median&heatmap_cell_limit=10"
const houski_map_data_price_heatmap = async (): Promise<MapResponse> => {
// You must copy the MapResponse type declarations from the
// Houski API documentation to strongly type the response
const url = new URL('http://127.0.0.1:8080/map');
url.searchParams.set('api_key', 'YOUR_API_KEY');
url.searchParams.set('bbox_ne_lat', '51.17345');
url.searchParams.set('bbox_ne_lng', '-113.96269');
url.searchParams.set('bbox_sw_lat', '50.87234');
url.searchParams.set('bbox_sw_lng', '-114.22313');
url.searchParams.set('heatmap', 'true');
url.searchParams.set('heatmap_aggregation_field', 'estimate_list_price');
url.searchParams.set('heatmap_aggregation_type', 'median');
url.searchParams.set('heatmap_cell_limit', '10');
const response = await fetch(url);
const data = await response.json();
return data;
}
(async () => {
let data: MapResponse = await houski_map_data_price_heatmap();
// Log the response
console.log(data);
})();
{
"aborted": false,
"cache_hit": true,
"cost_cents": 0.009000000543892384,
"data": {
"clusters": [],
"heatmap": [
{
"aggregation_value": 522944.5,
"intensity": 0.0,
"latitude": 51.02289581298828,
"longitude": -113.97220611572266,
"number_of_properties": 33622
},
{
"aggregation_value": 630433.5,
"intensity": 0.4238918125629425,
"latitude": 51.12326431274414,
"longitude": -114.07257843017578,
"number_of_properties": 64594
},
{
"aggregation_value": 776521.0,
"intensity": 1.0,
"latitude": 51.02289581298828,
"longitude": -114.07257843017578,
"number_of_properties": 132215
},
{
"aggregation_value": 775695.0,
"intensity": 0.9967426061630248,
"latitude": 51.02289581298828,
"longitude": -114.17294311523438,
"number_of_properties": 63585
},
{
"aggregation_value": 581118.0,
"intensity": 0.2294120341539383,
"latitude": 51.12326431274414,
"longitude": -113.97220611572266,
"number_of_properties": 15274
},
{
"aggregation_value": 706046.0,
"intensity": 0.7220759987831116,
"latitude": 51.12326431274414,
"longitude": -114.17294311523438,
"number_of_properties": 68065
},
{
"aggregation_value": 639930.0,
"intensity": 0.4613420367240906,
"latitude": 50.92252731323242,
"longitude": -114.07257843017578,
"number_of_properties": 74568
},
{
"aggregation_value": 649957.0,
"intensity": 0.5008843541145325,
"latitude": 50.92252731323242,
"longitude": -113.97220611572266,
"number_of_properties": 34141
},
{
"aggregation_value": 691301.0,
"intensity": 0.6639278531074524,
"latitude": 50.92252731323242,
"longitude": -114.17294311523438,
"number_of_properties": 10221
}
],
"properties": []
},
"error": "",
"heatmap_aggregation_field": "estimate_list_price",
"heatmap_aggregation_max_value": 776521.0,
"heatmap_aggregation_min_value": 522944.5,
"heatmap_aggregation_type": "median",
"is_clusters": false,
"is_heatmap": true,
"is_heatmap_aggregation": true,
"is_heatmap_aggregation_invert_intensity": false,
"is_properties": false,
"pagination": {
"current_page": 1,
"has_next_page": false,
"has_previous_page": false,
"page_total": 1
},
"price_quote": false,
"result_total": 9,
"time_ms": 291
}
curl -X GET "http://127.0.0.1:8080/map?abort_over=500000&address_regex=^1(1|2|3)(1|2|3|4|5).*$&api_key=YOUR_API_KEY&cluster_over=1000&page=1&polygon=51.0447_-114.0719,51.0544_-114.0719,51.0544_-114.0856,51.0452_-114.0856&results_per_page=3&select=address"
const houski_map_data_polygon = async (): Promise<MapResponse> => {
// You must copy the MapResponse type declarations from the
// Houski API documentation to strongly type the response
const url = new URL('http://127.0.0.1:8080/map');
url.searchParams.set('abort_over', '500000');
url.searchParams.set('address_regex', '^1(1|2|3)(1|2|3|4|5).*$');
url.searchParams.set('api_key', 'YOUR_API_KEY');
url.searchParams.set('cluster_over', '1000');
url.searchParams.set('page', '1');
url.searchParams.set('polygon', '51.0447_-114.0719,51.0544_-114.0719,51.0544_-114.0856,51.0452_-114.0856');
url.searchParams.set('results_per_page', '3');
url.searchParams.set('select', 'address');
const response = await fetch(url);
const data = await response.json();
return data;
}
(async () => {
let data: MapResponse = await houski_map_data_polygon();
// Log the response
console.log(data);
})();
{
"aborted": false,
"cache_hit": true,
"cost_cents": 0.119999997317791,
"data": {
"clusters": [],
"heatmap": [],
"properties": [
{
"address": "1213 608 9 Street SW",
"latitude": 51.04768371582031,
"longitude": -114.0837173461914,
"property_id": "10ab0ffc777c870"
},
{
"address": "1110 605 5 Avenue SW",
"latitude": 51.048583984375,
"longitude": -114.0745086669922,
"property_id": "10c1af6fea51866f"
},
{
"address": "1110 618 5 Avenue SW",
"latitude": 51.04884719848633,
"longitude": -114.07439422607422,
"property_id": "113a70687880c4e9"
}
]
},
"error": "",
"heatmap_aggregation_field": null,
"heatmap_aggregation_max_value": null,
"heatmap_aggregation_min_value": null,
"heatmap_aggregation_type": null,
"is_clusters": false,
"is_heatmap": false,
"is_heatmap_aggregation": null,
"is_heatmap_aggregation_invert_intensity": null,
"is_properties": true,
"pagination": {
"current_page": 1,
"has_next_page": true,
"has_previous_page": false,
"page_total": 59
},
"price_quote": false,
"result_total": 177,
"time_ms": 394
}
interface MapResponse {
aborted: boolean;
cache_hit: boolean;
cost_cents: number;
data: MapData;
error: string;
is_clusters: boolean;
is_properties: boolean;
is_heatmap: boolean;
is_heatmap_aggregation?: boolean | null;
is_heatmap_aggregation_invert_intensity?: boolean | null;
heatmap_aggregation_type?: string | null;
heatmap_aggregation_max_value?: number | null;
heatmap_aggregation_min_value?: number | null;
heatmap_aggregation_field?: string | null;
pagination: Pagination;
price_quote: boolean;
result_total: number;
time_ms: number;
}
interface MapData {
properties: Property[];
clusters: Cluster[];
heatmap: HeatPoint[];
}
interface Cluster {
latitude: number;
longitude: number;
number_of_properties: number;
}
interface HeatPoint {
latitude: number;
longitude: number;
intensity: number;
number_of_properties: number;
aggregation_value?: number | null;
}
interface Pagination {
current_page: number;
has_next_page: boolean;
has_previous_page: boolean;
page_total: number;
}
// You can find the Property type on the /properties endpoint API docs
// https://www.houski.ca/api-documentation/properties