Property Estimate API

Instant expected sale price range, council valuation, sales history and rental estimate for any NZ address. Built to be fed straight from the Google Places API. Requires an API key.

Type an address to try it.

Base URL

https://estimate.remark.nz

All endpoints are GET, return JSON unless noted, and allow cross-origin requests from any page (Access-Control-Allow-Origin: *).

Authentication

Every endpoint except this page and /health needs an API key. You will have been given one; if not, email oj@remark.nz. Send it in any one of these ways:

MethodExample
Header (preferred)Authorization: Bearer YOUR_KEY
Header (alternative)X-API-Key: YOUR_KEY
Query string (Sheets, quick tests)?key=YOUR_KEY

A missing or wrong key returns 401 {"error":"unauthorised"}. Keys are per person or per app, so keep yours out of public repos and client-side code where possible; if it leaks, ask for it to be rotated. If you must call from a browser, proxy through your own backend or accept that the key is visible.

Calls are logged (timestamp, key label, address queried, result, status, latency, caller IP and country) for support and fair-use monitoring. The key itself is never stored.

Endpoints

RequestReturns
/estimate?address=10 Hassing Place, CromwellFull property JSON (shape below). This is the one you normally want.
/estimate?address=…&format=textOne plain-text line: address, low – high, mid, estimate date, CV, last sale.
/estimate?address=…&raw=1Same JSON plus a raw object with the untouched upstream payloads.
/estimate?address=…&nocache=1Skips the 6-hour edge cache and fetches fresh.
/property/<property_id>Same JSON, looked up by property ID (from a previous response).
/search?q=10 HassingAddress candidates only, with IDs and coordinates. No estimate.
/batch?addresses=addr one|addr twoUp to 20 lookups in one call, separated by |. Returns {count, results:[…]}; a failed address carries an error instead of failing the whole batch.
/health{ok:true}

URL-encode the address if your client does not do it for you (%20 for spaces, %2F for the slash in 3/15 Frankton Road). A suburb or town after the street helps disambiguate; a comma is optional.

Feeding it from Google Places

Two ways, pick whichever your code already has in hand.

1. Pass formatted_address as-is

https://estimate.remark.nz/estimate?address=10 Hassing Place, Cromwell 9310, New Zealand

The postcode and , New Zealand are stripped automatically before matching.

2. Pass the address_components types as parameters

https://estimate.remark.nz/estimate?subpremise=3&street_number=15&route=Frankton Road&sublocality=Queenstown&locality=Queenstown
Google component typeQuery parameter
subpremise (unit / flat)subpremise
street_numberstreet_number
route (street name)route
sublocality / sublocality_level_1 / neighborhood (suburb)sublocality
locality (town / city)locality
// JavaScript: from a Places Autocomplete place object
const get = t => (place.address_components.find(c => c.types.includes(t)) || {}).long_name || '';
const params = new URLSearchParams({
  subpremise: get('subpremise'), street_number: get('street_number'), route: get('route'),
  sublocality: get('sublocality') || get('sublocality_level_1') || get('neighborhood'),
  locality: get('locality'),
});
const data = await (await fetch('https://estimate.remark.nz/estimate?' + params, { headers: { Authorization: 'Bearer YOUR_KEY' } })).json();

If both address and components are sent, address wins. Restrict Places Autocomplete to componentRestrictions: { country: 'nz' } and types: ['address'] for best results.

Examples

curl

curl -H "Authorization: Bearer YOUR_KEY" "https://estimate.remark.nz/estimate?address=10%20Hassing%20Place%2C%20Cromwell&format=text"
curl -H "Authorization: Bearer YOUR_KEY" "https://estimate.remark.nz/estimate?address=10%20Hassing%20Place%2C%20Cromwell" | jq .estimate

PowerShell

