htag-sdk
v1.8.1
Published
Official TypeScript SDK for HtAG Location Intelligence APIs — address search, property data, and market analytics for Australia
Downloads
1,040
Maintainers
Readme
htag-sdk
The official TypeScript SDK for the HtAG Location Intelligence API.
Provides typed access to Australian address data, property valuations, sales records, and market analytics. Zero runtime dependencies -- uses native fetch.
import { HtAgApiClient } from 'htag-sdk';
const client = new HtAgApiClient({
apiKey: process.env.HTAG_API_KEY!,
environment: 'prod',
});
const results = await client.address.geocode({ address: '100 George St Sydney' });
for (const r of results.results) {
console.log(`${r.address_label} (${r.address_key})`);
}Installation
npm install htag-sdkOr with your preferred package manager:
yarn add htag-sdk
pnpm add htag-sdkRequires Node.js 18+ (for native fetch).
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
import { HtAgApiClient } from 'htag-sdk';
const client = new HtAgApiClient({
apiKey: 'sk-org--your-org-id-your-key-value',
environment: 'prod', // 'dev' or 'prod'
});Or use a custom base URL:
const client = new HtAgApiClient({
apiKey: 'sk-...',
baseUrl: 'https://api.staging.htagai.com',
});3. Make Requests
// Geocode an address
const results = await client.address.geocode({ address: '15 Miranda Court Noble Park' });
console.log(`${results.total} matches`);
// Get property estimates
const est = await client.property.estimates({
addressKey: '15MIRANDACOURTNOBLEPARKvic3174',
});
for (const record of est.results) {
console.log(`Price estimate: $${record.price_estimate?.toLocaleString()}`);
console.log(`Last sold: $${record.last_sold_price?.toLocaleString()} on ${record.last_sold_date}`);
}Usage
Address Geocode
Resolve a free-text address to structured location data with geographic identifiers.
const results = await client.address.geocode({
address: '100 Hickox St Traralgon',
limit: 5, // max results (1 - 50)
});
for (const match of results.results) {
console.log(match.address_label);
console.log(` Key: ${match.address_key}`);
console.log(` Location: ${match.lat}, ${match.lon}`);
}Address Standardisation
Standardise raw address strings into structured, canonical components.
const result = await client.address.standardise({
addresses: [
'12 / 100-102 HICKOX STR TRARALGON, VIC 3844',
'15a smith st fitzroy vic 3065',
],
});
for (const item of result.results) {
if (item.error) {
console.log(`Failed: ${item.input_address} -- ${item.error}`);
} else {
const addr = item.standardised_address!;
console.log(item.input_address);
console.log(` -> ${addr.street_number} ${addr.street_name} ${addr.street_type}`);
console.log(` ${addr.suburb_or_locality} ${addr.state} ${addr.postcode}`);
console.log(` Key: ${item.address_key}`);
}
}Address Environment
Retrieve environmental risk data for an address including flood, bushfire, heritage, and zoning.
const env = await client.address.environment({
address: '15 Miranda Court, Noble Park VIC 3174',
});
for (const record of env.results) {
console.log(`Bushfire: ${record.bushfire}, Flood: ${record.flood}`);
console.log(`Heritage: ${record.heritage}, Zoning: ${record.zoning}`);
}Address Demographics
Retrieve socio-economic indices (SEIFA) and housing tenure data.
const demo = await client.address.demographics({
address: '15 Miranda Court, Noble Park VIC 3174',
});
for (const record of demo.results) {
console.log(`IRSAD: ${record.IRSAD}, IER: ${record.IER}`);
}Property Summary
Retrieve physical property attributes for an address.
const summary = await client.property.summary({
addressKey: '100102HICKOXSTREETTRARALGONVIC3844',
});
for (const record of summary.results) {
console.log(`Type: ${record.property_type}`);
console.log(`Beds: ${record.beds}, Baths: ${record.baths}, Parking: ${record.parking}`);
console.log(`Land: ${record.lot_size} sqm, Floor: ${record.floor_area} sqm`);
}Property Estimates
Retrieve valuation estimates and transaction history for an address.
const est = await client.property.estimates({
addressKey: '100102HICKOXSTREETTRARALGONVIC3844',
});
for (const record of est.results) {
console.log(`Price estimate: $${record.price_estimate?.toLocaleString()}`);
console.log(`Rent estimate: $${record.rent_estimate}/wk`);
console.log(`Last sold: $${record.last_sold_price?.toLocaleString()} on ${record.last_sold_date}`);
}Property Market
Retrieve market position indicators for an address.
const mkt = await client.property.market({
addressKey: '100102HICKOXSTREETTRARALGONVIC3844',
});
for (const record of mkt.results) {
console.log(`Rental %: ${record.rental_percentage}`);
console.log(`Years to own: ${record.years_to_own}`);
console.log(`Hold period: ${record.hold_period} years`);
}Sold Property Search
Search for recently sold properties near an address or coordinates.
const sold = await client.property.soldSearch({
address: '100 George St, Sydney NSW 2000',
radius: 2000, // metres
propertyType: 'house',
saleValueMin: 500_000,
saleValueMax: 2_000_000,
bedroomsMin: 3,
startDate: '2024-01-01',
});
console.log(`${sold.total} properties found`);
for (const prop of sold.results) {
const price = prop.sold_price ? `$${prop.sold_price.toLocaleString()}` : 'undisclosed';
console.log(` ${prop.street_address}, ${prop.suburb} -- ${price} (${prop.sold_date})`);
}All filter parameters are optional:
| Parameter | Type | Description |
|-----------|------|-------------|
| address | string | Free-text address to centre the search on |
| addressKey | string | GNAF address key |
| lat, lon | number | Coordinates for point-based search |
| radius | number | Search radius in metres (default 2000, max 5000) |
| proximity | string | 'any', 'sameStreet', or 'sameSuburb' |
| propertyType | string | 'house', 'unit', 'townhouse', 'land', 'rural' |
| saleValueMin, saleValueMax | number | Price range filter (AUD) |
| bedroomsMin, bedroomsMax | number | Bedroom count range |
| bathroomsMin, bathroomsMax | number | Bathroom count range |
| carSpacesMin, carSpacesMax | number | Car space range |
| startDate, endDate | string | Date range (ISO 8601, e.g. '2024-01-01') |
| landAreaMin, landAreaMax | number | Land area in sqm |
Parameters use camelCase in TypeScript and are automatically converted to snake_case for the API.
Market Summary
Get headline market metrics at suburb or LGA level.
const summary = await client.markets.summary({
level: 'suburb',
areaId: ['SAL10001'],
propertyType: ['house'],
});
for (const record of summary.results) {
console.log(`${record.suburb} (${record.state_name})`);
console.log(` Typical price: $${record.typical_price?.toLocaleString()}`);
console.log(` Rent: $${record.rent}/wk`);
}Market Growth
Retrieve cumulative or annualised growth rates for price, rent, and yield.
const growth = await client.markets.growthCumulative({
level: 'suburb',
areaId: ['SAL10001'],
propertyType: ['house'],
});
for (const record of growth.results) {
console.log(`1Y price growth: ${record.one_y_price_growth}`);
console.log(`5Y price growth: ${record.five_y_price_growth}`);
}Market Trends
Access historical trend data via client.markets.trends. All trend methods share the same parameter signature:
// Price history
const prices = await client.markets.trends.price({
level: 'suburb',
areaId: ['SAL10001'],
propertyType: ['house'],
periodEndMin: '2020-01-01',
limit: 50,
});
for (const p of prices.results) {
console.log(`${p.period_end}: $${p.typical_price?.toLocaleString()} (${p.sales} sales)`);
}
// Rent history
const rents = await client.markets.trends.rent({
level: 'suburb', areaId: ['SAL10001'],
});
// Yield history
const yields = await client.markets.trends.yieldHistory({
level: 'suburb', areaId: ['SAL10001'],
});
// Search interest index (buy/rent search indices)
const search = await client.markets.trends.searchIndex({
level: 'suburb', areaId: ['SAL10001'],
});
// Hold period
const hold = await client.markets.trends.holdPeriod({
level: 'suburb', areaId: ['SAL10001'],
});
// Growth rates (price, rent, yield changes)
const growth = await client.markets.trends.growthRates({
level: 'suburb', areaId: ['SAL10001'],
});
// Demand profile (sales by dwelling type and bedrooms)
const demand = await client.markets.trends.demandProfile({
level: 'suburb', areaId: ['SAL10001'],
});
// Stock on market
const som = await client.markets.trends.stockOnMarket({
level: 'suburb', areaId: ['SAL10001'],
});
// Days on market
const dom = await client.markets.trends.daysOnMarket({
level: 'suburb', areaId: ['SAL10001'],
});
// Clearance rate
const cr = await client.markets.trends.clearanceRate({
level: 'suburb', areaId: ['SAL10001'],
});
// Vacancy rate
const vac = await client.markets.trends.vacancy({
level: 'suburb', areaId: ['SAL10001'],
});Common trend parameters:
| Parameter | Type | Description |
|-----------|------|-------------|
| level | 'suburb' | 'lga' | Geographic level (required) |
| areaId | string[] | Area identifiers (required) |
| propertyType | string[] | ['house'], ['unit'], etc. |
| periodEndMin | string | Filter from this date |
| periodEndMax | string | Filter up to this date |
| bedrooms | string | string[] | Bedroom filter |
| limit | number | Max results (default 100, max 1000) |
| offset | number | 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)
const results = await client.internal.address.search({ q: '100 George St Sydney' });
// Address insights (enriched address data)
const insights = await client.internal.address.insights({
address: '15 Miranda Court, Noble Park VIC 3174',
});
// Automated Valuation Model (batch, up to 50 properties)
const avm = await client.internal.property.avm({
addressKey: ['100102HICKOXSTREETTRARALGONVIC3844'],
});
// Market snapshots with filtering
const snapshots = await client.internal.markets.snapshots({
level: 'suburb',
propertyType: ['house'],
areaId: ['SAL10001'],
});
// Advanced market query with logical filters
const queryResults = await 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
const supply = await client.internal.markets.trends.supplyDemand({
level: 'suburb', areaId: ['SAL10001'],
});
const perf = await client.internal.markets.trends.performance({
level: 'suburb', areaId: ['SAL10001'],
});If you call an internal method without the required scope, the API will return a 403 error.
Request Cancellation
All methods accept an AbortSignal for cancellation:
const controller = new AbortController();
// Cancel after 5 seconds
setTimeout(() => controller.abort(), 5000);
try {
const results = await client.address.geocode({
address: '100 George St',
signal: controller.signal,
});
} catch (err) {
if (err instanceof HtAgError && err.message === 'Request aborted') {
console.log('Request was cancelled');
}
}Error Handling
The SDK raises typed errors for API failures:
import {
HtAgApiClient,
HtAgError,
AuthenticationError,
RateLimitError,
ValidationError,
ServerError,
} from 'htag-sdk';
const client = new HtAgApiClient({ apiKey: 'sk-...' });
try {
const results = await client.address.geocode({ address: 'Syd' });
} catch (err) {
if (err instanceof AuthenticationError) {
// 401 or 403 -- bad API key or insufficient scope
console.error(`Auth failed (HTTP ${err.status})`);
} else if (err instanceof RateLimitError) {
// 429 -- throttled (after exhausting retries)
console.error('Rate limited, try again later');
} else if (err instanceof ValidationError) {
// 400 or 422 -- bad request params
console.error(`Invalid request: ${err.message}`);
console.error('Details:', err.body);
} else if (err instanceof ServerError) {
// 5xx -- upstream failure (after exhausting retries)
console.error(`Server error (HTTP ${err.status})`);
} else if (err instanceof HtAgError) {
// Network/timeout/other
console.error(`Request failed: ${err.message}`);
}
}All errors carry:
message-- human-readable descriptionstatus-- HTTP status code (if applicable)body-- raw response bodyurl-- the request URL that failedcause-- the underlying error (for network failures)
Retries
The SDK automatically retries transient failures:
- Retried statuses: 429, 500, 502, 503, 504
- Network errors: connection failures, timeouts
- Max retries: 3 (configurable)
- Backoff: exponential (0.5s base, 2x multiplier, random jitter)
Configure retry behaviour:
const client = new HtAgApiClient({
apiKey: 'sk-...',
maxRetries: 5, // default is 3
timeout: 120_000, // request timeout in ms (default 30000)
});Configuration Reference
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| apiKey | string | required | Your HtAG API key |
| environment | 'dev' | 'prod' | 'prod' | API environment |
| baseUrl | string | -- | Custom base URL (overrides environment) |
| timeout | number | 30000 | Request timeout in milliseconds |
| maxRetries | number | 3 | Maximum retry attempts |
| retryBaseDelay | number | 500 | Base delay between retries in ms |
CommonJS
The package ships with both ESM and CommonJS builds:
// ESM (recommended)
import { HtAgApiClient } from 'htag-sdk';
// CommonJS
const { HtAgApiClient } = require('htag-sdk');TypeScript
All types are exported for use in your application:
import type {
AddressRecord,
GeocodeRecord,
SoldPropertyRecord,
PropertySummaryRecord,
PropertyEstimatesRecord,
PropertyMarketRecord,
MarketSummaryRecord,
PriceHistoryOut,
RentHistoryOut,
BaseResponse,
LevelEnum,
PropertyTypeEnum,
} from 'htag-sdk';Requirements
- Node.js >= 18 (for native
fetch) - No runtime dependencies
License
MIT
