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.4.0.tar.gz (26.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.4.0-py3-none-any.whl (35.4 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: htag_sdk-0.4.0.tar.gz
  • Upload date:
  • Size: 26.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.4.0.tar.gz
Algorithm Hash digest
SHA256 c2a1c64fddc610f6a837657ed14f45781dce5d724bf7419dd2f2589d6c869278
MD5 7fb1d203ceb234115ce5b9f96e424004
BLAKE2b-256 22e73c78959f564500068c1d3625d1771b0b48c82e4735237514d1cb6fdbc0ff

See more details on using hashes here.

File details

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

File metadata

  • Download URL: htag_sdk-0.4.0-py3-none-any.whl
  • Upload date:
  • Size: 35.4 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.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 72f9a05fbd7050c48af84de8a372e2003fb7542937af25547684d55fadf4e685
MD5 280b5973b4d2d55eb4bfddf1c53c134a
BLAKE2b-256 8b55dd5f5a17dad878e3e169abae39fb325d117ac89ce8d4f6f3392094a85a1f

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