$h = @{ Authorization = "Bearer YOUR_KEY" }
$r = Invoke-RestMethod "https://estimate.remark.nz/estimate?address=10 Hassing Place, Cromwell" -Headers $h
$r.estimate            # low / mid / high / date
$r.council_valuation.capital
Invoke-RestMethod "https://estimate.remark.nz/estimate?address=10 Hassing Place, Cromwell&format=text" -Headers $h

Python

import requests
H = {"Authorization": "Bearer YOUR_KEY"}
r = requests.get("https://estimate.remark.nz/estimate", params={"address": "10 Hassing Place, Cromwell"}, headers=H).json()
if "error" in r:
    print(r["error"])
else:
    e = r["estimate"]
    print(r["matched_address"], e["low"], e["mid"], e["high"], e["date"])

JavaScript (browser or Node)

const res = await fetch("https://estimate.remark.nz/estimate?address=" + encodeURIComponent("10 Hassing Place, Cromwell"), {
  headers: { Authorization: "Bearer YOUR_KEY" },
});
const data = await res.json();
if (data.error) console.log(data.error);
else console.log(data.matched_address, data.estimate.low, data.estimate.high);

Google Sheets

=IMPORTDATA("https://estimate.remark.nz/estimate?address="&ENCODEURL(A2)&"&format=text&key=YOUR_KEY")

Sheets caches IMPORTDATA results for a while; the text format keeps the result to one cell.

Response shape

{
  "query": "10 Hassing Place, Cromwell",
  "matched_address": "10 Hassing Place, Cromwell",     // always check this
  "property_id": "10a2cfb4-3f55-426b-8e7b-a4e268b01121",
  "lat": -45.0412, "lng": 169.1841,

  "estimate": {                       // null if no estimate exists for the property
    "low": 810000, "mid": 865000, "high": 915000,
    "date": "2026-09-03",             // when the estimate was last revised
    "display": "$810K – $915K",
    "model": "v3 (2025-01-22)"
  },
  "rental_estimate": { "low_per_week": 880, "high_per_week": 1480, "date": "2026-06-02", "gross_yield": "7.1%" },
  "council_valuation": {
    "capital": 830000, "land": 480000, "improvements": 350000, "date": "2025-09-01",
    "history": [ { "date": "2025-09-01", "capital": 830000, … }, { "date": "2022-10-01", … } ]
  },
  "last_sale": { "price": 383000, "date": "2014-08-13", "type": "S11", "council_confirmed": true },
  "sales": [ …every recorded sale, newest first… ],
  "property": {
    "bedrooms": 3, "bathrooms": 2, "car_spaces": 1,
    "floor_area_m2": 185, "land_area_m2": 644, "decade_built": "1970",
    "ownership": "Freehold", "land_use": "house",
    "legal_description": "LOT 5 DP 17109", "title": "OT11C/1270", "valuation_ref": "28506/19100",
    "suburb": "Cromwell", "city": "Cromwell"
  },
  "listing": {                        // only present while the property is on the market
    "headline": "Space, Flexibility and Greenway Living",
    "display_price": "Deadline sale by 24 Sept",
    "agency": "PGG Wrightson",
    "trademe_ids": ["6112545398"],
    "cover_image": "https://…"
  },
  "trademe_url": "https://www.trademe.co.nz/a/property/residential/sale/listing/6112545398",
  "candidates": [ …other address matches, if the top pick looks wrong… ],
  "fetched_at": "2026-09-13T01:02:03.000Z"
}

All money values are whole NZ dollars. Areas are square metres. Dates are YYYY-MM-DD.

Errors

StatusMeaning
400Missing address / q / addresses parameter.
401Missing or invalid API key. See Authentication above.
404no street address matched that query — nothing in the index looked like a street address. Or address found but no property record exists for it — typical for commercial sites, brand-new subdivisions and some units; the response includes candidates.
502Upstream data source unreachable. Retry later.

Error bodies are JSON: {"query": "…", "error": "…", "status": 404}. Success bodies never contain an error key, so if (data.error) is a safe check.

Good to know

Questions? Contact oj@remark.nz for support.