GET /api/activity-heatmap
National activity feed aggregated to H3 resolution-5 hexagons. Returns a GeoJSON FeatureCollection of geocoded events from the last 90 days, weighted by event count per cell. Use this to power map-based heatmap visualizations of where activity is happening across the country, without scoping to a single metro.
The response is cached at the edge for 1 hour (s-maxage=3600, stale-while-revalidate=86400), so it’s safe to fetch once on map mount instead of refetching as the viewport changes. There are no query parameters — the window and resolution are fixed.
Event counts per cell are long-tailed (p50 ≈ 22, p99 ≈ 270, max in the thousands). The weight field is the raw count; apply your own log or interpolation curve when styling heatmap layers so dense metros don’t saturate the legend.
Example
curl "https://axiomlocus.io/api/activity-heatmap"
Mapbox GL example
const res = await fetch("https://axiomlocus.io/api/activity-heatmap");
const fc = await res.json();
map.addSource("activity", { type: "geojson", data: fc });
map.addLayer({
id: "activity-heatmap",
type: "heatmap",
source: "activity",
paint: {
"heatmap-weight": [
"interpolate", ["linear"], ["get", "weight"],
0, 0, 25, 0.2, 75, 0.45, 200, 0.75, 500, 1,
],
},
});
Response
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": { "type": "Point", "coordinates": [-122.4194, 37.7749] },
"properties": { "weight": 287, "h3": "852830fffffffff" }
}
],
"meta": {
"window_days": 90,
"parent_res": 5,
"hex_count": 412
}
}
| Field | Type | Description |
|---|
features[].geometry.coordinates | [lng, lat] | Center of the H3 res-5 hexagon. |
features[].properties.weight | int | Number of events that occurred in this hex over the window. |
features[].properties.h3 | string | H3 res-5 cell index. |
meta.window_days | int | Lookback window in days (currently 90). |
meta.parent_res | int | H3 resolution of the aggregation (currently 5). |
meta.hex_count | int | Number of hexes with at least one event. |
GET /api/permits/hotspots
H3-aggregated building permit hotspots for a metro, ranked by permit density and total estimated cost.
Query parameters
| Name | Type | Required | Default | Description |
|---|
metro_slug | string | ✓ | | Metro slug (e.g. sf, nyc, chi, la). |
days | int | | 90 | Lookback period in days. |
limit | int | | 25 | Max hotspot cells to return. |
Example
curl "https://axiomlocus.io/api/permits/hotspots?metro_slug=sf&days=90&limit=10"
Response
{
"metro": "sf",
"days": 90,
"hotspots": [
{
"h3_index": "882a100d63fffff",
"center_lat": 37.7749,
"center_lng": -122.4194,
"permit_count": 47,
"total_value": 12500000,
"avg_value": 265957,
"top_type": "new_construction"
},
{
"h3_index": "882a100d65fffff",
"center_lat": 37.7851,
"center_lng": -122.4094,
"permit_count": 32,
"total_value": 8700000,
"avg_value": 271875,
"top_type": "renovation"
}
]
}
| Field | Type | Description |
|---|
hotspots | array | H3 cells ranked by permit activity. |
hotspots[].permit_count | int | Total permits in the lookback window. |
hotspots[].total_value | float | Sum of estimated costs. |
hotspots[].top_type | string | Most common permit type in this cell. |
GET /api/crime/hotspots
Crime incident hotspots aggregated by H3 cell for a metro area.
Query parameters
| Name | Type | Required | Default | Description |
|---|
metro_slug | string | ✓ | | Metro slug. |
days | int | | 90 | Lookback period in days. |
category | string | | | Filter by crime category: property, violent, other. |
limit | int | | 25 | Max cells to return. |
Example
curl "https://axiomlocus.io/api/crime/hotspots?metro_slug=sf&days=30&category=property"
Response
{
"metro": "sf",
"days": 30,
"total_incidents": 1243,
"hotspots": [
{
"h3_index": "882a100d63fffff",
"incident_count": 89,
"top_category": "property",
"per_capita_rate": 12.4,
"trend": "declining"
}
]
}
| Field | Type | Description |
|---|
total_incidents | int | Total incidents across all cells in the lookback window. |
hotspots[].incident_count | int | Number of incidents in this H3 cell. |
hotspots[].per_capita_rate | float | Incidents per 1,000 residents. |
hotspots[].trend | string | Trend vs prior period: increasing, stable, declining. |
GET /api/commuter-flows
LEHD Origin-Destination commuter flow data for a Census tract. Shows where workers live and where residents work.
Query parameters
| Name | Type | Required | Default | Description |
|---|
tract | string | ✓ | | Census tract GEOID (11 digits). |
direction | string | | both | inbound (workers coming in), outbound (residents leaving), or both. |
limit | int | | 20 | Top N connected tracts to return. |
Example
curl "https://axiomlocus.io/api/commuter-flows?tract=06075010800&direction=inbound&limit=10"
Response
{
"tract": "06075010800",
"inbound": {
"total_workers": 14200,
"flows": [
{ "origin_tract": "06081611100", "workers": 890, "pct": 6.3, "origin_name": "Daly City" },
{ "origin_tract": "06001402100", "workers": 720, "pct": 5.1, "origin_name": "Oakland" }
]
},
"outbound": {
"total_residents_working": 3400,
"flows": [
{ "dest_tract": "06075017902", "workers": 210, "pct": 6.2, "dest_name": "SoMa" }
]
}
}
| Field | Type | Description |
|---|
inbound.total_workers | int | Total workers commuting into this tract. |
inbound.flows | array | Top origin tracts with worker counts. |
outbound.total_residents_working | int | Total residents who commute out. |
outbound.flows | array | Top destination tracts. |
GET /api/v1/spatial-diagnostics
Identify spatial score outliers using Local Moran’s I (LISA) statistics and topological data analysis. Use this to find cells that behave differently from their neighbors — high-scoring cells surrounded by low scores, or vice versa.
Query parameters
| Name | Type | Required | Default | Description |
|---|
metro_slug | string | | | Filter by metro area. |
type | string | | | Spatial cluster type: HH (High-High), HL (High-Low), LH (Low-High), LL (Low-Low), TDA_DONUT (topological donut pattern). |
limit | int | | 50 | Max results (max 500). |
Cluster types explained:
| Type | Meaning | Use case |
|---|
HH | High score surrounded by high scores | Established strong markets. |
HL | High score surrounded by low scores | Potential anchor locations driving change. |
LH | Low score surrounded by high scores | Underperforming cells in strong markets — possible opportunities. |
LL | Low score surrounded by low scores | Weak market clusters. |
TDA_DONUT | Ring of activity with a hollow center | Emerging development patterns where activity surrounds but hasn’t reached the core. |
When no type is specified, the endpoint returns HL and LH outliers — the most analytically interesting patterns.
Example
curl "https://axiomlocus.io/api/v1/spatial-diagnostics?metro_slug=sf&type=HL&limit=10" \
-H "Authorization: Bearer al_your_key_here"
Response
{
"outliers": [
{
"h3_index": "882a100d63fffff",
"metro_slug": "sf",
"score_baseline": 72,
"experimental_composite": 78,
"spatial_diagnostics": {
"cluster_type": "HL",
"local_moran": 2.34
}
}
]
}
| Field | Type | Description |
|---|
outliers | array | Cells matching the spatial pattern, sorted by statistical significance. |
outliers[].score_baseline | number | Current composite score. |
outliers[].spatial_diagnostics.cluster_type | string | LISA cluster classification. |
outliers[].spatial_diagnostics.local_moran | number | Local Moran’s I statistic. Higher absolute values indicate stronger spatial outlier behavior. |
GET /api/backtest
Requires authentication. Session-based — available through the dashboard.
Validate scoring model accuracy by comparing historical predictions against outcomes. Requires at least 90 days of score history and 5 or more monitored locations.
Example
curl "https://axiomlocus.io/api/backtest" \
-H "Authorization: Bearer al_your_key_here"
Response (ready)
{
"status": "ready",
"data_collection_start": "2026-01-15",
"days_of_data": 94,
"metrics": {
"r_squared": 0.82,
"hit_rate": 0.76,
"miss_rate": 0.68,
"sample_size": 12
}
}
Response (insufficient data)
{
"status": "insufficient_data",
"days_until_ready": 42,
"days_of_data": 48,
"data_collection_start": "2026-03-01",
"sample_size": 3
}
| Field | Type | Description |
|---|
status | string | ready or insufficient_data. |
metrics.r_squared | number | Correlation between predicted and actual scores (0–1). |
metrics.hit_rate | number | Fraction of high-scored locations (above 70) that maintained or improved. |
metrics.miss_rate | number | Fraction of low-scored locations (below 30) that stayed low or declined. |
metrics.sample_size | number | Number of location pairs used in the analysis. |