Skip to main content

Official Python SDK for the HtAG Location Intelligence API — address search, property data, and market analytics for Australia

Project description

htag-sdk

The official Python SDK for the HtAG Location Intelligence API.

Provides typed, ergonomic access to Australian address data, property valuations, sales records, and market analytics with both synchronous and asynchronous clients.

from htag_sdk import HtAgApi

client = HtAgApi(api_key="sk-...", environment="prod")

results = client.address.geocode("100 George St Sydney")
for r in results.results:
    print(f"{r.address_label}  ({r.address_key})")

Installation

pip install htag-sdk

Or with your preferred package manager:

uv add htag-sdk
poetry add htag-sdk

Requires Python 3.9+.

Quick Start

1. Get an API Key

Sign up at developer.htagai.com and create an API key from the Settings page.

2. Create a Client

from htag_sdk import HtAgApi

client = HtAgApi(
    api_key="sk-org--your-org-id-your-key-value",
    environment="prod",   # "dev" or "prod"
)

Or use a custom base URL:

client = HtAgApi(api_key="sk-...", base_url="https://api.staging.htagai.com")

3. Make Requests

# Geocode an address
results = client.address.geocode("15 Miranda Court Noble Park")
print(results.total, "matches")

# Get property estimates
est = client.property.estimates(address_key="15MIRANDACOURTNOBLEPARKvic3174")
for record in est.results:
    print(f"Price estimate: ${record.price_estimate:,}")
    print(f"Last sold: ${record.last_sold_price:,} on {record.last_sold_date}")

# Close when done (or use a context manager)
client.close()

Usage

Address Geocode

Resolve a free-text address to structured location data with geographic identifiers.

results = client.address.geocode(
    "100 Hickox St Traralgon",
    limit=5,         # max results (1 - 50)
)

for match in results.results:
    print(f"{match.address_label}")
    print(f"  Key: {match.address_key}")
    print(f"  Location: {match.lat}, {match.lon}")

Address Standardisation

Standardise raw address strings into structured, canonical components.

result = client.address.standardise([
    "12 / 100-102 HICKOX STR TRARALGON, VIC 3844",
    "15a smith st fitzroy vic 3065",
])

for item in result.results:
    if item.error:
        print(f"Failed: {item.input_address} -- {item.error}")
    else:
        addr = item.standardised_address
        print(f"{item.input_address}")
        print(f"  -> {addr.street_number} {addr.street_name} {addr.street_type}")
        print(f"     {addr.suburb_or_locality} {addr.state} {addr.postcode}")
        print(f"  Key: {item.address_key}")

Address Environment

Retrieve environmental risk data for an address including flood, bushfire, heritage, and zoning.

env = client.address.environment(address="15 Miranda Court, Noble Park VIC 3174")
for record in env.results:
    print(f"Bushfire: {record.bushfire}, Flood: {record.flood}")
    print(f"Heritage: {record.heritage}, Zoning: {record.zoning}")

Address Demographics

Retrieve socio-economic indices (SEIFA) and housing tenure data.

demo = client.address.demographics(address="15 Miranda Court, Noble Park VIC 3174")
for record in demo.results:
    print(f"IRSAD: {record.IRSAD}, IER: {record.IER}")

Property Summary

Retrieve physical property attributes for an address.

summary = client.property.summary(address_key="100102HICKOXSTREETTRARALGONVIC3844")
for record in summary.results:
    print(f"Type: {record.property_type}")
    print(f"Beds: {record.beds}, Baths: {record.baths}, Parking: {record.parking}")
    print(f"Land: {record.lot_size} sqm, Floor: {record.floor_area} sqm")

Property Estimates

Retrieve valuation estimates and transaction history for an address.

est = client.property.estimates(address_key="100102HICKOXSTREETTRARALGONVIC3844")
for record in est.results:
    print(f"Price estimate: ${record.price_estimate:,}")
    print(f"Rent estimate: ${record.rent_estimate}/wk")
    print(f"Last sold: ${record.last_sold_price:,} on {record.last_sold_date}")

Property Market

Retrieve market position indicators for an address.

mkt = client.property.market(address_key="100102HICKOXSTREETTRARALGONVIC3844")
for record in mkt.results:
    print(f"Rental %: {record.rental_percentage:.0%}")
    print(f"Years to own: {record.years_to_own}")
    print(f"Hold period: {record.hold_period} years")

Sold Property Search

Search for recently sold properties near an address or coordinates.

sold = client.property.sold_search(
    address="100 George St, Sydney NSW 2000",
    radius=2000,              # metres
    property_type="house",
    sale_value_min=500_000,
    sale_value_max=2_000_000,
    bedrooms_min=3,
    start_date="2024-01-01",
)

