35 endpoints. Search by name, path, or keyword to jump straight to one.
POST/v1/suburbs/batch1 credit per suburb, 3 with sold
Up to 5 (Free), 10 (Hobby), 20 (Starter, Builder), 50 (Pro), 100 (Growth, Scale) or 200 (Enterprise) suburbs per call, each with what `GET /v1/suburbs/{state}/{suburb}` returns (`stats`) and optionally what `.../sold` returns (`sold`). One call uses one rate-limit token however many suburbs it carries, so a sweep that was two calls per suburb becomes one call per batch.
Priced per suburb, exactly as if you called each part singly: 1 credit for `stats`, plus 2 for `sold`, so 3 credits per suburb with both. A suburb not in the dataset comes back with `status: 404` in its slot and is not charged, and the `sold` part of a suburb outside our sold-records coverage (`has_sold_records: false`) is not charged either. If your remaining allowance cannot cover the batch, the whole call returns 429 `quota_exceeded` and nothing is charged.
Each result has its own `status`; the same plan gates apply to every field as on the single-suburb endpoints.
Parameters
| Name | In | Required | Description |
|---|
| suburbs | query | Yes | Body field: array of `{ state, suburb, postcode? }`. Duplicates are fetched and charged once. |
| include | query | No | Body field: `["stats"]` (default), `["sold"]` or both. |
| sold | query | No | Body field: `{ months, limit }` for the sold part, as on `.../sold`. |
Request
curl -X POST "https://api.propradar.com.au/v1/suburbs/batch" \
-H "X-API-Key: pr_live_..." \
-H "Content-Type: application/json" \
-d '{
"suburbs": [
{
"state": "NSW",
"suburb": "Castle Hill"
},
{
"state": "VIC",
"suburb": "Richmond"
}
],
"include": [
"stats",
"sold"
],
"sold": {
"months": 12,
"limit": 5
}
}'
Response
{ "query": { "include": ["stats", "sold"], "suburbs": 2, "sold": { "months": 12, "limit": 5 } },
"billing": { "credits_per_suburb": 3, "credits_charged": 6, "credits_refunded": 0 },
"results": [{ "state": "NSW", "suburb": "Castle Hill", "postcode": null, "status": 200,
"stats": { "suburb": "CASTLE HILL", "...": "as GET /v1/suburbs/{state}/{suburb}" },
"sold": { "sold": ["..."], "pagination": { "...": "..." } } }] }
GET/v1/suburbs
The coverage index. Returns every COVERED suburb, with a per-resource `available` map so you can tell BEFORE spending a request which sub-resources will actually return data for it.
Start an integration here. Enumerate once, store the suburbs you care about, then call the detail and sub-resource endpoints only where `available` says there is something to fetch. Guessing suburb names instead is the single largest source of wasted credits on this API: over one 30-day window, 19.5% of all calls returned 404, across 4,896 distinct suburb names that were never in the dataset.
`available.detail` is always true (every row here resolves on /v1/suburbs/{state}/{suburb} with a non-empty body). A suburb only appears once it has at least one substantive statistic, which is the same gate the public /coverage page uses: a bare row in our stats table returns 200 with an empty body and is not counted as coverage by either surface. `price_history` and `market_cycle` are deliberately absent from the map: both 404 on transaction-volume thresholds rather than on simple data presence, so a flag would be wrong often enough to mislead.
Available on every plan including free, and paginated with offset/limit, and at the 1000 maximum the whole dataset is about 13 requests.
Parameters
| Name | In | Required | Description |
|---|
| state | query | No | 2-3 letter code (NSW, VIC, QLD, SA, WA, TAS, ACT, NT). Omit for all states. |
| has | query | No | Comma-separated availability flags; returns only suburbs where ALL are true. One or more of: listings, sold, heat_history, medians. |
| limit | query | No | Rows per page, 1-1000 (default 200). |
| offset | query | No | Row offset for paging (default 0). Use pagination.next_offset. |
Request
curl "https://api.propradar.com.au/v1/suburbs?state=NSW&has=heat_history&limit=2" \
-H "X-API-Key: pr_live_..."
Response
{
"query": { "state": "NSW", "has": ["heat_history"], "limit": 2, "offset": 0 },
"suburbs": [
{ "suburb": "ABBOTSFORD", "state": "NSW", "postcode": 2046,
"available": { "detail": true, "listings": true, "sold": true,
"heat_history": true, "medians": true } },
{ "suburb": "ABERCROMBIE RIVER", "state": "NSW", "postcode": 2795,
"available": { "detail": true, "listings": false, "sold": false,
"heat_history": true, "medians": false } }
],
"pagination": { "offset": 0, "limit": 2, "total": 4162, "next_offset": 2 }
}
GET/v1/healthFree
Free: costs no credits (it still counts toward the per-second rate limit). Checks authentication and returns the caller's plan and remaining monthly credits. Useful as a smoke test from monitoring.
Request
curl "https://api.propradar.com.au/v1/health" \
-H "X-API-Key: pr_live_..."
Response
{
"status": "ok",
"plan": "free",
"quota": { "limit": 50, "used": 12, "remaining": 38,
"resets_at_unix": 1748736000, "unit": "credits" },
"burst": { "limit": 20, "remaining": 19, "unit": "requests" },
"timestamp": "2026-05-24T08:42:00.000Z"
}
Testing these endpoints?
Create a free key and run the examples against live responses before you wire them into your app.
See plans & get free keyGET/v1/usageFree
Current-month credit consumption plus daily and per-endpoint breakdowns over a trailing window: the dashboard's usage panel as JSON. Calls to this endpoint are free: they cost no credits, don't consume burst tokens, and don't appear in the usage numbers, so it's safe to poll from monitoring. It also keeps working once your credits are exhausted. Anyone holding the API key can call it, which makes it the easiest way to give teammates usage visibility without a dashboard login.
Parameters
| Name | In | Required | Description |
|---|
| days | query | No | Trailing window in days for the daily/endpoint breakdowns (default 30, max 90). |
Request
curl "https://api.propradar.com.au/v1/usage?days=30" \
-H "X-API-Key: pr_live_..."
Response
{
"plan": "pro",
"quota": { "limit": 80000, "used": 12480, "remaining": 67520,
"resets_at_unix": 1754006400, "unit": "credits" },
"window_days": 30,
"daily": [
{ "date": "2026-07-01", "request_count": 512,
"served_count": 512, "rejected_count": 0 },
{ "date": "2026-07-02", "request_count": 488,
"served_count": 486, "rejected_count": 2 }
],
"endpoints": [
{ "endpoint": "/v1/properties/{id}", "request_count": 8210,
"client_error_count": 14, "rate_limited_count": 0,
"server_error_count": 0, "p50_ms": 62 }
],
"timestamp": "2026-07-07T08:42:00.000Z"
}
GET/v1/properties/search
Pass a street + postcode; the API does a case-insensitive prefix match against on-market and sold records and returns up to 10 matching property_ids in `matches[]`. The top-level `property_id`, `found`, `on_market`, and `sold_record_available` fields mirror the first match for v1 back-compat. You don't need the full "street, SUBURB, STATE, postcode" form; just the street as it would appear on a listing. If no match is found, `property_id` is null, `found:false`, `matches:[]`, and a scope hint explains that off-market properties may be outside coverage.
Start here for any workflow that needs a property_id: the other property endpoints all take an id, and search is the only one that can resolve a raw address.
Parameters
| Name | In | Required | Description |
|---|
| address | query | Yes | Street address (e.g. "1/8 Bristol Road" or "284 O'Hea Street"). Trailing comma tolerated. |
| postcode | query | Yes | 4-digit postcode |
Request
curl "https://api.propradar.com.au/v1/properties/search?address=1/8+Bristol+Road&postcode=3044" \
-H "X-API-Key: pr_live_..."
Response
{
"property_id": "a1b2c3d4",
"found": true,
"on_market": true,
"sold_record_available": false,
"matches": [
{ "property_id": "a1b2c3d4",
"address": "1/8 Bristol Road, PASCOE VALE, VIC, 3044",
"on_market": true,
"sold_record_available": false }
]
}
GET/v1/properties/{property_id}2 credits
Costs 2 credits. Returns the full property record: attributes, current listing, valuation, rental estimate, and last sold record. The example `property_id` below is illustrative: properties get sold and rotate out of the active dataset, so always resolve a current id via `/v1/properties/search` first.
**Branch on `listing.listing_status`**, which is `on_market`, `withdrawn`, or `sold`. `withdrawn` means the listing stopped being advertised and we hold no sale for it, so do NOT report it as sold. A withdrawn property can still carry a `last_sale` block from an EARLIER sale; that is history, not the outcome of this campaign. `withdrawn` is reversible: a relist returns the property to `on_market`. The older `listing.on_market` boolean is retained as a derived alias and is deprecated, since it cannot tell a withdrawal from a sale.
A property we have seen but that is no longer advertised returns 200 with `listing_status: "withdrawn"`, not 404. A 404 means the address is not in the dataset at all.
`_links.web` is the canonical PropRadar page for the property. The API does not return third-party listing links, so use this when you need somewhere to send a user.
Pro+ keys also receive an `analytics` block with the investment score, component breakdown, and planning-risk overlays. Free/Hobby/Starter responses omit the field entirely (no schema regression).
Tier-gated fields carry their normal null/omitted value for plans that cannot access them, and every such field is enumerated in a top-level `locked_fields` object with the plan needed to unlock it, so a null there means 'upgrade to reveal', not 'missing from the dataset'. When anything is gated, a sibling `locked_fields_note` string summarises it in one sentence (e.g. "20 fields are hidden on your plan (18 require Hobby, 2 require Pro). See locked_fields for how to unlock each one.") for a caller reading the response quickly rather than walking the nested object; both keys are absent once your plan clears every gate. On the Free tier the record identifies the property and its physical attributes; every derived or commercial value is Hobby and above: the valuation and its range and confidence, the rental estimate and yield, `potential_sell_price`, the last sale, the listing's asking prices, sale type and days on market, zoning and year built. Coordinates (`geo`) are returned on every plan.
Parameters
| Name | In | Required | Description |
|---|
| property_id | path | Yes | 8- or 12-char hex hash of the normalised address. Use /v1/properties/search to resolve an address. |
Request
curl "https://api.propradar.com.au/v1/properties/{property_id}" \
-H "X-API-Key: pr_live_..."
Response
{
"property_id": "a1b2c3d4",
"address": {
"full": "1/8 Bristol Road, PASCOE VALE, VIC, 3044",
"street": "1/8 Bristol Road",
"suburb": "PASCOE VALE",
"state": "VIC",
"postcode": 3044
},
"attributes": { "bedrooms": 2, "bathrooms": 1, "parking": 2,
"property_type": "Apartment", "year_built": 1995 },
"listing": { "listing_status": "on_market", "on_market": true,
"off_market_since": null, "asking_price_low": 640000,
"asking_price_high": 720000, "sale_type": "Private Sale",
"days_on_market": 14 },
"valuation": { "estimated_value": 660000,
"estimated_value_low": 600000,
"estimated_value_high": 720000,
"confidence": "high", "as_of": "2026-05-15",
"agreement": { "estimate_support": "multiple", "dispersion_pct": 8.4, "review_recommended": false } },
"rental": { "estimated_weekly": 495,
"estimated_yield_pct": 3.9, "confidence": "high" },
"last_sale": { "sold_price": 580000, "sold_date": "2018-06-12", "days_on_market": 31 },
"potential_sell_price": 665000,
"data_quality_flags": [],
"analytics": {
"investment_score": 68.2,
"investment_score_confidence": "high",
"score_breakdown": { "yield_score": 7.2, "capital_growth": 14.5, ... },
"risk_overlays": { "flood": false, "bushfire": false, "heritage": true }
}
}
GET/v1/comparables2 credits
Costs 2 credits. Sold properties within a radius and time window of a reference point, ranked by distance. `property_type` is exact-match, case-insensitive: pass "House" for houses only, not "Townhouse". Use `pagination.next_cursor` to walk deeper than the offset cap. Requires Hobby or higher; Free keys get 403 plan_upgrade_required (credits refunded). Specify the reference point EITHER as `property_id` (Hobby+) OR as `lat`+`lng` (Pro+); sending both is a 400. Reach for `lat`/`lng` when the subject property is not in our data: address lookup covers on-market listings and properties with a sold record, so a long-held, never-listed home resolves in neither and 404s on the `property_id` path. That is a coverage limit, not a plan gate (upgrading does not change it), so geocode the address yourself and pass the point.
Parameters
| Name | In | Required | Description |
|---|
| property_id | query | No | Reference property already in the dataset. Mutually exclusive with lat/lng. |
| lat | query | No | Latitude of your own reference point (Pro+). Requires lng. Australia only (-44 to -9); out of range returns 400, which also catches a swapped pair. |
| lng | query | No | Longitude of your own reference point (Pro+). Requires lat. Australia only (112 to 154). |
| radius_m | query | No | Search radius in metres (default 1000, max 5000) |
| months | query | No | Look-back window in months (default 12, max 24) |
| property_type | query | No | Exact match, case-insensitive. e.g. House, Apartment, Townhouse, Unit, Villa. If omitted, Land and Commercial are excluded (not comparable to a dwelling); pass the type explicitly to get them. |
| beds | query | No | Match exact bedroom count |
| limit | query | No | Page size (default 20, max 20). Use `cursor` for deeper walks. |
| cursor | query | No | Opaque cursor from the previous response's `pagination.next_cursor`. Preferred for deep pagination. |
| offset | query | No | Legacy offset pagination. Capped at 100: use `cursor` to walk deeper or narrow `radius_m`/`months`. |
Request
curl "https://api.propradar.com.au/v1/comparables?lat=-32.059&lng=115.957&radius_m=1000&months=12" \
-H "X-API-Key: pr_live_..."
Response
// lat/lng mode: property_id is null, geo echoes your own point.
{
"reference": { "property_id": null, "address": null,
"geo": { "lat": -32.059, "lng": 115.957 } },
"comparables": [{ "property_id": "...", "distance_m": 280, "sold_price": 712000, ... }],
"pagination": { "limit": 20, "offset": 0,
"next_cursor": "eyJkbSI6MjgwLCJpIjoiYTFiMmMzZDQifQ", "next_offset": 20 }
}
GET/v1/suburbs/{state}/{suburb}
Medians, growth rates, days on market, supply/demand for a suburb. Updated fortnightly. When a suburb name spans multiple postcodes (e.g. MELBOURNE VIC has three), the best-quality row is returned and the other matches are listed in `alternates[]` so you can re-query unambiguously; pass `?postcode=` to pin a specific one.
A 404 carries `error.suggestions[]` when the name you sent is a near-miss for real suburbs in that state, each with `suburb`, `state` and `postcode` so you can retry directly. Spelling is the largest single source of wasted credits on this API, so branch on that array rather than retrying the same string.
Parameters
| Name | In | Required | Description |
|---|
| state | path | Yes | Two-letter state code (VIC, NSW, QLD, ...) |
| suburb | path | Yes | Suburb name (URL-encoded) |
| postcode | query | No | 4-digit postcode. Use to disambiguate when a suburb name spans multiple postcodes. |
Request
curl "https://api.propradar.com.au/v1/suburbs/VIC/Pascoe%20Vale" \
-H "X-API-Key: pr_live_..."
Response
{
"suburb": "PASCOE VALE", "state": "VIC", "postcode": 3044,
"medians": { "house_price": 1180000, "unit_price": 645000, "house_weekly_rent": 480, "unit_weekly_rent": 395 },
"growth": { "house": { "qtr_pct": 1.83, "1y_pct": 7.43, "3y_pct": 18.07, "5y_pct": 31.12, "10y_pct": 62.38 }, "unit": { ... } },
"yields": { "house_gross_pct": 2.11, "unit_gross_pct": 3.18 },
"market_dynamics": { "house_days_on_market": 33, "unit_days_on_market": 28,
"vacancy_rate_pct": 1.43, "house_inventory_months": 2.17,
"house_stock_on_market_pct": 0.63, "sold_vs_asking_pct": 2.1,
"house_heat_score": 72, "house_sales_12mo": 145, ... },
"demographics": { ... }, "location": { ... },
// Starter+ only:
"livability": { "walk_score": 78, "transit_score": 64,
"crime_rate_per_1000": 42.1, "crime_rank": 33,
"school_index": 1043, "schools_nearby": 12 },
// Pro+ only:
"supply_demand": { "house": { "building_approvals": 53, "avg_hold_period_years": 9.48, "buyer_demand": "high" }, "unit": { ... } },
"rent_trends": { "house": { "qtr_pct": 0.97, "1y_pct": 4.83, "3y_pct": 13.12, "5y_pct": 19.37, "10y_pct": 31.44 }, "unit": { ... } },
"auction": { "median_premium_pct": 4.0, "sample_size_band": "100-249", "granularity": "suburb" },
"data_quality_flags": ["aggregated_wider_geography"],
"alternates": [],
"as_of": "2026-05-12T03:00:00Z"
}
GET/v1/suburbs/rankings2 credits
Costs 2 credits. Ranks suburbs in a state by yield, growth, market heat, median price, or days on market after applying source-agreement, sample-size, suspicious-data, and sanity filters. Use it to build high-confidence suburb screens without surfacing raw data artifacts.
Parameters
| Name | In | Required | Description |
|---|
| state | query | Yes | State code, e.g. VIC or NSW |
| metric | query | No | yield_houses, yield_units, growth_1yr_houses, growth_1yr_units, heat_score_houses, heat_score_units, median_price_houses, median_price_units, days_on_market_houses, or days_on_market_units |
| property_type | query | No | houses or units; shorthand when metric is omitted |
| min_median_price | query | No | Optional trusted median price floor |
| max_median_price | query | No | Optional trusted median price ceiling |
| top | query | No | Rows to return (default 20, max 50) |
Request
curl "https://api.propradar.com.au/v1/suburbs/rankings?state=VIC&metric=yield_units&top=10" \
-H "X-API-Key: pr_live_..."
Response
{ "metric": "yield_units", "state": "VIC", "top": 10,
"results": [{ "rank": 1, "suburb": "PASCOE VALE", "postcode": 3044,
"metric_value": 5.4, "metric_confidence": "medium",
"median_unit_price": 615000, "yield_units": 5.4,
"sales_12mo_units": 42 }] }
GET/v1/suburbs/screen5 credits
Costs 5 credits, the most expensive read on the API: it scans every covered suburb. Returns every suburb that clears ALL of the thresholds you pass. Stack as many as you like in one call: yield, 1/3/5-year price growth, 1/5-year population growth, rent growth, median price, vacancy, inventory, market heat, days on market, distance to the CBD, unemployment, socioeconomic score, owner-occupier share, population and sales volume.
**A worked screen, start to finish.**
1. Screen the suburbs. For NSW houses above 5% yield, 25% five-year growth and 1% five-year population growth: `GET /v1/suburbs/screen?state=NSW&min_yield=5&min_growth_5yr=25&min_population_growth_5yr=1`
2. Read `total_matches`. If it is 0, read `hint`: when the confidence bar is what emptied the result, the response tells you how many suburbs would match at `min_confidence=any`, so you know whether to relax the bar or a threshold.
3. Narrow or widen. `location_type=metro` or `regional` is usually the most effective single filter, because a high yield bar on its own tends to surface small rural localities; `min_population` and `max_distance_to_cbd_km` do similar work. Order the result with `sort=` (default `yield`) and page with `limit` and `offset`.
4. Get the properties. Pass each match to `/v1/properties/listings?state=NSW&suburb=<suburb>` for the individual listings, or to `/v1/suburbs/{state}/{suburb}` for the full statistics behind the row.
Use `/v1/suburbs/rankings` instead when you want the top N ordered by a single metric. Use this endpoint when you want everything above a set of cutoffs.
**Set `min_confidence` deliberately.** It defaults to `medium` and gates the evidence behind the filters you actually used: the median always, yield only when you filter or sort on yield, growth only when you filter or sort on growth. Screening on population growth alone is never narrowed by yield confidence. Drop to `low` or `any` to widen, and read `median_confidence`, `yield_confidence` and `growth_1yr_confidence` on each row to see what you accepted.
**Check `postcode_area_figures` before acting on a row.** When true, the median and yield describe the whole postcode rather than that suburb, so several localities sharing that postcode return identical numbers and are not independent matches. These rows appear only at `min_confidence=any`.
**Three data facts that should shape your thresholds.** Multi-year growth is single-source: 8,283 suburbs carry two or more independent source families on 1-year growth and none do on the 3, 5 or 10-year windows, so `min_confidence` on a multi-year period tests only that a source reported it (8,589 suburbs, against 820 with none), and `growth_1yr_confidence` is named for the one window that carries a rating. Inventory is populated for 88% of suburbs on houses but 34% on units, so `max_inventory_months` with `property_type=units` excludes most of the table. Heat score is banded rather than continuous, with 60 and 55 alone accounting for 58% of rated suburbs, so move `min_heat_score` in steps of 5.
Invalid parameters return 400 naming the parameter at fault rather than falling back to a default, so a typo in a threshold fails loudly instead of returning a screen you did not ask for.
Parameters
| Name | In | Required | Description |
|---|
| state | query | No | State code, e.g. NSW. Omit to screen nationally. |
| postcode | query | No | Comma-separated 4-digit postcodes to restrict to, e.g. 2480,2650. Omit for no postcode restriction. |
| location_type | query | No | metro or regional. Populated for every suburb (2,524 metro, 10,125 regional), so it is the cheapest way to keep a yield screen out of micro-localities. |
| property_type | query | No | houses (default) or units; selects which median, yield, growth, rent, inventory, heat and days-on-market columns are screened |
| min_confidence | query | No | high, medium (default), low, or any. `any` also admits unrated, single-source and postcode-area figures; suspicious-data exclusions and sanity bands still apply. Applies to every figure returned: a median, yield or growth figure whose own tier is below the bar is null (its `*_confidence` field still says why). Multi-year growth follows the 1-year tier unless you filtered or sorted on that period. |
| min_yield | query | No | Minimum gross rental yield percent. Values above 12 are treated as data artifacts and never returned. |
| max_yield | query | No | Maximum gross rental yield percent |
| min_growth_1yr | query | No | Minimum 1-year median price growth percent |
| min_growth_3yr | query | No | Minimum 3-year median price growth percent |
| min_growth_5yr | query | No | Minimum 5-year median price growth percent |
| min_population_growth_1yr | query | No | Minimum 1-year population growth percent |
| min_population_growth_5yr | query | No | Minimum 5-year population growth percent |
| min_median_price | query | No | Minimum median price for the chosen property type (AUD) |
| max_median_price | query | No | Maximum median price for the chosen property type (AUD) |
| max_vacancy_rate | query | No | Maximum vacancy rate percent |
| max_days_on_market | query | No | Maximum median days on market |
| max_distance_to_cbd_km | query | No | Maximum straight-line distance to the capital city CBD, in km |
| min_rent_growth_1yr | query | No | Minimum 1-year median rent change percent, the yield trajectory behind the current yield |
| max_inventory_months | query | No | Maximum months of inventory (supply tightness). Populated for 88% of suburbs on houses but only 34% on units, so filtering it with property_type=units excludes most of the table. |
| min_heat_score | query | No | Minimum market heat score, 0-100. Banded rather than continuous: 60 and 55 alone account for 58% of rated suburbs, so a threshold of 61 drops thousands of suburbs that 60 admits. Move it in steps of 5. |
| max_unemployment_rate | query | No | Maximum unemployment rate percent |
| min_socioeconomic_score | query | No | Minimum socioeconomic (SEIFA-derived) score, 0-100 |
| min_owner_occupied_pct | query | No | Minimum share of dwellings that are owner-occupied |
| min_population | query | No | Minimum suburb population; useful for excluding micro-localities |
| min_sales_count | query | No | Minimum trailing 12-month sales for the chosen property type (default 5) |
| sort | query | No | yield, growth_1yr, growth_3yr, growth_5yr, population_growth_1yr, population_growth_5yr, median_price, days_on_market, sales_12mo, rent_growth_1yr, heat_score, or distance_to_cbd (default yield) |
| limit | query | No | Page size (default 20, max 20; larger values are reduced to 20) |
| offset | query | No | Pagination offset (max 1000) |
Request
curl "https://api.propradar.com.au/v1/suburbs/screen?state=NSW&min_yield=5&min_growth_5yr=25&min_population_growth_5yr=1" \
-H "X-API-Key: pr_live_..."
Response
{ "state": "NSW", "property_type": "houses",
"min_confidence": "medium", "sort": "yield",
"count": 3, "total_matches": 3,
"pagination": { "limit": 50, "offset": 0, "next_offset": null },
"results": [{ "suburb": "BROKEN HILL", "postcode": 2880,
"median_price": 250000, "yield_pct": 7.5,
"growth_1yr_pct": 4.2, "growth_5yr_pct": 94.0,
"population": 17700, "population_growth_5yr_pct": -1.2,
"vacancy_rate_pct": 1.4, "days_on_market": 62,
"sales_12mo": 310,
"rent_growth_1yr_pct": 8.1, "inventory_months": 2.4,
"heat_score": 60, "location_type": "regional",
"distance_to_cbd_km": 940.2, "unemployment_rate_pct": 3.1,
"socioeconomic_score": 25, "owner_occupied_pct": 66.2,
"median_confidence": "medium", "yield_confidence": "medium",
"growth_1yr_confidence": "limited",
"postcode_area_figures": false }] }
GET/v1/suburbs/{state}/{suburb}/sold2 credits
Costs 2 credits. Paginated list of sold properties, most recent first. Returns card-level fields only (id, address, beds/baths/parking, property type, sold price, sold date); call `/v1/properties/{property_id}` for land/floor area, days on market, valuation context, and the full structured address. Filter by months, property type, bedroom range, and price range. Use the opaque cursor returned in `pagination.next_cursor` for stable pagination; `offset` is supported but silently misses rows when new sold data inserts between page fetches.
**`sold_price` requires Hobby or higher.** Below Hobby it returns `null` on every row, and the top-level `locked_fields` object enumerates it with the plan needed to unlock it, so a null here means "upgrade to reveal", never "no sale on record". Every other field on the card is available on every plan, including Free.
Parameters
| Name | In | Required | Description |
|---|
| state | path | Yes | Two-letter state code |
| suburb | path | Yes | Suburb name (URL-encoded) |
| months | query | No | Look-back window (default 12, max 60) |
| property_type | query | No | Exact match, case-insensitive (House, Apartment, Townhouse, Unit, Villa) |
| min_beds | query | No | Minimum bedrooms |
| max_beds | query | No | Maximum bedrooms |
| min_price | query | No | Minimum sold price (AUD). Hobby and above; 403 below it. |
| max_price | query | No | Maximum sold price (AUD). Hobby and above; 403 below it. |
| limit | query | No | Page size (default 20, max 20). Use `cursor` for deeper walks. |
| cursor | query | No | Opaque cursor from a previous response's `pagination.next_cursor`. Recommended over `offset` for stable pagination. |
| offset | query | No | Legacy offset pagination (max 1000). Use `cursor` for deeper walks. |
Request
curl "https://api.propradar.com.au/v1/suburbs/VIC/Pascoe%20Vale/sold?months=12&min_beds=3" \
-H "X-API-Key: pr_live_..."
Response
{ "state": "VIC", "suburb": "Pascoe Vale",
"sold": [{ "property_id": "61902b5e",
"address": "2/9 Austin Crescent, Pascoe Vale, VIC, 3044",
"bedrooms": 3, "bathrooms": 1, "parking": 1,
"property_type": "Townhouse",
"sold_price": 670000, "sold_date": "2026-05-25" }],
"pagination": { "next_cursor": "eyJk...", "next_offset": 20, "limit": 20 } }
GET/v1/suburbs/{state}/{suburb}/listings3 credits
Costs 3 credits. Paginated list of currently-on-market listings, newest first. Returns card-level fields only (id, address, beds/baths/parking, property type, asking price, sale type, added date); call `/v1/properties/{property_id}` for floor area, valuation, potential sell price, yield, and the full structured address. Use `pagination.next_cursor` for stable pagination; `offset` is supported but silently misses rows when new listings appear between page fetches.
`land_size` and the `min_land_size`/`max_land_size` filters are Pro+ only, same as on `/v1/properties/listings`: below Pro the field is null and passing either filter is a 403.
**Sizing a walk:** at most 10 rows come back per call, so a full walk costs roughly `listings in the suburb / 10` calls, not a fixed page count. A quiet suburb is one call; the largest in the dataset runs to about 485 listings, or 49. Follow `next_cursor` until it is null rather than assuming a number of pages.
**Refreshing:** use `modified_since` instead of re-walking, and take listing endings from `withdrawn` events on /v1/properties/recently-modified. Note a relist keeps its ORIGINAL `added_at` when the off-market gap was under 90 days, so it reappears in its old sort position rather than at the top, so a newest-first incremental walk will not see it, but `modified_since` will.
Parameters
| Name | In | Required | Description |
|---|
| state | path | Yes | Two-letter state code |
| suburb | path | Yes | Suburb name (URL-encoded) |
| property_type | query | No | Exact match, case-insensitive (House, Apartment, Townhouse, Unit, Villa) |
| min_beds | query | No | Minimum bedrooms |
| max_beds | query | No | Maximum bedrooms |
| min_price | query | No | Minimum asking price (AUD). Hobby and above; 403 below it. |
| max_price | query | No | Maximum asking price (AUD). Hobby and above; 403 below it. |
| min_land_size | query | No | Minimum land size in square metres. Pro+ only (403 on lower plans). Excludes listings with no land_size on file rather than treating unknown as a match. |
| max_land_size | query | No | Maximum land size in square metres. Pro+ only (403 on lower plans). Excludes listings with no land_size on file rather than treating unknown as a match. |
| modified_since | query | No | ISO 8601 timestamp. Only return listings whose record changed at or after it. Turns a nightly re-walk into a delta: pass your last run's timestamp and quiet suburbs return a single short page. Cannot report listings that ENDED (those leave this endpoint entirely), so pair it with `withdrawn` events on /v1/properties/recently-modified. |
| limit | query | No | Page size (default 10, max 10). Use `cursor` for deeper walks. |
| cursor | query | No | Opaque cursor from a previous response's `pagination.next_cursor`. Recommended over `offset` for stable pagination. |
| offset | query | No | Legacy offset pagination (max 1000). Use `cursor` for deeper walks. |
Request
curl "https://api.propradar.com.au/v1/suburbs/VIC/Pascoe%20Vale/listings?property_type=House" \
-H "X-API-Key: pr_live_..."
Response
{ "state": "VIC", "suburb": "Pascoe Vale",
"listings": [{ "property_id": "8644b8fd",
"address": "2/28 Danin Street, Pascoe Vale, VIC, 3044",
"bedrooms": 2, "bathrooms": 2, "parking": 1,
"property_type": "Apartment",
"asking_price_low": 725000, "asking_price_high": 750000,
"land_size": 154, "sale_type": "Auction",
"added_at": "2026-05-26T16:36:43.000Z" }],
"pagination": { "next_cursor": "eyJk...", "next_offset": 10, "limit": 10 } }
GET/v1/properties/listings3 credits
Costs 3 credits. Same data as `/v1/suburbs/{state}/{suburb}/listings`, but you give a state instead of one suburb. No address or property_id needed - just state plus whatever filters you care about. Good for "3+ bedroom houses under $900k anywhere in Melbourne" instead of walking suburbs one at a time.
`suburb` and `postcode` narrow the search (comma-separated, up to 25 each, ANDed together if you pass both); leave both off and it searches the whole state. Pro+ only - lower plans get 403 before we even look at the other parameters, and the credits are refunded. `geo` is always in the response here, unlike the per-suburb endpoint, since the whole thing already requires Pro+.
A state with no suburb/postcode filter can have thousands of rows, so use `cursor` to walk it rather than `offset`.
Every row carries a `signals` block: `price_changes` (each asking-price change, oldest first, with `direction`), a `price_change_summary` (number of changes and cuts, first and latest asking price, overall change), and `relisted_at` when the property was withdrawn and advertised again. Filter with `signal=price_drop` or `signal=relisted`.
Parameters
| Name | In | Required | Description |
|---|
| state | query | Yes | Two-letter state code (VIC, NSW, QLD, WA, SA, TAS, ACT, NT) |
| suburb | query | No | Comma-separated suburb names, up to 25. Omit to search the whole state. |
| postcode | query | No | Comma-separated 3-4 digit postcodes, up to 25. |
| property_type | query | No | Exact match, case-insensitive (House, Apartment, Townhouse, Unit, Villa) |
| min_beds | query | No | Minimum bedrooms |
| max_beds | query | No | Maximum bedrooms |
| min_price | query | No | Minimum asking price (AUD) |
| max_price | query | No | Maximum asking price (AUD) |
| min_land_size | query | No | Minimum land size in square metres |
| max_land_size | query | No | Maximum land size in square metres |
| min_days_on_market | query | No | Minimum days since added_at. Capped at 365. |
| max_days_on_market | query | No | Maximum days since added_at. Use e.g. 7 or 14 for recently-listed only. Capped at 365. |
| modified_since | query | No | ISO 8601 timestamp. Only return listings whose record changed at or after it. |
| limit | query | No | Page size (default 10, max 10). Use `cursor` for deeper walks. |
| cursor | query | No | Opaque cursor from a previous response's `pagination.next_cursor`. |
| offset | query | No | Legacy offset pagination (max 1000). Use `cursor` for a state-wide sweep. |
| signal | query | No | price_drop (latest asking-price change was a cut) or relisted (withdrawn and advertised again). |
| signal_days | query | No | Window for `signal`, in days (default 30 for price_drop, 90 for relisted; max 365). |
Request
curl "https://api.propradar.com.au/v1/properties/listings?state=VIC&suburb=Richmond,South%20Yarra&min_beds=3&max_price=900000" \
-H "X-API-Key: pr_live_..."
Response
{ "state": "VIC",
"listings": [{ "property_id": "8644b8fd",
"address": "2/28 Danin Street, Richmond, VIC, 3121",
"suburb": "RICHMOND", "postcode": 3121,
"bedrooms": 3, "bathrooms": 2, "parking": 1,
"property_type": "Townhouse",
"asking_price_low": 825000, "asking_price_high": 875000,
"land_size": 210, "sale_type": "Auction",
"added_at": "2026-05-26T16:36:43.000Z",
"geo": { "lat": -37.8197, "lng": 145.0007 } }],
"pagination": { "next_cursor": "eyJk...", "next_offset": 10, "limit": 10 } }
GET/v1/properties/{property_id}/history
Chronological timeline of listing, price-change, and sold events for a property.
Parameters
| Name | In | Required | Description |
|---|
| property_id | path | Yes | 8- or 12-char hex property id |
Request
curl "https://api.propradar.com.au/v1/properties/{property_id}/history" \
-H "X-API-Key: pr_live_..."
Response
{ "property_id": "a1b2c3d4", "history": [{ "event_type": "listed", "date": "2026-01-15", "price": 640000 }, ...], "currently_on_market": true }
GET/v1/properties/{property_id}/sold_summary
Convenience endpoint for the latest sold record, including sold-vs-asking percentage computed server-side. Returns 404 when the property has no sold record in our dataset, and 403 plan_upgrade_required on Free (this endpoint is Hobby and above; `/v1/properties/{property_id}` is the free-tier equivalent, but returns null for `last_sale.sold_price` there too - see that endpoint's `locked_fields`).
Parameters
| Name | In | Required | Description |
|---|
| property_id | path | Yes | 8- or 12-char hex property id |
Request
curl "https://api.propradar.com.au/v1/properties/{property_id}/sold_summary" \
-H "X-API-Key: pr_live_..."
Response
{ "property_id": "a1b2c3d4",
"sold": { "price": 735000, "date": "2026-05-20", "days_on_market": 31, "sale_type": "Private Sale" },
"asking": { "price_low": 700000, "price_high": 720000 },
"sold_vs_asking_pct": 2.08 }
GET/v1/properties/{property_id}/psp_with_confidence2 credits
Costs 2 credits. Potential Sell Price with the model's expected MAE, confidence label, and low/high estimate. The headline PSP is also on `/v1/properties/{property_id}`; this endpoint adds the confidence band.
Parameters
| Name | In | Required | Description |
|---|
| property_id | path | Yes | 8- or 12-char hex property id |
Request
curl "https://api.propradar.com.au/v1/properties/{property_id}/psp_with_confidence" \
-H "X-API-Key: pr_live_..."
Response
{ "property_id": "a1b2c3d4",
"potential_sell_price": 665000,
"confidence": "high",
"expected_mae_pct": 4.9,
"low_estimate": 630000,
"high_estimate": 700000 }
GET/v1/properties/{property_id}/similar3 credits
Costs 3 credits. Up to 10 active listings in the same suburb and property type, with bedrooms within +/-1 and asking price within +/-25% of the reference price anchor. Ordered by closest price distance.
Parameters
| Name | In | Required | Description |
|---|
| property_id | path | Yes | 8- or 12-char hex property id |
Request
curl "https://api.propradar.com.au/v1/properties/{property_id}/similar" \
-H "X-API-Key: pr_live_..."
Response
{ "reference_property_id": "a1b2c3d4",
"query": { "suburb": "PASCOE VALE", "property_type": "Apartment", "bedrooms": 2,
"price_anchor": 665000, "price_band_pct": 25, "beds_delta": 1 },
"similar": [{ "property_id": "b2c3d4e5", "address": "...", "asking_price_high": 675000 }] }
GET/v1/properties/{property_id}/nearby3 credits
Costs 3 credits. Active for-sale listings within a radius of a reference property, ordered by distance. Use this for buyer-agent workflows and competing-listing checks when sold comparables alone are not enough. Results carry no valuation; read `/v1/properties/{property_id}` for a property's estimate.
Parameters
| Name | In | Required | Description |
|---|
| property_id | path | Yes | 8- or 12-char hex property id |
| radius_m | query | No | Search radius in metres (default 1000, max 5000) |
| property_type | query | No | Exact property type filter, case-insensitive |
| beds | query | No | Bedroom filter |
| limit | query | No | Page size (default 10, max 10; larger values are reduced to 10) |
| cursor | query | No | Opaque cursor for continuation |
Request
curl "https://api.propradar.com.au/v1/properties/{property_id}/nearby?radius_m=1000&limit=5" \
-H "X-API-Key: pr_live_..."
Response
{ "property_id": "a1b2c3d4", "radius_m": 1000,
"results": [{ "property_id": "b2c3d4e5", "address": "...", "distance_m": 184.3,
"attributes": { "bedrooms": 2, "property_type": "Apartment" },
"listing": { "asking_price_high": 675000 } }],
"pagination": { "limit": 5, "has_more": false, "next_cursor": null } }
POST/v1/properties/bulk2 credits per address
POST an array of {address, postcode} items; returns each with full property data or found:false. Costs 2 credits per address resolved, the same as looking each up individually. Starter+ plans only. Per-request cap is plan-dependent: Starter = 2, Pro = 3, Growth = 5, Scale = 10, Enterprise = 25. Send an `Idempotency-Key` header to make retries safe; replays return the cached response and refund the credits.
Parameters
| Name | In | Required | Description |
|---|
| Idempotency-Key | header | No | Opaque, caller-chosen, <=200 printable-ASCII chars. Stored 24h. Replays return cached response with `Idempotent-Replayed: true`; same key with different body returns 422. |
Request
curl -X POST "https://api.propradar.com.au/v1/properties/bulk" \
-H "X-API-Key: pr_live_..." \
-H "Content-Type: application/json" \
-d '{
"addresses": [
{
"address": "1/8 Bristol Rd, Pascoe Vale",
"postcode": "3044"
}
]
}'
Response
{ "results": [{ "input": {...}, "property_id": "a1b2c3d4", "found": true, "attributes": {...} }], "summary": { "total": 1, "found": 1, ... } }
POST/v1/valuations2 credits (quick), 25 credits (enhance)
Value an address that isn't currently listed. Two modes, neither runs unless you ask for it by name - a request with neither flag returns 400. `"quick": true` returns an instant estimate from PropRadar's own model, synchronously, for 2 credits (no daily-cap spend). Send `bedrooms` and `bathrooms` with it: without them the range for a generic dwelling is usually too wide to publish and no estimate comes back (`missing_inputs` and `hint` in the response say what to add). Optional `land_size` and `floor_area` (square metres) tighten the range further. `"enhance": true` additionally queues the enhanced multi-source blend; returns a job `id`, poll `/v1/valuations/{id}` for the result (~30 min) - daily caps per plan: Hobby 2, Starter 4, Builder 4, Pro 6, Growth 12, Scale 30, Enterprise 100. The enhanced mode costs 25 credits, charged before the job is queued and refunded in full if the submission is rejected or an identical job was already in flight. Returns 409 if a property already exists at the address (resubmit with `"force": true` to value it anyway). Send an `Idempotency-Key` header to make retries safe.
Parameters
| Name | In | Required | Description |
|---|
| Idempotency-Key | header | No | Opaque, caller-chosen, <=200 printable-ASCII chars. Replays return the cached response with `Idempotent-Replayed: true` and refund the credits. |
Request
curl -X POST "https://api.propradar.com.au/v1/valuations" \
-H "X-API-Key: pr_live_..." \
-H "Content-Type: application/json" \
-d '{
"address": "286 Brays Road",
"postcode": "4503",
"state": "QLD",
"property_type": "House",
"quick": true
}'
Response
{ "status": "estimated", "source": "propradar_avm",
"estimated_value": 712000, "estimated_value_low": 665000, "estimated_value_high": 759000,
"estimated_rent_weekly": 480, "confidence": "Medium",
"enhancement": { "available": true, "how": "Resubmit the same body with \"enhance\": true.", ... } }
GET/v1/valuations/{id}Free every 30s
Free once every 30 seconds per job: the first poll in each 30-second window costs nothing, and repeats inside the window return the same cached response and cost 1 credit. The `X-Poll-After` header gives the seconds until the next free poll. Returns the status of a job from POST /v1/valuations. Scoped to your API key's owner (other keys' jobs return 404). While pending/processing it returns an ETA; when completed it returns the estimate, weekly rent, yield, and potential sell price inline (investment score on Pro+), plus a link to the full `/v1/properties/{id}` record.
Parameters
| Name | In | Required | Description |
|---|
| id | path | Yes | Job id from the POST /v1/valuations response. |
Request
curl "https://api.propradar.com.au/v1/valuations/{id}" \
-H "X-API-Key: pr_live_..."
Response
{ "id": 4815, "status": "completed",
"address": "286 Brays Road", "property_id": "c8868204",
"attributes": { "bedrooms": 5, "bathrooms": 3, "parking": 5,
"property_type": "House", "land_size_sqm": 1999 },
"valuation": { "estimated_value": 1510000, "estimated_value_low": 1330000,
"estimated_value_high": 1690000, "confidence": "high" },
"rental": { "estimated_weekly": 935, "estimated_yield_pct": 3.2, "confidence": "high" },
"potential_sell_price": 1510000,
"analytics": { "investment_score": 71.5, "investment_score_confidence": "high" },
"_links": { "property": "/v1/properties/c8868204" } }
POST/v1/valuations/estimate2 credits
Costs 2 credits. Give a suburb/postcode plus attributes (beds, baths, land size, property type) instead of a street address, and get back a value range and confidence. "What would a 4-bed house on ~600sqm in this postcode go for." Unlike `POST /v1/valuations`, which resolves a real address and takes ~30 minutes, this answers synchronously and doesn't touch anything outside our own data.
With no address, we can't pin the estimate to a parcel, only a locality, so `confidence` is always `"Low"` (see `coordinate_basis`). **Branch on `precision`, not `confidence`.** `precision` bands how tight this particular locality's range is, and it moves: on an 8,000-sale temporal holdout, median absolute error ran ~7% for `high`, ~11% `medium`, ~14% `low` and ~20%+ `very_low`. A uniform master-planned suburb usually bands `high`; an architecturally mixed inner suburb bands `low` or `very_low`. If there's nothing nearby to compare against, `estimated_value` and its range come back null with `reason: "insufficient_comparables"` instead of a guess.
Pro+ tier. If you're prototyping on a lower plan before launch, ask us and we can enable it on your key.
Request
curl -X POST "https://api.propradar.com.au/v1/valuations/estimate" \
-H "X-API-Key: pr_live_..." \
-H "Content-Type: application/json" \
-d '{
"suburb": "Pascoe Vale",
"postcode": 3044,
"state": "VIC",
"property_type": "House",
"bedrooms": 3,
"bathrooms": 1,
"land_size": 550
}'
Response
{ "query": { "suburb": "Pascoe Vale", "postcode": 3044, "state": "VIC",
"property_type": "House", "bedrooms": 3, "bathrooms": 1, "land_size": 550 },
"estimated_value": 1031701, "estimated_value_low": 832464, "estimated_value_high": 1259106,
"confidence": "Low", "precision": "medium", "comp_count": 40,
"coordinate_basis": "suburb_centroid", "reason": null }
GET/v1/suburbs/{state}/{suburb}/forecast2 credits
Costs 2 credits. PropRadar's own 12-month outlook for a suburb: the expected change in median house price, unit price, house rent and unit rent, each as a percentage with a direction (`up`, `down`, or `flat` within +/-1%). Any of the four can be null where a suburb does not have enough recent history to forecast, and the response omits nothing else. A suburb with none of the four returns 404 `no_forecast`. These are model estimates, not guarantees, refreshed as new market data arrives. **Read `state_percentile` alongside `change_pct`.** The model is strongest at ranking suburbs against each other, and an absolute level for a single suburb is less certain, so each figure carries `state_percentile` (0 = weakest outlook in the state, 100 = strongest). `change_pct` is capped at +/-30, with `capped: true` when the cap applied. Plans below Pro get 403 `plan_upgrade_required` and the credit is refunded.
Parameters
| Name | In | Required | Description |
|---|
| state | path | Yes | State code (VIC, NSW, ...). |
| suburb | path | Yes | Suburb name, case-insensitive. |
| postcode | query | No | Disambiguate suburbs that span more than one postcode. |
Request
curl "https://api.propradar.com.au/v1/suburbs/VIC/Pascoe%20Vale/forecast" \
-H "X-API-Key: pr_live_..."
Response
{ "suburb": "PASCOE VALE", "state": "VIC", "postcode": 3044, "horizon_months": 12,
"houses": { "price": { "change_pct": 4.2, "direction": "up", "state_percentile": 31 },
"rent": { "change_pct": 3.1, "direction": "up", "state_percentile": 67 } },
"units": { "price": { "change_pct": 0.4, "direction": "flat", "state_percentile": 48 },
"rent": { "change_pct": 2.6, "direction": "up", "state_percentile": 78 } },
"note": "Model estimates of the expected change over the next 12 months, not guarantees. ..." }
GET/v1/developments/nearby2 credits
Costs 2 credits. Development applications (DAs) lodged near a coordinate, closest first, with description, category, status, estimated cost, dwelling count, lodged and decision dates, and distance. Filter with `status=decided|pending` (default all). `radius_km` defaults to 2 (max 5) and `limit` to 20 (max 20). Only applications that carry coordinates can be placed and coverage varies by state, so an empty list means none we can place here, not that none exist. Plans below Pro get 403 `plan_upgrade_required` and the credit is refunded.
Parameters
| Name | In | Required | Description |
|---|
| lat | query | Yes | Latitude, Australia only (-44 to -9). |
| lng | query | Yes | Longitude, Australia only (112 to 154). |
| radius_km | query | No | Search radius in km. Default 2, max 5. |
| status | query | No | all (default), decided, or pending. |
| limit | query | No | Max rows. Default and max 20. |
Request
curl "https://api.propradar.com.au/v1/developments/nearby?lat=-33.8688&lng=151.2093&radius_km=1&limit=5" \
-H "X-API-Key: pr_live_..."
Response
{ "query": { "lat": -33.8688, "lng": 151.2093, "radius_km": 1, "status": "all", "limit": 5 },
"count": 1,
"developments": [{ "da_id": "DA-2026-0142", "council": "City of Sydney", "suburb": "SYDNEY",
"address": "1 Example St", "description": "Mixed-use development, 42 apartments",
"category": "Residential", "status": "Under assessment", "is_decided": false,
"dev_cost": 18500000, "n_dwellings": 42, "lodged_date": "2026-08-11",
"decision_date": null, "distance_km": 0.34, "geo": { "lat": -33.8671, "lng": 151.2110 } }],
"coverage_note": "Only applications with coordinates are returned. Coverage varies by state, so an empty list does not mean nothing has been lodged." }
GET/v1/suburbs/{state}/{suburb}/rentals3 credits
Costs 3 credits. Properties currently advertised for rent, newest first, one row per property. Each carries the weekly rent, bond, bedrooms, bathrooms, parking, property type, listed and available-from dates. Filter with `property_type`, `min_beds`, `max_beds`, `min_rent` and `max_rent` (weekly). Up to 10 rows per call; page with `cursor`. `geo` is Pro+. Free keys get 403 `plan_upgrade_required` and the credits are refunded.
Parameters
| Name | In | Required | Description |
|---|
| state | path | Yes | Two-letter state code |
| suburb | path | Yes | Suburb name, case-insensitive |
| property_type | query | No | Exact match, case-insensitive (House, Unit, Townhouse, ...) |
| min_beds | query | No | Minimum bedrooms |
| max_beds | query | No | Maximum bedrooms |
| min_rent | query | No | Minimum weekly rent |
| max_rent | query | No | Maximum weekly rent |
| limit | query | No | Page size (default and max 10) |
| cursor | query | No | Opaque cursor from the previous response's `pagination.next_cursor` |
Request
curl "https://api.propradar.com.au/v1/suburbs/NSW/Mosman/rentals?min_beds=2&max_rent=1200&limit=5" \
-H "X-API-Key: pr_live_..."
Response
{ "state": "NSW", "suburb": "MOSMAN",
"rentals": [{ "property_id": "a1b2c3d4", "address": "4/12 Example St, MOSMAN, NSW, 2088",
"weekly_rent": 950, "bond": 4100, "bedrooms": 2, "bathrooms": 1, "parking": 1,
"property_type": "Apartment", "listed_date": "2026-09-20",
"available_from": "2026-10-04", "added_at": "2026-09-21T02:10:00.000Z",
"geo": { "lat": -33.829, "lng": 151.244 } }],
"pagination": { "limit": 5, "next_cursor": "eyJk..." } }
POST/v1/rent/estimate2 credits
Costs 2 credits. Give a suburb/postcode plus attributes (beds, baths, land size, property type) and get back a weekly rent range and confidence. "What would a 3-bed house in this postcode rent for." Synchronous, and it answers from our own data only.
With no address the estimate anchors to a locality, so `confidence` is always `"Low"` (see `coordinate_basis`). **Branch on `precision`**, which bands how tight this locality's range is. If there is not enough nearby evidence, the rent figures come back null with a `reason` instead of a guess.
Pro+ tier. Plans below Pro get 403 `plan_upgrade_required` and the credits are refunded. Validation errors and temporary service failures are refunded too.
Request
curl -X POST "https://api.propradar.com.au/v1/rent/estimate" \
-H "X-API-Key: pr_live_..." \
-H "Content-Type: application/json" \
-d '{
"suburb": "Pascoe Vale",
"postcode": 3044,
"state": "VIC",
"property_type": "House",
"bedrooms": 3,
"bathrooms": 1,
"land_size": 550
}'
Response
{ "query": { "suburb": "Pascoe Vale", "postcode": 3044, "state": "VIC",
"property_type": "House", "bedrooms": 3, "bathrooms": 1, "land_size": 550 },
"estimated_weekly_rent": 640, "estimated_weekly_rent_low": 570, "estimated_weekly_rent_high": 715,
"confidence": "Low", "precision": "medium", "comp_count": 40,
"coordinate_basis": "suburb_centroid", "reason": null }
GET/v1/properties/recently-modified3 credits
Costs 3 credits per call, so poll at a sensible interval (every 15 minutes is 8,640 credits a month). Mirror-sync feed for listing mutations, newly matched sold records, and withdrawals since an ISO timestamp. Use it to keep your local copy fresh without re-walking every suburb. `since` must be within the last 30 days.
**`withdrawn` is how you learn a listing ended.** A property that stops being advertised simply disappears from the suburb endpoints, so polling those can never tell you it is gone: without this event a synced copy accumulates listings that are no longer live. It is REVERSIBLE: a relist produces a later `listing_update` for the same property_id, so apply events in `event_date` order and let the latest win rather than treating a withdrawal as final.
Withdrawn events carry the last known asking price from before the listing was pulled.
**Filter to your area.** Pass `state` and/or `postcode` (up to 50, comma-separated) so you only pay for the events you keep.
Parameters
| Name | In | Required | Description |
|---|
| since | query | Yes | ISO 8601 timestamp within the last 30 days |
| event_type | query | No | all (default), listing_update, sold, or withdrawn |
| state | query | No | Only events in this state (e.g. NSW) |
| postcode | query | No | Only events in these postcodes, comma-separated, up to 50 (e.g. 2500,2502) |
| limit | query | No | Page size (default 20, max 20; larger values are reduced to 20) |
| cursor | query | No | Opaque cursor from the previous response's `pagination.next_cursor` |
Request
curl "https://api.propradar.com.au/v1/properties/recently-modified?since=2026-05-29T00%3A00%3A00Z&event_type=all&limit=20" \
-H "X-API-Key: pr_live_..."
Response
{ "query": { "since": "2026-05-29T00:00:00.000Z", "event_type": "all", "limit": 20 },
"events": [{ "event_type": "listing_update", "event_date": "2026-05-31T02:12:00.000Z",
"property_id": "a1b2c3d4", "summary": { "suburb": "PASCOE VALE" } },
{ "event_type": "withdrawn", "event_date": "2026-05-30T22:40:00.000Z",
"property_id": "e5f6a7b8", "summary": { "suburb": "COBURG",
"asking_price_low": 780000, "asking_price_high": 830000 } }],
"pagination": { "limit": 20, "next_cursor": "eyJk..." } }
GET/v1/properties/{property_id}/investment_analysis3 credits
Costs 3 credits. Full investment-score component breakdown plus derived gross/net yield using the property's reference price. Same score the consumer site shows behind its Pro paywall: yield, capital growth, market dynamics, location, value-buy, risk. Hobby/Starter callers get 403 plan_upgrade_required; the credits are refunded.
Parameters
| Name | In | Required | Description |
|---|
| property_id | path | Yes | 8- or 12-char hex property id |
Request
curl "https://api.propradar.com.au/v1/properties/{property_id}/investment_analysis" \
-H "X-API-Key: pr_live_..."
Response
{
"property_id": "a1b2c3d4",
"investment_score": 68.2,
"confidence": "high",
"score_breakdown": {
"yield_score": 7.2, "capital_growth": 14.5,
"market_dynamics": 12.1, "value_buy": 16.2,
"location": 11.3, "risk_adjustment": -8.4,
"base_score": 57.7, "relative_percentile": 68.0,
"version": 9
},
"cashflow": {
"reference_price": 665000,
"annual_rental_income": 25688,
"gross_yield_pct": 3.9, "net_yield_pct": 2.8,
"assumptions": { "expense_ratio_pct": 25, "vacant_weeks_per_year": 2,
"reference_price_source": "potential_sell_price" }
}
}
GET/v1/suburbs/{state}/{suburb}/market_cycle
2-axis classification (heat x momentum x volatility) fusing 16 momentum signals (including the PropRadar 12-month growth outlook) with absolute-price gates that stop phase labels from contradicting headline growth. Pick a property type with `?property_type=houses|units` (default houses). Suburbs without enough sample to classify return 404 insufficient_data. Hobby/Starter callers get 403 plan_upgrade_required; the credits are refunded.
Parameters
| Name | In | Required | Description |
|---|
| state | path | Yes | Two-letter state code |
| suburb | path | Yes | Suburb name (URL-encoded) |
| property_type | query | No | houses (default) or units |
| postcode | query | No | 4-digit postcode. Use to disambiguate when a suburb name spans multiple postcodes. |
Request
curl "https://api.propradar.com.au/v1/suburbs/VIC/Pascoe%20Vale/market_cycle?property_type=houses" \
-H "X-API-Key: pr_live_..."
Response
{
"suburb": "PASCOE VALE", "state": "VIC", "postcode": 3044,
"property_type": "houses",
"market_cycle": {
"phase": "rising", "confidence": "high",
"dps": 62, "dps_level": "medium",
"momentum_score": 0.80, "momentum_direction": "positive",
"volatility_score": 0.32, "volatility_level": "moderate",
"signal_quality_score": 0.81, "signal_alignment": 0.74,
"estimated_months_to_next_phase": 15,
"override_applied": false,
"top_signals": [
{ "name": "Vacancy Pressure", "value": "1% vacancy rate",
"metrics": [{ "key": "vacancy_rate_pct", "label": "Vacancy rate", "value": 1.0, "unit": "percent" }],
"score": 2, "direction": "positive", "weight": 0.18, "contribution": 0.36, "available": true },
...
],
"signals": [ ... 15 entries ... ]
},
"as_of": "2026-05-12T03:00:00Z"
}
GET/v1/suburbs/{state}/{suburb}/price_history
Annual median sold price, median weekly rent (sparse, only years that carry it), and derived gross yield for one property type, oldest first. Each point is one calendar year. Hobby/Starter callers get 403 plan_upgrade_required; the credits are refunded.
Parameters
| Name | In | Required | Description |
|---|
| state | path | Yes | Two-letter state code |
| suburb | path | Yes | Suburb name (URL-encoded) |
| property_type | query | No | houses (default) or units |
| years | query | No | Trailing years (default 15, max 40). `months` is accepted as a legacy alias and treated as a count of years. |
| postcode | query | No | 4-digit postcode. Use to disambiguate when a suburb name spans multiple postcodes. |
Request
curl "https://api.propradar.com.au/v1/suburbs/VIC/Pascoe%20Vale/price_history?property_type=houses&years=15" \
-H "X-API-Key: pr_live_..."
Response
{ "suburb": "Pascoe Vale", "state": "VIC", "property_type": "houses",
"granularity": "annual", "points": 8,
"series": [{ "year": 2026, "median_price": 1200000,
"median_weekly_rent": 630, "gross_yield_pct": 2.7, "sample_count_band": "100-249" }],
"as_of": 2026 }
GET/v1/suburbs/{state}/{suburb}/heat_history
Daily snapshots of the suburb market-heat score and its supply/demand signals, oldest first. Shows whether a market is heating or cooling. The snapshot history began accumulating recently, so the series is shallow for now (days, not months) and deepens over time; use `points` to see how much exists. Hobby/Starter callers get 403 plan_upgrade_required; the credits are refunded.
Parameters
| Name | In | Required | Description |
|---|
| state | path | Yes | Two-letter state code |
| suburb | path | Yes | Suburb name (URL-encoded) |
| days | query | No | Trailing window of days (default 90, max 365) |
| postcode | query | No | 4-digit postcode to disambiguate. |
Request
curl "https://api.propradar.com.au/v1/suburbs/VIC/Pascoe%20Vale/heat_history?days=90" \
-H "X-API-Key: pr_live_..."
Response
{ "suburb": "Pascoe Vale", "state": "VIC", "granularity": "daily", "points": 6,
"series": [{ "date": "2026-05-31", "house_heat_score": 72.5, "unit_heat_score": 60.1,
"house_days_on_market": 30, "house_inventory_months": 2.0,
"vacancy_rate_pct": 1.0, "auction_clearance_pct": 70 }],
"as_of": "2026-05-31" }
GET/v1/regions
Lists the regions we publish macro indicators for: 8 capital cities, 8 states/territories, and a national row. Call /v1/regions/{slug} for detail. Below Growth, returns 403 plan_upgrade_required (credits refunded).
Request
curl "https://api.propradar.com.au/v1/regions" \
-H "X-API-Key: pr_live_..."
Response
{ "regions": [{ "region_type": "city", "slug": "sydney", "name": "Sydney", "state": "NSW", "suburb_count": 642 }, ...],
"count": 17 }
GET/v1/regions/{slug}
Aggregate market + economic indicators for one region: medians, yields, growth, market dynamics, distributions, quarterly price/rent/vacancy time-series, and economy (population, migration, unemployment, building approvals, mortgage + cash rates). Mortgage/cash-rate series are on the national row; city/state rows carry null for those. Below Growth, returns 403.
Parameters
| Name | In | Required | Description |
|---|
| slug | path | Yes | Region slug from /v1/regions (e.g. sydney, vic, national) |
Request
curl "https://api.propradar.com.au/v1/regions/sydney" \
-H "X-API-Key: pr_live_..."
Response
{ "region_type": "city", "slug": "sydney", "name": "Sydney", "state": "NSW",
"suburb_count": 642, "population": 5300000,
"medians": { "house_price": 1450000, "unit_price": 820000, ... },
"yields": { "house_gross_pct": 2.7, "unit_gross_pct": 3.9, ... },
"growth": { "house_1y_pct": 4.2, ... }, "market_dynamics": { ... },
"distributions": { "yield": {...}, "price": {...} },
"time_series": { "median_price": [...], "median_rent": [...], "vacancy_rate": [...] },
"economy": { "population_growth_1y_pct": 1.8, "unemployment_rate_pct": 3.9,
"building_approvals_12mo": 41200, "cash_rate_pct": null },
"as_of": "2026-05-31T03:00:00Z" }
GET/v1/addresses/suggest
Priced per session: pass the same `session` token (any 8-64 letters, digits, `-` or `_` you make up per address being typed) on each call and the first call costs 1 credit, then the next 15 within 3 minutes are free. The response's `session` block shows the call number and free calls left. Without a token every call costs 1 credit.
Type-ahead for an address box. Send what the user has typed so far and get back the properties whose address contains it, ranked with on-market properties first, then sold, then off-market. No postcode needed. Every word must appear in the address; a leading street number or unit (`12`, `5/10`) must start it, and street-type abbreviations match either form (`st` finds Street, `rd` finds Road).
Each suggestion carries the `property_id` to pass to `/v1/properties/{property_id}`. Identity only, so it is available on every plan. Needs at least 3 letters or digits; call it after a short pause in typing rather than on every keystroke.
Parameters
| Name | In | Required | Description |
|---|
| q | query | Yes | The address typed so far, e.g. `12 smith st rich`. 3 to 120 characters. |
| state | query | No | Limit to one state (VIC, NSW, QLD, WA, SA, TAS, ACT, NT). |
| postcode | query | No | Limit to one postcode. |
| limit | query | No | Suggestions to return (default 5, max 10). |
| session | query | No | Your own token for one address being typed (8-64 of A-Z a-z 0-9 - _). The first call in a session costs 1 credit; the next 15 within 3 minutes are free. |
Request
curl "https://api.propradar.com.au/v1/addresses/suggest?q=284%20o%27hea%20st&limit=5&session=addr-7f3c9a21" \
-H "X-API-Key: pr_live_..."
Response
{ "query": { "q": "284 o'hea st", "state": null, "postcode": null, "limit": 5 },
"session": { "call": 3, "free_calls_left": 13 },
"suggestions": [{ "property_id": "b4b38060",
"address": "284 O'Hea Street, Pascoe Vale South, VIC, 3044",
"suburb": "PASCOE VALE SOUTH", "state": "VIC", "postcode": 3044,
"status": "on_market" }] }
GET/v1/suburbs/resolve
Resolve what a user typed - a suburb, a misspelling, a wider place name, a postcode - into the suburbs we hold, each with the `path` to request it. Trailing state codes and postcodes in `q` act as filters (`richmond vic`, `Richmond VIC 3121`).
`match_type` says why each suburb matched: `exact`, `postcode`, `prefix` (`logan` finds Logan Central and Logan Reserve), `word` (`noarlunga` finds Port Noarlunga), `contains`, or `fuzzy` for a likely misspelling (`werribbee` finds Werribee). Only suburbs with data are returned. Use it before `/v1/suburbs/{state}/{suburb}` instead of guessing names and paying for 404s.
Parameters
| Name | In | Required | Description |
|---|
| q | query | Yes | Suburb name, place, or postcode, optionally followed by a state code and postcode. |
| state | query | No | Limit to one state. Overrides a state code in `q`. |
| limit | query | No | Suburbs to return (default 5, max 20). |
Request
curl "https://api.propradar.com.au/v1/suburbs/resolve?q=werribbee" \
-H "X-API-Key: pr_live_..."
Response
{ "query": { "q": "werribbee", "name": "WERRIBBEE", "state": null, "postcode": null, "limit": 5 },
"suburbs": [{ "suburb": "WERRIBEE", "state": "VIC", "postcode": 3030,
"match_type": "fuzzy", "score": 0.73,
"path": "/v1/suburbs/VIC/werribee" }] }