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 sales records, and market analytics with both synchronous and asynchronous clients.
from htag import HtAgApi
client = HtAgApi(api_key="sk-...", environment="prod")
results = client.address.search("100 George St Sydney")
for r in results.results:
print(f"{r.address_label} (score {r.score:.2f})")
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 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
# Search for addresses
results = client.address.search("15 Miranda Court Noble Park")
print(results.total, "matches")
# Get insights for an address
insights = client.address.insights(address="15 Miranda Court, Noble Park VIC 3174")
for record in insights.results:
print(f"Bushfire: {record.bushfire}, Flood: {record.flood}")
# Close when done (or use a context manager)
client.close()
Usage
Address Search
Find addresses by free-text query with fuzzy matching.
results = client.address.search(
"100 Hickox St Traralgon",
threshold=0.3, # minimum match score (0.1 - 1.0)
limit=5, # max results (1 - 50)
)
for match in results.results:
print(f"{match.address_label}")
print(f" Key: {match.address_key}")
print(f" Score: {match.score:.2f}")
print(f" Location: {match.lat}, {match.lon}")
Address Insights
Retrieve enriched data for addresses including risk flags, SEIFA scores, and zoning.
Provide exactly one of address, address_keys, or legal_parcel_id:
# By address string
insights = client.address.insights(
address="15 Miranda Court, Noble Park VIC 3174"
)
# By GNAF address keys (up to 50)
insights = client.address.insights(
address_keys=["100102HICKOXSTREETTRARALGONVIC3844"]
)
# By legal parcel ID
insights = client.address.insights(
legal_parcel_id="2\\TP574754"
)
for record in insights.results:
print(f"Address: {record.address_label}")
print(f" Bushfire risk: {record.bushfire}")
print(f" Flood risk: {record.flood}")
print(f" Heritage: {record.heritage}")
print(f" SEIFA (IRSAD): {record.IRSAD}")
print(f" Zoning: {record.zoning}")
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}")
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 Snapshots
Get current market metrics at suburb or LGA level.
snapshots = client.markets.snapshots(
level="suburb",
property_type=["house"],
area_id=["SAL10001"],
limit=10,
)
for snap in snapshots.results:
print(f"{snap.suburb} ({snap.state_name})")
print(f" Typical price: ${snap.typical_price:,}")
print(f" Rent: ${snap.rent}/wk")
print(f" Yield: {snap.yield_val:.1%}" if snap.yield_val else "")
print(f" 1Y growth: {snap.one_y_price_growth:.1%}" if snap.one_y_price_growth else "")
Market Query (Advanced)
Run complex market searches with filter logic using AND/OR/NOT operators.
from htag import AdvancedSearchBody
# Using a dict
results = client.markets.query({
"level": "suburb",
"mode": "search",
"property_types": ["house"],
"typical_price_min": 500_000,
"typical_price_max": 1_500_000,
"limit": 20,
})
# Or using the typed model
body = AdvancedSearchBody(
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},
]
},
)
results = client.markets.query(body)
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"])
# Supply & demand (inventory, vacancies, clearance rate)
supply = client.markets.trends.supply_demand(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"])
# Performance essentials (price, rent, sales, rentals, yield)
perf = client.markets.trends.performance(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"])
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 |
Async Usage
Every method is available as an async equivalent:
import asyncio
from htag import AsyncHtAgApi
async def main():
client = AsyncHtAgApi(api_key="sk-...", environment="prod")
# All the same methods, just with await
results = await client.address.search("100 George St Sydney")
insights = await client.address.insights(address_keys=["GANSW716626498"])
sold = await client.property.sold_search(address="100 George St Sydney")
prices = await client.markets.trends.price(level="suburb", area_id=["SAL10001"])
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.search("Sydney")
# Async
async with AsyncHtAgApi(api_key="sk-...") as client:
results = await client.address.search("Sydney")
Error Handling
The SDK raises typed exceptions for API errors:
from htag import (
HtAgApi,
AuthenticationError,
RateLimitError,
ValidationError,
ServerError,
ConnectionError,
)
client = HtAgApi(api_key="sk-...")
try:
results = client.address.search("Syd")
except AuthenticationError as e:
# 401 or 403 — bad API key
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-0.3.0.tar.gz.
File metadata
- Download URL: htag_sdk-0.3.0.tar.gz
- Upload date:
- Size: 26.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e3bbf9fadadf20db747ce31cd25f4a482a98bea390d9e469a7392bec477cc8fc
|
|
| MD5 |
5a56e4fb1b16c157e635b8cb13bd4475
|
|
| BLAKE2b-256 |
0a8aa1ac51210ceab9d2669bda31fc40b7a82c68187cb5145191f305dda3ecbe
|
File details
Details for the file htag_sdk-0.3.0-py3-none-any.whl.
File metadata
- Download URL: htag_sdk-0.3.0-py3-none-any.whl
- Upload date:
- Size: 35.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ee33fd4ce036999fdeab0091c84b5af98fd945a936bde126767e9ebb95423e4f
|
|
| MD5 |
6ff086df9fcd899e4fc46f84ca925874
|
|
| BLAKE2b-256 |
9454172dd570af876f8e73e6476a76e6df4da45aac0967dc52fe14fa6c8b32f0
|