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

# 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_sdk 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_sdk 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_sdk 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 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-0.6.0.tar.gz (41.9 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-0.6.0-py3-none-any.whl (35.5 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for htag_sdk-0.6.0.tar.gz
Algorithm Hash digest
SHA256 9984d8512205005ac61c4e0c732892dfc2fd5d91c5242c5115661603a295ba58
MD5 9cf1ca33734bf8ce1e020bc591d67933
BLAKE2b-256 85bded638437e63adb80c94e1781929db56aa2ab40f2feb866d3e605284bb096

See more details on using hashes here.

File details

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

File metadata

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

File hashes

Hashes for htag_sdk-0.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7a8c59eff9b726b4565508713d5c5b4a7b1398604782e1b24ba9e0f02282311b
MD5 b338360b702c7f9af54b3e06afc6c272
BLAKE2b-256 6ad803d3329e3a07e9ed920e2a71abfa23c0d681549b19cd3a993b43f5a6c4bd

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