htag-sdk 0.9.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  The official TypeScript SDK for the [HtAG](https://htagai.com) Location Intelligence API.
4
4
 
5
- Provides typed access to Australian address data, property sales records, and market analytics. Zero runtime dependencies uses native `fetch`.
5
+ Provides typed access to Australian address data, property valuations, sales records, and market analytics. Zero runtime dependencies -- uses native `fetch`.
6
6
 
7
7
  ```typescript
8
8
  import { HtAgApiClient } from 'htag-sdk';
@@ -12,9 +12,9 @@ const client = new HtAgApiClient({
12
12
  environment: 'prod',
13
13
  });
14
14
 
15
- const results = await client.address.search({ q: '100 George St Sydney' });
15
+ const results = await client.address.geocode({ address: '100 George St Sydney' });
16
16
  for (const r of results.results) {
17
- console.log(`${r.address_label} (score ${r.score.toFixed(2)})`);
17
+ console.log(`${r.address_label} (${r.address_key})`);
18
18
  }
19
19
  ```
20
20
 
@@ -62,72 +62,39 @@ const client = new HtAgApiClient({
62
62
  ### 3. Make Requests
63
63
 
64
64
  ```typescript
65
- // Search for addresses
66
- const results = await client.address.search({ q: '15 Miranda Court Noble Park' });
65
+ // Geocode an address
66
+ const results = await client.address.geocode({ address: '15 Miranda Court Noble Park' });
67
67
  console.log(`${results.total} matches`);
68
68
 
69
- // Get insights for an address
70
- const insights = await client.address.insights({
71
- address: '15 Miranda Court, Noble Park VIC 3174',
69
+ // Get property estimates
70
+ const est = await client.property.estimates({
71
+ addressKey: '15MIRANDACOURTNOBLEPARKvic3174',
72
72
  });
73
- for (const record of insights.results) {
74
- console.log(`Bushfire: ${record.bushfire}, Flood: ${record.flood}`);
73
+ for (const record of est.results) {
74
+ console.log(`Price estimate: $${record.price_estimate?.toLocaleString()}`);
75
+ console.log(`Last sold: $${record.last_sold_price?.toLocaleString()} on ${record.last_sold_date}`);
75
76
  }
76
77
  ```
77
78
 
78
79
  ## Usage
79
80
 
80
- ### Address Search
81
+ ### Address Geocode
81
82
 
82
- Find addresses by free-text query with fuzzy matching.
83
+ Resolve a free-text address to structured location data with geographic identifiers.
83
84
 
84
85
  ```typescript
85
- const results = await client.address.search({
86
- q: '100 Hickox St Traralgon',
87
- threshold: 0.3, // minimum match score (0.1 - 1.0)
86
+ const results = await client.address.geocode({
87
+ address: '100 Hickox St Traralgon',
88
88
  limit: 5, // max results (1 - 50)
89
89
  });
90
90
 
91
91
  for (const match of results.results) {
92
92
  console.log(match.address_label);
93
93
  console.log(` Key: ${match.address_key}`);
94
- console.log(` Score: ${match.score.toFixed(2)}`);
95
94
  console.log(` Location: ${match.lat}, ${match.lon}`);
96
95
  }
97
96
  ```
98
97
 
99
- ### Address Insights
100
-
101
- Retrieve enriched data for addresses including risk flags, SEIFA scores, and zoning.
102
-
103
- Provide exactly one of `address`, `addressKeys`, or `legalParcelId`:
104
-
105
- ```typescript
106
- // By address string
107
- const insights = await client.address.insights({
108
- address: '15 Miranda Court, Noble Park VIC 3174',
109
- });
110
-
111
- // By GNAF address keys (up to 50)
112
- const insights2 = await client.address.insights({
113
- addressKeys: ['100102HICKOXSTREETTRARALGONVIC3844'],
114
- });
115
-
116
- // By legal parcel ID
117
- const insights3 = await client.address.insights({
118
- legalParcelId: '2\\TP574754',
119
- });
120
-
121
- for (const record of insights.results) {
122
- console.log(`Address: ${record.address_label}`);
123
- console.log(` Bushfire risk: ${record.bushfire}`);
124
- console.log(` Flood risk: ${record.flood}`);
125
- console.log(` Heritage: ${record.heritage}`);
126
- console.log(` SEIFA (IRSAD): ${record.IRSAD}`);
127
- console.log(` Zoning: ${record.zoning}`);
128
- }
129
- ```
130
-
131
98
  ### Address Standardisation
132
99
 
133
100
  Standardise raw address strings into structured, canonical components.
@@ -142,7 +109,7 @@ const result = await client.address.standardise({
142
109
 
143
110
  for (const item of result.results) {
144
111
  if (item.error) {
145
- console.log(`Failed: ${item.input_address} ${item.error}`);
112
+ console.log(`Failed: ${item.input_address} -- ${item.error}`);
146
113
  } else {
147
114
  const addr = item.standardised_address!;
148
115
  console.log(item.input_address);
@@ -153,6 +120,78 @@ for (const item of result.results) {
153
120
  }
154
121
  ```
155
122
 
123
+ ### Address Environment
124
+
125
+ Retrieve environmental risk data for an address including flood, bushfire, heritage, and zoning.
126
+
127
+ ```typescript
128
+ const env = await client.address.environment({
129
+ address: '15 Miranda Court, Noble Park VIC 3174',
130
+ });
131
+ for (const record of env.results) {
132
+ console.log(`Bushfire: ${record.bushfire}, Flood: ${record.flood}`);
133
+ console.log(`Heritage: ${record.heritage}, Zoning: ${record.zoning}`);
134
+ }
135
+ ```
136
+
137
+ ### Address Demographics
138
+
139
+ Retrieve socio-economic indices (SEIFA) and housing tenure data.
140
+
141
+ ```typescript
142
+ const demo = await client.address.demographics({
143
+ address: '15 Miranda Court, Noble Park VIC 3174',
144
+ });
145
+ for (const record of demo.results) {
146
+ console.log(`IRSAD: ${record.IRSAD}, IER: ${record.IER}`);
147
+ }
148
+ ```
149
+
150
+ ### Property Summary
151
+
152
+ Retrieve physical property attributes for an address.
153
+
154
+ ```typescript
155
+ const summary = await client.property.summary({
156
+ addressKey: '100102HICKOXSTREETTRARALGONVIC3844',
157
+ });
158
+ for (const record of summary.results) {
159
+ console.log(`Type: ${record.property_type}`);
160
+ console.log(`Beds: ${record.beds}, Baths: ${record.baths}, Parking: ${record.parking}`);
161
+ console.log(`Land: ${record.lot_size} sqm, Floor: ${record.floor_area} sqm`);
162
+ }
163
+ ```
164
+
165
+ ### Property Estimates
166
+
167
+ Retrieve valuation estimates and transaction history for an address.
168
+
169
+ ```typescript
170
+ const est = await client.property.estimates({
171
+ addressKey: '100102HICKOXSTREETTRARALGONVIC3844',
172
+ });
173
+ for (const record of est.results) {
174
+ console.log(`Price estimate: $${record.price_estimate?.toLocaleString()}`);
175
+ console.log(`Rent estimate: $${record.rent_estimate}/wk`);
176
+ console.log(`Last sold: $${record.last_sold_price?.toLocaleString()} on ${record.last_sold_date}`);
177
+ }
178
+ ```
179
+
180
+ ### Property Market
181
+
182
+ Retrieve market position indicators for an address.
183
+
184
+ ```typescript
185
+ const mkt = await client.property.market({
186
+ addressKey: '100102HICKOXSTREETTRARALGONVIC3844',
187
+ });
188
+ for (const record of mkt.results) {
189
+ console.log(`Rental %: ${record.rental_percentage}`);
190
+ console.log(`Years to own: ${record.years_to_own}`);
191
+ console.log(`Hold period: ${record.hold_period} years`);
192
+ }
193
+ ```
194
+
156
195
  ### Sold Property Search
157
196
 
158
197
  Search for recently sold properties near an address or coordinates.
@@ -171,7 +210,7 @@ const sold = await client.property.soldSearch({
171
210
  console.log(`${sold.total} properties found`);
172
211
  for (const prop of sold.results) {
173
212
  const price = prop.sold_price ? `$${prop.sold_price.toLocaleString()}` : 'undisclosed';
174
- console.log(` ${prop.street_address}, ${prop.suburb} ${price} (${prop.sold_date})`);
213
+ console.log(` ${prop.street_address}, ${prop.suburb} -- ${price} (${prop.sold_date})`);
175
214
  }
176
215
  ```
177
216
 
@@ -194,51 +233,38 @@ All filter parameters are optional:
194
233
 
195
234
  > Parameters use camelCase in TypeScript and are automatically converted to snake_case for the API.
196
235
 
197
- ### Market Snapshots
236
+ ### Market Summary
198
237
 
199
- Get current market metrics at suburb or LGA level.
238
+ Get headline market metrics at suburb or LGA level.
200
239
 
201
240
  ```typescript
202
- const snapshots = await client.markets.snapshots({
241
+ const summary = await client.markets.summary({
203
242
  level: 'suburb',
204
- propertyType: ['house'],
205
243
  areaId: ['SAL10001'],
206
- limit: 10,
244
+ propertyType: ['house'],
207
245
  });
208
246
 
209
- for (const snap of snapshots.results) {
210
- console.log(`${snap.suburb} (${snap.state_name})`);
211
- console.log(` Typical price: $${snap.typical_price?.toLocaleString()}`);
212
- console.log(` Rent: $${snap.rent}/wk`);
213
- if (snap.yield != null) console.log(` Yield: ${(snap.yield * 100).toFixed(1)}%`);
214
- if (snap.one_y_price_growth != null) {
215
- console.log(` 1Y growth: ${(snap.one_y_price_growth * 100).toFixed(1)}%`);
216
- }
247
+ for (const record of summary.results) {
248
+ console.log(`${record.suburb} (${record.state_name})`);
249
+ console.log(` Typical price: $${record.typical_price?.toLocaleString()}`);
250
+ console.log(` Rent: $${record.rent}/wk`);
217
251
  }
218
252
  ```
219
253
 
220
- ### Market Query (Advanced)
254
+ ### Market Growth
221
255
 
222
- Run complex market searches with filter logic using AND/OR/NOT operators.
256
+ Retrieve cumulative or annualised growth rates for price, rent, and yield.
223
257
 
224
258
  ```typescript
225
- const results = await client.markets.query({
259
+ const growth = await client.markets.growthCumulative({
226
260
  level: 'suburb',
227
- mode: 'search',
228
- property_types: ['house'],
229
- typical_price_min: 500_000,
230
- typical_price_max: 1_500_000,
231
- logic: {
232
- and: [
233
- { field: 'one_y_price_growth', gte: 0.05 },
234
- { field: 'vacancy_rate', lte: 0.03 },
235
- ],
236
- },
237
- limit: 20,
261
+ areaId: ['SAL10001'],
262
+ propertyType: ['house'],
238
263
  });
239
264
 
240
- for (const snap of results.results) {
241
- console.log(`${snap.suburb}: $${snap.typical_price?.toLocaleString()}`);
265
+ for (const record of growth.results) {
266
+ console.log(`1Y price growth: ${record.one_y_price_growth}`);
267
+ console.log(`5Y price growth: ${record.five_y_price_growth}`);
242
268
  }
243
269
  ```
244
270
 
@@ -261,50 +287,52 @@ for (const p of prices.results) {
261
287
 
262
288
  // Rent history
263
289
  const rents = await client.markets.trends.rent({
264
- level: 'suburb',
265
- areaId: ['SAL10001'],
290
+ level: 'suburb', areaId: ['SAL10001'],
266
291
  });
267
292
 
268
293
  // Yield history
269
294
  const yields = await client.markets.trends.yieldHistory({
270
- level: 'suburb',
271
- areaId: ['SAL10001'],
272
- });
273
-
274
- // Supply & demand (inventory, vacancies, clearance rate)
275
- const supply = await client.markets.trends.supplyDemand({
276
- level: 'suburb',
277
- areaId: ['SAL10001'],
295
+ level: 'suburb', areaId: ['SAL10001'],
278
296
  });
279
297
 
280
298
  // Search interest index (buy/rent search indices)
281
299
  const search = await client.markets.trends.searchIndex({
282
- level: 'suburb',
283
- areaId: ['SAL10001'],
300
+ level: 'suburb', areaId: ['SAL10001'],
284
301
  });
285
302
 
286
303
  // Hold period
287
304
  const hold = await client.markets.trends.holdPeriod({
288
- level: 'suburb',
289
- areaId: ['SAL10001'],
290
- });
291
-
292
- // Performance essentials (price, rent, sales, rentals, yield)
293
- const perf = await client.markets.trends.performance({
294
- level: 'suburb',
295
- areaId: ['SAL10001'],
305
+ level: 'suburb', areaId: ['SAL10001'],
296
306
  });
297
307
 
298
308
  // Growth rates (price, rent, yield changes)
299
309
  const growth = await client.markets.trends.growthRates({
300
- level: 'suburb',
301
- areaId: ['SAL10001'],
310
+ level: 'suburb', areaId: ['SAL10001'],
302
311
  });
303
312
 
304
313
  // Demand profile (sales by dwelling type and bedrooms)
305
314
  const demand = await client.markets.trends.demandProfile({
306
- level: 'suburb',
307
- areaId: ['SAL10001'],
315
+ level: 'suburb', areaId: ['SAL10001'],
316
+ });
317
+
318
+ // Stock on market
319
+ const som = await client.markets.trends.stockOnMarket({
320
+ level: 'suburb', areaId: ['SAL10001'],
321
+ });
322
+
323
+ // Days on market
324
+ const dom = await client.markets.trends.daysOnMarket({
325
+ level: 'suburb', areaId: ['SAL10001'],
326
+ });
327
+
328
+ // Clearance rate
329
+ const cr = await client.markets.trends.clearanceRate({
330
+ level: 'suburb', areaId: ['SAL10001'],
331
+ });
332
+
333
+ // Vacancy rate
334
+ const vac = await client.markets.trends.vacancy({
335
+ level: 'suburb', areaId: ['SAL10001'],
308
336
  });
309
337
  ```
310
338
 
@@ -321,6 +349,56 @@ Common trend parameters:
321
349
  | `limit` | number | Max results (default 100, max 1000) |
322
350
  | `offset` | number | Pagination offset |
323
351
 
352
+ ## Internal API
353
+
354
+ Some endpoints require the `internal_api` scope on your API key. These are accessed via the `client.internal` namespace:
355
+
356
+ ```typescript
357
+ // Address search (trigram similarity matching)
358
+ const results = await client.internal.address.search({ q: '100 George St Sydney' });
359
+
360
+ // Address insights (enriched address data)
361
+ const insights = await client.internal.address.insights({
362
+ address: '15 Miranda Court, Noble Park VIC 3174',
363
+ });
364
+
365
+ // Automated Valuation Model (batch, up to 50 properties)
366
+ const avm = await client.internal.property.avm({
367
+ addressKey: ['100102HICKOXSTREETTRARALGONVIC3844'],
368
+ });
369
+
370
+ // Market snapshots with filtering
371
+ const snapshots = await client.internal.markets.snapshots({
372
+ level: 'suburb',
373
+ propertyType: ['house'],
374
+ areaId: ['SAL10001'],
375
+ });
376
+
377
+ // Advanced market query with logical filters
378
+ const queryResults = await client.internal.markets.query({
379
+ level: 'suburb',
380
+ mode: 'search',
381
+ property_types: ['house'],
382
+ typical_price_min: 500_000,
383
+ logic: {
384
+ and: [
385
+ { field: 'one_y_price_growth', gte: 0.05 },
386
+ { field: 'vacancy_rate', lte: 0.03 },
387
+ ],
388
+ },
389
+ });
390
+
391
+ // Internal trend endpoints
392
+ const supply = await client.internal.markets.trends.supplyDemand({
393
+ level: 'suburb', areaId: ['SAL10001'],
394
+ });
395
+ const perf = await client.internal.markets.trends.performance({
396
+ level: 'suburb', areaId: ['SAL10001'],
397
+ });
398
+ ```
399
+
400
+ If you call an internal method without the required scope, the API will return a 403 error.
401
+
324
402
  ## Request Cancellation
325
403
 
326
404
  All methods accept an `AbortSignal` for cancellation:
@@ -332,8 +410,8 @@ const controller = new AbortController();
332
410
  setTimeout(() => controller.abort(), 5000);
333
411
 
334
412
  try {
335
- const results = await client.address.search({
336
- q: '100 George St',
413
+ const results = await client.address.geocode({
414
+ address: '100 George St',
337
415
  signal: controller.signal,
338
416
  });
339
417
  } catch (err) {
@@ -360,20 +438,20 @@ import {
360
438
  const client = new HtAgApiClient({ apiKey: 'sk-...' });
361
439
 
362
440
  try {
363
- const results = await client.address.search({ q: 'Syd' });
441
+ const results = await client.address.geocode({ address: 'Syd' });
364
442
  } catch (err) {
365
443
  if (err instanceof AuthenticationError) {
366
- // 401 or 403 bad API key
444
+ // 401 or 403 -- bad API key or insufficient scope
367
445
  console.error(`Auth failed (HTTP ${err.status})`);
368
446
  } else if (err instanceof RateLimitError) {
369
- // 429 throttled (after exhausting retries)
447
+ // 429 -- throttled (after exhausting retries)
370
448
  console.error('Rate limited, try again later');
371
449
  } else if (err instanceof ValidationError) {
372
- // 400 or 422 bad request params
450
+ // 400 or 422 -- bad request params
373
451
  console.error(`Invalid request: ${err.message}`);
374
452
  console.error('Details:', err.body);
375
453
  } else if (err instanceof ServerError) {
376
- // 5xx upstream failure (after exhausting retries)
454
+ // 5xx -- upstream failure (after exhausting retries)
377
455
  console.error(`Server error (HTTP ${err.status})`);
378
456
  } else if (err instanceof HtAgError) {
379
457
  // Network/timeout/other
@@ -383,11 +461,11 @@ try {
383
461
  ```
384
462
 
385
463
  All errors carry:
386
- - `message` human-readable description
387
- - `status` HTTP status code (if applicable)
388
- - `body` raw response body
389
- - `url` the request URL that failed
390
- - `cause` the underlying error (for network failures)
464
+ - `message` -- human-readable description
465
+ - `status` -- HTTP status code (if applicable)
466
+ - `body` -- raw response body
467
+ - `url` -- the request URL that failed
468
+ - `cause` -- the underlying error (for network failures)
391
469
 
392
470
  ## Retries
393
471
 
@@ -414,7 +492,7 @@ const client = new HtAgApiClient({
414
492
  |--------|------|---------|-------------|
415
493
  | `apiKey` | string | required | Your HtAG API key |
416
494
  | `environment` | `'dev'` \| `'prod'` | `'prod'` | API environment |
417
- | `baseUrl` | string | | Custom base URL (overrides environment) |
495
+ | `baseUrl` | string | -- | Custom base URL (overrides environment) |
418
496
  | `timeout` | number | `30000` | Request timeout in milliseconds |
419
497
  | `maxRetries` | number | `3` | Maximum retry attempts |
420
498
  | `retryBaseDelay` | number | `500` | Base delay between retries in ms |
@@ -438,9 +516,12 @@ All types are exported for use in your application:
438
516
  ```typescript
439
517
  import type {
440
518
  AddressRecord,
441
- AddressSearchResult,
519
+ GeocodeRecord,
442
520
  SoldPropertyRecord,
443
- MarketSnapshot,
521
+ PropertySummaryRecord,
522
+ PropertyEstimatesRecord,
523
+ PropertyMarketRecord,
524
+ MarketSummaryRecord,
444
525
  PriceHistoryOut,
445
526
  RentHistoryOut,
446
527
  BaseResponse,