print(f"{sold.total} properties found")
for prop in sold.results:
    price = f"${prop.sold_price:,.0f}" if prop.sold_price else "undisclosed"
    print(f"  {prop.street_address}, {prop.suburb} -- {price} ({prop.sold_date})")

All filter parameters are optional:

Parameter Type Description
address str Free-text address to centre the search on
address_key str GNAF address key
lat, lon float Coordinates for point-based search
radius int Search radius in metres (default 2000, max 5000)
proximity str "any", "sameStreet", or "sameSuburb"
property_type str "house", "unit", "townhouse", "land", "rural"
sale_value_min, sale_value_max float Price range filter (AUD)
bedrooms_min, bedrooms_max int Bedroom count range
bathrooms_min, bathrooms_max int Bathroom count range
car_spaces_min, car_spaces_max int Car space range
start_date, end_date str Date range (ISO 8601, e.g. "2024-01-01")
land_area_min, land_area_max int Land area in sqm

Market Summary

Get headline market metrics at suburb or LGA level.

summary = client.markets.summary(
    level="suburb",
    area_id=["SAL10001"],
    property_type=["house"],
)

for record in summary.results:
    print(f"{record.suburb} ({record.state_name})")
    print(f"  Typical price: ${record.typical_price:,}")
    print(f"  Rent: ${record.rent}/wk")

Market Growth

Retrieve cumulative or annualised growth rates for price, rent, and yield.

growth = client.markets.growth_cumulative(
    level="suburb",
    area_id=["SAL10001"],
    property_type=["house"],
)

for record in growth.results:
    print(f"1Y price growth: {record.one_y_price_growth:.1%}")
    print(f"5Y price growth: {record.five_y_price_growth:.1%}")

Market Trends

Access historical trend data via client.markets.trends. All trend methods share the same parameter signature:

# Price history
prices = client.markets.trends.price(
    level="suburb",
    area_id=["SAL10001"],
    property_type=["house"],
    period_end_min="2020-01-01",
    limit=50,
)
for p in prices.results:
    print(f"{p.period_end}: ${p.typical_price:,} ({p.sales} sales)")

# Rent history
rents = client.markets.trends.rent(level="suburb", area_id=["SAL10001"])

# Yield history
yields = client.markets.trends.yield_history(level="suburb", area_id=["SAL10001"])

# Search interest index (buy/rent search indices)
search = client.markets.trends.search_index(level="suburb", area_id=["SAL10001"])

# Hold period
hold = client.markets.trends.hold_period(level="suburb", area_id=["SAL10001"])

# Growth rates (price, rent, yield changes)
growth = client.markets.trends.growth_rates(level="suburb", area_id=["SAL10001"])

# Demand profile (sales by dwelling type and bedrooms)
demand = client.markets.trends.demand_profile(level="suburb", area_id=["SAL10001"])

# Stock on market
som = client.markets.trends.stock_on_market(level="suburb", area_id=["SAL10001"])

# Days on market
dom = client.markets.trends.days_on_market(level="suburb", area_id=["SAL10001"])

# Clearance rate
cr = client.markets.trends.clearance_rate(level="suburb", area_id=["SAL10001"])

# Vacancy rate
vac = client.markets.trends.vacancy(level="suburb", area_id=["SAL10001"])

Common trend parameters:

Parameter Type Description
level str "suburb" or "lga" (required)
area_id list[str] Area identifiers (required)
property_type list[str] ["house"], ["unit"], etc.
period_end_min str Filter from this date
period_end_max str Filter up to this date
bedrooms str or list[str] Bedroom filter
limit int Max results (default 100, max 1000)
offset int Pagination offset

Internal API

Some endpoints require the internal_api scope on your API key. These are accessed via the client.internal namespace:

# Address search (trigram similarity matching)
results = client.internal.address.search("100 George St Sydney")

# Address insights (enriched address data)
insights = client.internal.address.insights(
    address="15 Miranda Court, Noble Park VIC 3174"
)

# Automated Valuation Model (batch, up to 50 properties)
avm = client.internal.property.avm(
    address_key=["100102HICKOXSTREETTRARALGONVIC3844"]
)

# Market snapshots with filtering
snapshots = client.internal.markets.snapshots(
    level="suburb",
    property_type=["house"],
    area_id=["SAL10001"],
)

# Advanced market query with logical filters
results = client.internal.markets.query({
    "level": "suburb",
    "mode": "search",
    "property_types": ["house"],
    "typical_price_min": 500_000,
    "logic": {
        "and": [
            {"field": "one_y_price_growth", "gte": 0.05},
            {"field": "vacancy_rate", "lte": 0.03},
        ]
    },
})

