htag-sdk
v0.7.0
Published
Official TypeScript SDK for HtAG Location Intelligence APIs — address search, property data, and market analytics for Australia
Maintainers
Readme
htag-sdk
The official TypeScript SDK for the HtAG Location Intelligence API.
Provides typed access to Australian address data, property 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.search({ q: '100 George St Sydney' });
for (const r of results.results) {
console.log(`${r.address_label} (score ${r.score.toFixed(2)})`);
}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
// Search for addresses
const results = await client.address.search({ q: '15 Miranda Court Noble Park' });
console.log(`${results.total} matches`);
// Get insights for an address
const insights = await client.address.insights({
address: '15 Miranda Court, Noble Park VIC 3174',
});
for (const record of insights.results) {
console.log(`Bushfire: ${record.bushfire}, Flood: ${record.flood}`);
}Usage
Address Search
Find addresses by free-text query with fuzzy matching.
const results = await client.address.search({
q: '100 Hickox St Traralgon',
threshold: 0.3, // minimum match score (0.1 - 1.0)
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(` Score: ${match.score.toFixed(2)}`);
console.log(` Location: ${match.lat}, ${match.lon}`);
}Address Insights
Retrieve enriched data for addresses including risk flags, SEIFA scores, and zoning.
Provide exactly one of address, addressKeys, or legalParcelId:
// By address string
const insights = await client.address.insights({
address: '15 Miranda Court, Noble Park VIC 3174',
});
// By GNAF address keys (up to 50)
const insights2 = await client.address.insights({
addressKeys: ['100102HICKOXSTREETTRARALGONVIC3844'],
});
// By legal parcel ID
const insights3 = await client.address.insights({
legalParcelId: '2\\TP574754',
});
for (const record of insights.results) {
console.log(`Address: ${record.address_label}`);
console.log(` Bushfire risk: ${record.bushfire}`);
console.log(` Flood risk: ${record.flood}`);
console.log(` Heritage: ${record.heritage}`);
console.log(` SEIFA (IRSAD): ${record.IRSAD}`);
console.log(` Zoning: ${record.zoning}`);
}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}`);
}
}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 Snapshots
Get current market metrics at suburb or LGA level.
const snapshots = await client.markets.snapshots({
level: 'suburb',
propertyType: ['house'],
areaId: ['SAL10001'],
limit: 10,
});
for (const snap of snapshots.results) {
console.log(`${snap.suburb} (${snap.state_name})`);
console.log(` Typical price: $${snap.typical_price?.toLocaleString()}`);
console.log(` Rent: $${snap.rent}/wk`);
if (snap.yield != null) console.log(` Yield: ${(snap.yield * 100).toFixed(1)}%`);
if (snap.one_y_price_growth != null) {
console.log(` 1Y growth: ${(snap.one_y_price_growth * 100).toFixed(1)}%`);
}
}Market Query (Advanced)
Run complex market searches with filter logic using AND/OR/NOT operators.
const results = await client.markets.query({
level: 'suburb',
mode: 'search',
property_types: ['house'],
typical_price_min: 500_000,
typical_price_max: 1_500_000,
logic: {
and: [
{ field: 'one_y_price_growth', gte: 0.05 },
{ field: 'vacancy_rate', lte: 0.03 },
],
},
limit: 20,
});
for (const snap of results.results) {
console.log(`${snap.suburb}: $${snap.typical_price?.toLocaleString()}`);
}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'],
});
// Supply & demand (inventory, vacancies, clearance rate)
const supply = await client.markets.trends.supplyDemand({
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'],
});
// Performance essentials (price, rent, sales, rentals, yield)
const perf = await client.markets.trends.performance({
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'],
});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 |
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.search({
q: '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.search({ q: 'Syd' });
} catch (err) {
if (err instanceof AuthenticationError) {
// 401 or 403 — bad API key
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,
AddressSearchResult,
SoldPropertyRecord,
MarketSnapshot,
PriceHistoryOut,
RentHistoryOut,
BaseResponse,
LevelEnum,
PropertyTypeEnum,
} from 'htag-sdk';Requirements
- Node.js >= 18 (for native
fetch) - No runtime dependencies
License
MIT
