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.

  1. Create a free account and open your account.
  2. Create an API key. It is shown once; store it as a secret.
  3. Export your key, then query the profile at the captured Surrey point:
Bash · set your key
# 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_KEY
First address-profile call
curl --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.

Property tax estimate with your assessed value
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.

POST/v1/addresses/resolve
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"}'
Responsesupportedreal response, shortened
{
  "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.

Bash · CSV batch and results
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.csv

Real 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
coverage.statusMeaning
supportedHillbase has this data for the place you asked about.
partialSome of the area or record set is covered; an empty result is weaker evidence.
unsupportedThis capability is not available for that place yet.
unavailableThe service could not answer right now; retry later.
staleThe newest data is older than its freshness window.
restrictedMatching 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_… and poi_… 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 limits
PlanCalls per dayCalls per minutePrice
Free1,00060Free
Pro50,000300US$49/month
Business500,0001,200US$249/month

The daily quota is shared by all keys on an account and resets at 00:00 UTC. See Pricing.

Errors

Errors
HTTPCodeWhat to do
401missing_api_key / invalid_api_keySend a valid key; revoked keys and deleted accounts are rejected.
422validation errorA parameter is missing or out of range; detail lists the field.
429rate_limitedPer-minute limit reached; wait for Retry-After seconds.
429daily_quota_exceededDaily plan quota used; Retry-After counts down to 00:00 UTC.
503unavailableA 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.

Bash · save, read and pause a Signal
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.

Connect your Hillbase account to ChatGPT on the web.

Use location records in your Claude conversations.

Use the remote connector in the desktop app with your Hillbase account.

Add Hillbase to your local coding workspace.

Cursor

API key

Ground your agent's location answers in Hillbase records.

Call the remote MCP server from your application using the Responses API.

Use 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.