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 descriptionstatus_code-- HTTP status (if applicable)body-- raw response bodyrequest_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-Afterheader
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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file htag_sdk-1.1.0.tar.gz.
File metadata
- Download URL: htag_sdk-1.1.0.tar.gz
- Upload date:
- Size: 59.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3c9744e6f82401a3538d3999522f38f3b837495a146e5c4fce49f2bf0641332c
|
|
| MD5 |
dd7947f17a539c02357e65ff70756f4f
|
|
| BLAKE2b-256 |
656a5dcc81a9193e87e85cbc7ca06134aca35946233569fc069727a4379f03e7
|
File details
Details for the file htag_sdk-1.1.0-py3-none-any.whl.
File metadata
- Download URL: htag_sdk-1.1.0-py3-none-any.whl
- Upload date:
- Size: 66.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fb1e1a5525cbc0caf2991bb237c62f24cfe8d561697279f65adb7a76d2bba057
|
|
| MD5 |
4bedb4b3f6ce4a2d921cd69f55732c5a
|
|
| BLAKE2b-256 |
d3c7c9ef044c4698b581763bba309cf60491151cdd7437a92850d95ccd56089d
|