Documentation
Hillbase API
One REST API (and an MCP server) for public data about Canadian addresses: schools and catchments, transit, nearby places, zoning, parcels, permits, hazards, crime statistics and census profiles.
Quickstart
From sign-up to your first address profile in about two minutes. You need a terminal with Bash and curl.
- Create a free account and open your account.
- Create an API key. It is shown once; store it as a secret.
- Export your key, then query the profile at the captured Surrey point:
# Paste the key shown once in your Hillbase account.
# Run in a private terminal; keep keys out of source control.
read -r -s -p "Hillbase API key: " HILLBASE_API_KEY; printf '\n'
export HILLBASE_API_KEYcurl --fail-with-body "https://api.hillbase.com/v1/address/profile" \
-H "X-API-Key: $HILLBASE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"point":{"lat":49.1416582,"lng":-122.8795092},"region":"BC","locality":"Surrey"}'HTTP 200 returns a point and named sections. Inspect coverage inside each section before using its records. This fixed excerpt is from that exact point; it is not fetched when you open this page.
Real captured response · school_assignments section excerpt. Captured values can differ from current data.
{
"schema_version": "hillbase.v0",
"quality": {
"basis": "mixed",
"effective_date": null,
"last_checked_at": null,
"limitations": [
{
"code": "absence_not_proof",
"message": "No matching record does not prove that none exists."
},
{
"code": "assessment_not_market_value",
"message": "An assessed value is not a sale price or current market value."
},
{
"code": "assignment_not_admission",
"message": "A school assignment does not guarantee admission."
}
]
},
"data": {
"point": {
"lat": 49.1416582,
"lng": -122.8795092
},
"sections": {
"school_assignments": {
"data": [
{
"school": {
"id": "sch_g2nriqq5vei4",
"name": "Strawberry Hill Elementary",
"school_type": "elementary",
"authority": {
"id": "aut_3usld4ldzlrt",
"name": "Surrey",
"authority_type": "school_district",
"external_id": "BC36"
}
},
"catchment": {
"id": "cat_fthhjbggwfj6",
"level": "elementary",
"program": "Regular",
"school_year": "2026-2027",
"grade_start": null,
"grade_end": null,
"boundary_version": "62eb312788d0d5b34c433177cd312678172ddf2a0408677a521aef9b8d9247e2",
"verification_status": "unverified"
},
"match_method": "official_boundary",
"confidence": "high",
"source": {
"id": "ca-bc-surrey-school-catchments",
"record_id": null,
"updated_at": null,
"observed_at": "2026-10-08T23:37:04.966500Z"
}
},
{
"school": {
"id": "sch_xw46knkm4azw",
"name": "Princess Margaret Secondary",
"school_type": "secondary",
"authority": {
"id": "aut_3usld4ldzlrt",
"name": "Surrey",
"authority_type": "school_district",
"external_id": "BC36"
}
},
"catchment": {
"id": "cat_ttoatnlwnxst",
"level": "secondary",
"program": "Regular",
"school_year": "2026-2027",
"grade_start": null,
"grade_end": null,
"boundary_version": "d43d656d971288cdbb153345c28c4a057d4dbe349cb51a990dc58b676856fdb9",
"verification_status": "unverified"
},
"match_method": "official_boundary",
"confidence": "high",
"source": {
"id": "ca-bc-surrey-school-catchments",
"record_id": null,
"updated_at": null,
"observed_at": "2026-10-08T23:37:04.966500Z"
}
}
],
"coverage": {
"capability": "school_assignments",
"status": "supported",
"outcome": "resolved",
"geography": "point",
"period": null,
"message": null
},
"quality": {
"basis": "derived",
"effective_date": null,
"last_checked_at": "2026-10-08T23:37:04.966500Z",
"limitations": [
{
"code": "assignment_not_admission",
"message": "A school assignment does not guarantee admission."
},
{
"code": "boundary_year_specific",
"message": "Boundaries apply to their stated year and may change."
}
]
},
"provenance": [
{
"source_id": "ca-bc-surrey-school-catchments",
"source_updated_at": null,
"observed_at": null,
"snapshot": null
}
],
"warnings": []
}
}
},
"retrieved_at": "2026-10-12T18:51:41.790023Z"
}For a street address, replace point with {"address":{"line1":"7633 124 St","city":"Surrey","region":"BC","country":"CA"}} as the request body. Address resolution and the full profile consume plan credits.
Estimate property tax with an assessed value you already have (for example from an MPAC notice or a listing): assessed_value takes precedence over a publisher assessment, which is returned separately as publisher_assessed_value. The response is an estimate with its component breakdown, rate year and exclusions.
curl -H "X-API-Key: $HILLBASE_API_KEY" \
"https://api.hillbase.com/v1/property-tax/estimate?lat=43.6515&lng=-79.3825&assessed_value=1000000&assessment_year=2025&property_class=residential"Base URL: https://api.hillbase.com. All endpoints are under /v1 and return JSON. See the endpoint reference for every parameter and a real example response.
Examples
Real requests and responses for common calls. These examples are fixed captures; run them yourself with your key.
curl -X POST "https://api.hillbase.com/v1/addresses/resolve" \
-H "X-API-Key: $HILLBASE_API_KEY" -H "Content-Type: application/json" \
-d '{"line1":"100 Queen St W","city":"Toronto","region":"ON","country":"CA"}'{
"schema_version": "hillbase.v0",
"data": {
"point": {
"lat": 43.651504,
"lng": -79.382497
},
"geocode": {
"provider": "lake.address_points",
"score": null,
"precision": "civic_number",
"address": {
"line1": "100 Queen ST W",
"line2": null,
"city": "Toronto",
"region": "ON",
"postal_code": "M5H2N1",
"country": "CA"
}
}
},
"coverage": {
"capability": "address_resolution",
"status": "supported",
"outcome": "resolved",
"geography": "ON",
"period": null,
"message": "Address resolved; caller inputs are not stored."
},
"provenance": [],
"warnings": [],
"retrieved_at": "2026-10-12T18:52:01.220757Z"
}Batch address queries
Submit your addresses or points to POST /v1/batches and get a job ID immediately. A job accepts up to 10,000 items (100 on Free) and the same section names as an address profile. Use demographics for census data; census is also accepted.
cat > addresses.csv <<'CSV'
lat,lng,region,locality
49.1416582,-122.8795092,BC,Surrey
CSV
curl --fail-with-body "https://api.hillbase.com/v1/batches?sections=school_assignments,property_values" \
-H "X-API-Key: $HILLBASE_API_KEY" -H "Content-Type: text/csv" \
--data-binary @addresses.csv > batch.json
# Requires jq. Submission returns HTTP 202; processing is asynchronous.
BATCH_ID=$(jq -r '.data.id' batch.json)
curl --fail-with-body -H "X-API-Key: $HILLBASE_API_KEY" \
"https://api.hillbase.com/v1/batches/$BATCH_ID"
# Repeat polling until data.status is completed, cancelled or failed.
# After completion, download the first page as CSV.
curl --fail-with-body -H "X-API-Key: $HILLBASE_API_KEY" \
"https://api.hillbase.com/v1/batches/$BATCH_ID/results?format=csv&offset=0&limit=1000" \
-o results.csvReal captured response. Captured values can differ from current data.
{
"schema_version": "hillbase.v0",
"data": {
"id": "bat_uu7tsnpefhou",
"status": "completed",
"total": 1,
"processed": 1,
"resolved": 1,
"no_match": 0,
"errors": 0,
"progress": 1,
"credits": 3,
"sections": [
"school_assignments",
"property_values"
],
"created_at": "2026-10-12T18:49:50.797639+00:00",
"expires_at": "2026-10-19T18:49:50.797639+00:00"
},
"coverage": {
"capability": "batch_queries",
"status": "supported",
"outcome": "resolved",
"geography": null,
"period": null,
"message": null
},
"provenance": [],
"warnings": [],
"retrieved_at": "2026-10-12T18:49:54.229096Z"
}For a JSON array, pass the sections in ?sections=school_assignments,property_values. To upload CSV, use Content-Type: text/csv and --data-binary @addresses.csv with that query parameter. CSV headers are line1,city,region (optional postal_code,country), or lat,lng (optional region,locality).
Poll GET /v1/batches/ID for status, progress and resolved, no-match and error counts. Results use the same coverage and privacy filtering as individual queries. Download newline-delimited JSON or CSV with GET /v1/batches/ID/results?format=jsonl or format=csv. Each result has its original zero-based index; CSV includes a JSON result column. Pages cover input indices offset through offset + limit - 1 (limit at most 1,000). Follow X-Hillbase-Next-Offset; while a job is running, revisit pages after completion for unfinished items.
Results expire seven days after submission. DELETE /v1/batches/ID cancels pending work and leaves completed results available until expiry. Credits are charged at submission with no refund for cancellation: each requested section costs the equivalent single call, plus address resolution for address inputs. A full profile costs 10 credits per item. Submission is refused when the remaining daily balance cannot cover the job; the error reports required and remaining credits. Polling and result downloads add no query credits.
Authentication
Send your key as X-API-Key: your-key or Authorization: Bearer your-key. GET /v1/coverage is public. Keep keys on your server; never ship them in browser or mobile code. You can hold up to five active keys and revoke any of them from your account.
Responses and coverage
Every response uses the same envelope. Read data together with coverage: an empty result only means “nothing found” when the capability is covered for that place.
The captured profile above shows the real envelope: schema_version, data, coverage, quality, provenance, warnings and retrieved_at. Quality states the answer basis, known effective date, last confirmed check and limitations.
| coverage.status | Meaning |
|---|---|
supported | Hillbase has this data for the place you asked about. |
partial | Some of the area or record set is covered; an empty result is weaker evidence. |
unsupported | This capability is not available for that place yet. |
unavailable | The service could not answer right now; retry later. |
stale | The newest data is older than its freshness window. |
restricted | Matching records exist but are withheld for your key's purpose. |
coverage.outcome is resolved, no_match or error for this particular query. provenance lists the sources that contributed records; their publishers and licences are listed under Data attribution, and GET /v1/sources returns publisher, licence and attribution per source_id.
- IDs such as
sch_…,pmt_…andpoi_…are permanent. - Distances are straight-line (
distance.method = "straight_line"), in kilometres. - Regional crime statistics describe an area, never an individual home.
- Personal contact details (phone numbers, e-mail addresses) in publisher text are never returned.
Limits and plans
| Plan | Calls per day | Calls per minute | Price |
|---|---|---|---|
| Free | 1,000 | 60 | Free |
| Pro | 50,000 | 300 | US$49/month |
| Business | 500,000 | 1,200 | US$249/month |
The daily quota is shared by all keys on an account and resets at 00:00 UTC. See Pricing.
Errors
| HTTP | Code | What to do |
|---|---|---|
| 401 | missing_api_key / invalid_api_key | Send a valid key; revoked keys and deleted accounts are rejected. |
| 422 | validation error | A parameter is missing or out of range; detail lists the field. |
| 429 | rate_limited | Per-minute limit reached; wait for Retry-After seconds. |
| 429 | daily_quota_exceeded | Daily plan quota used; Retry-After counts down to 00:00 UTC. |
| 503 | unavailable | A backing store is down; the body is still an envelope with coverage unavailable. |
Error bodies carry detail.code and detail.message.
Signals
Save a permit search by radius or municipality and read matching changes after new snapshots are loaded. This example creates a saved search with no webhook. It uses jq to read the returned ID.
curl --fail-with-body "https://api.hillbase.com/v1/signals" \
-H "X-API-Key: $HILLBASE_API_KEY" -H "Content-Type: application/json" \
-d '{"name":"Surrey permit activity","area":{"lat":49.1416582,"lng":-122.8795092,"radius_km":1}}' \
> signal.json
SIGNAL_ID=$(jq -r '.data.id' signal.json)
curl --fail-with-body -H "X-API-Key: $HILLBASE_API_KEY" \
"https://api.hillbase.com/v1/signals/$SIGNAL_ID/matches?limit=50&offset=0"
# Pause the saved search when you are done.
curl --fail-with-body -X DELETE -H "X-API-Key: $HILLBASE_API_KEY" \
"https://api.hillbase.com/v1/signals/$SIGNAL_ID"Real captured response. Captured values can differ from current data.
{
"schema_version": "hillbase.v0",
"data": {
"signal_id": "mon_wh2gyl66r5jq",
"items": [],
"total": 0,
"limit": 50,
"offset": 0,
"next_offset": null
},
"coverage": {
"capability": "signals",
"status": "supported",
"outcome": "no_match",
"geography": null,
"period": null,
"message": null
},
"provenance": [],
"warnings": [],
"retrieved_at": "2026-10-12T18:49:54.538468Z"
}Optional filters include permit_kind, work_type, project_category, min_value_cad, status and event. Use the canonical values in the Signal schema. Free accounts can keep one active Signal, Pro ten and Business one hundred.
An empty matches list means no matching changes have been recorded for this search. It does not mean there are no permits. Use /digest?date=YYYY-MM-DD for a UTC day's summary. Follow data.next_offset when reading matches. DELETE pauses the search; webhooks, when configured, use signed deliveries.
MCP for AI assistants
Use the same data through streamable HTTP at https://api.hillbase.com/mcp. Chat app UIs sign in with OAuth; local developer clients and model APIs use an API key. Each connection shares your Hillbase plan's credits.
ChatGPT connector
OAuthConnect your Hillbase account to ChatGPT on the web.
Use location records in your Claude conversations.
Claude Desktop
OAuthUse the remote connector in the desktop app with your Hillbase account.
Claude Code
API keyAdd Hillbase to your local coding workspace.
Cursor
API keyGround your agent's location answers in Hillbase records.
OpenAI Responses API
API keyCall the remote MCP server from your application using the Responses API.
Anthropic Messages API
API keyUse the Messages API MCP connector without running a separate MCP client.
Disconnect OAuth apps or revoke API keys in your account. See the endpoint reference for tool-equivalent REST calls.