# Internal trend endpoints
supply = client.internal.markets.trends.supply_demand(
    level="suburb", area_id=["SAL10001"]
)
perf = client.internal.markets.trends.performance(
    level="suburb", area_id=["SAL10001"]
)

If you call an internal method without the required scope, the API will return a 403 error.

Async Usage

Every method is available as an async equivalent:

import asyncio
from htag_sdk import AsyncHtAgApi

async def main():
    client = AsyncHtAgApi(api_key="sk-...", environment="prod")

    # All the same methods, just with await
    results = await client.address.geocode("100 George St Sydney")
    est = await client.property.estimates(address_key="...")
    sold = await client.property.sold_search(address="100 George St Sydney")
    prices = await client.markets.trends.price(level="suburb", area_id=["SAL10001"])

    # Internal methods also available
    insights = await client.internal.address.insights(address="100 George St Sydney")

    await client.close()

asyncio.run(main())

Context Manager

Both clients support context managers for automatic cleanup:

# Sync
with HtAgApi(api_key="sk-...") as client:
    results = client.address.geocode("Sydney")

# Async
async with AsyncHtAgApi(api_key="sk-...") as client:
    results = await client.address.geocode("Sydney")

Error Handling

The SDK raises typed exceptions for API errors:

from htag_sdk import (
    HtAgApi,
    AuthenticationError,
    RateLimitError,
    ValidationError,
    ServerError,
    ConnectionError,
)

client = HtAgApi(api_key="sk-...")

try:
    results = client.address.geocode("Syd")
except AuthenticationError as e:
    # 401 or 403 -- bad API key or insufficient scope
    print(f"Auth failed: {e.message}")
except RateLimitError as e:
    # 429 -- throttled (after exhausting retries)
    print(f"Rate limited. Retry after: {e.retry_after}s")
except ValidationError as e:
    # 400 or 422 -- bad request params
    print(f"Invalid request: {e.message}")
    print(f"Details: {e.body}")
except ServerError as e:
    # 5xx -- upstream failure (after exhausting retries)
    print(f"Server error: {e.status_code}")
except ConnectionError as e:
    # Network/DNS/TLS failure
    print(f"Connection failed: {e.message}")

All exceptions carry:

  • message -- human-readable description
  • status_code -- HTTP status (if applicable)
  • body -- raw response body
  • request_id -- request identifier (if returned by the API)

Retries

The SDK automatically retries transient failures:

  • Retried statuses: 429, 500, 502, 503, 504
  • Max retries: 3 (configurable)
  • Backoff: exponential (0.5s base, 2x multiplier, 25% jitter, 30s cap)
  • 429 handling: respects Retry-After header

Configure retry behaviour:

client = HtAgApi(
    api_key="sk-...",
    max_retries=5,    # default is 3
    timeout=120.0,    # request timeout in seconds (default 60)
)

Configuration Reference

Parameter Type Default Description
api_key str required Your HtAG API key
environment str "prod" "dev" or "prod"
base_url str -- Custom base URL (overrides environment)
timeout float 60.0 Request timeout in seconds
max_retries int 3 Maximum retry attempts

Requirements

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

htag_sdk-1.4.0.tar.gz (62.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

htag_sdk-1.4.0-py3-none-any.whl (69.5 kB view details)

Uploaded Python 3

File details

Details for the file htag_sdk-1.4.0.tar.gz.

File metadata

  • Download URL: htag_sdk-1.4.0.tar.gz
  • Upload date:
  • Size: 62.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.13

File hashes

Hashes for htag_sdk-1.4.0.tar.gz
Algorithm Hash digest
SHA256 df58970f2874d303a328f584fb32c4006f7daf1df28c102895df5e6849292407
MD5 31038d52f78c17434e596685923668ef
BLAKE2b-256 b187082c1a4889571679004b90f61cdc763569478a9177005d38dcd504fbef6e

See more details on using hashes here.

File details

Details for the file htag_sdk-1.4.0-py3-none-any.whl.

File metadata

  • Download URL: htag_sdk-1.4.0-py3-none-any.whl
  • Upload date:
  • Size: 69.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.13

File hashes

Hashes for htag_sdk-1.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0a07c300dd076c6abf1a7768437db14770d8f873eea1479a358297756ec9dec3
MD5 659d5571ea76f815f5251ec1dfd8e015
BLAKE2b-256 5c6bc4664a94d5f1025507f4a64736df7d0eb87ded2a8fc702ebd9de6216cbf6

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page