Skip to main contentSkip to main content

Australian Property Data API documentation

Build with listings, sold prices, valuations, rental estimates, yields, and suburb statistics. Base URL: https://api.propradar.com.au/v1

Free key

50 credits/month, no credit card.

Fast test loop

Try endpoints live from this page.

Production shape

Stable IDs, rate-limit headers, OpenAPI.

Dataset coverage

Refreshed daily | suburb stats fortnightly
12,000+
Suburbs tracked
110k+
Active listings
430k+
Sold records
All 8
States + territories

Metros covered: Sydney, Melbourne, Brisbane, Perth, Adelaide, Hobart, Darwin, Canberra, plus Geelong, Ballarat, Bendigo, Gold Coast, Sunshine Coast, and the Central Coast. Suburb statistics include medians, growth windows, rental yields, days on market, vacancy, demographics, socioeconomic deciles, and market heat signals. Active listings and recent sold records carry full attributes, valuations, rental estimates, and (on Pro+) investment scores.

See every covered postcode and suburb

Try it in your browser

Paste your API key and call an endpoint live. Pick a preset below or type your own path.

Try it in your browser

Paste your API key, pick an endpoint, and see a real JSON response.

Stored in your browser for this playground. Don't have one? Get a free key

Add your API key above to send a live request.Create a free key
GET/api/v1/properties/search

Authentication

Every request must include your API key in the X-API-Key header. Keys start with pr_live_. Issue a key at /developers.

curl https://api.propradar.com.au/v1/health \
  -H "X-API-Key: pr_live_..."

Alternative: Authorization: Bearer pr_live_... works too.

Client libraries

Thin official clients that track this spec and handle the parts raw HTTP calls don't: key auth, automatic back-off and retry on a 429, cursor pagination, and typed access to the Pro+ endpoints. Drop a single file into your project, no build step needed.

Needs requests.

from propradar import PropRadar

pr = PropRadar(api_key="pr_live_...")
hist = pr.suburb_price_history("VIC", "richmond", years=15)
for p in hist["series"]:
    print(p["year"], p["median_price"])

TypeScript / JavaScript

Download propradar.ts

Zero dependencies; uses the global fetch (Node 18+, Deno, Bun, browsers).

import { PropRadar } from "./propradar";

const pr = new PropRadar({ apiKey: "pr_live_..." });
const hist = await pr.suburbPriceHistory("VIC", "richmond", { years: 15 });
hist.series.forEach((p) => console.log(p.year, p.median_price));

Prefer to generate your own? The full machine-readable spec is at /api/v1/openapi.json.

Endpoints

35 endpoints. Search by name, path, or keyword to jump straight to one.

35 of 35 endpoints
POST/v1/suburbs/batch1 credit per suburb, 3 with sold

Many suburbs in one call: statistics and recent sales

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
NameInRequiredDescription
suburbsqueryYesBody field: array of `{ state, suburb, postcode? }`. Duplicates are fetched and charged once.
includequeryNoBody field: `["stats"]` (default), `["sold"]` or both.
soldqueryNoBody 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

List every suburb in the dataset (coverage index)

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
NameInRequiredDescription
statequeryNo2-3 letter code (NSW, VIC, QLD, SA, WA, TAS, ACT, NT). Omit for all states.
hasqueryNoComma-separated availability flags; returns only suburbs where ALL are true. One or more of: listings, sold, heat_history, medians.
limitqueryNoRows per page, 1-1000 (default 200).
offsetqueryNoRow 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

Authenticated health probe

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 key
GET/v1/usageFree

Account usage and credit allowance

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
NameInRequiredDescription
daysqueryNoTrailing 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/{property_id}2 credits

Look up a single property

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
NameInRequiredDescription
property_idpathYes8- 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

Recent sold comparables (Hobby+; lat/lng mode Pro+)

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
NameInRequiredDescription
property_idqueryNoReference property already in the dataset. Mutually exclusive with lat/lng.
latqueryNoLatitude of your own reference point (Pro+). Requires lng. Australia only (-44 to -9); out of range returns 400, which also catches a swapped pair.
lngqueryNoLongitude of your own reference point (Pro+). Requires lat. Australia only (112 to 154).
radius_mqueryNoSearch radius in metres (default 1000, max 5000)
monthsqueryNoLook-back window in months (default 12, max 24)
property_typequeryNoExact 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.
bedsqueryNoMatch exact bedroom count
limitqueryNoPage size (default 20, max 20). Use `cursor` for deeper walks.
cursorqueryNoOpaque cursor from the previous response's `pagination.next_cursor`. Preferred for deep pagination.
offsetqueryNoLegacy 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}

Suburb statistics

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
NameInRequiredDescription
statepathYesTwo-letter state code (VIC, NSW, QLD, ...)
suburbpathYesSuburb name (URL-encoded)
postcodequeryNo4-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

Trust-gated suburb rankings (Pro+ tier)

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
NameInRequiredDescription
statequeryYesState code, e.g. VIC or NSW
metricqueryNoyield_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_typequeryNohouses or units; shorthand when metric is omitted
min_median_pricequeryNoOptional trusted median price floor
max_median_pricequeryNoOptional trusted median price ceiling
topqueryNoRows 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

Multi-criteria suburb screener (Pro+ tier)

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
NameInRequiredDescription
statequeryNoState code, e.g. NSW. Omit to screen nationally.
postcodequeryNoComma-separated 4-digit postcodes to restrict to, e.g. 2480,2650. Omit for no postcode restriction.
location_typequeryNometro 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_typequeryNohouses (default) or units; selects which median, yield, growth, rent, inventory, heat and days-on-market columns are screened
min_confidencequeryNohigh, 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_yieldqueryNoMinimum gross rental yield percent. Values above 12 are treated as data artifacts and never returned.
max_yieldqueryNoMaximum gross rental yield percent
min_growth_1yrqueryNoMinimum 1-year median price growth percent
min_growth_3yrqueryNoMinimum 3-year median price growth percent
min_growth_5yrqueryNoMinimum 5-year median price growth percent
min_population_growth_1yrqueryNoMinimum 1-year population growth percent
min_population_growth_5yrqueryNoMinimum 5-year population growth percent
min_median_pricequeryNoMinimum median price for the chosen property type (AUD)
max_median_pricequeryNoMaximum median price for the chosen property type (AUD)
max_vacancy_ratequeryNoMaximum vacancy rate percent
max_days_on_marketqueryNoMaximum median days on market
max_distance_to_cbd_kmqueryNoMaximum straight-line distance to the capital city CBD, in km
min_rent_growth_1yrqueryNoMinimum 1-year median rent change percent, the yield trajectory behind the current yield
max_inventory_monthsqueryNoMaximum 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_scorequeryNoMinimum 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_ratequeryNoMaximum unemployment rate percent
min_socioeconomic_scorequeryNoMinimum socioeconomic (SEIFA-derived) score, 0-100
min_owner_occupied_pctqueryNoMinimum share of dwellings that are owner-occupied
min_populationqueryNoMinimum suburb population; useful for excluding micro-localities
min_sales_countqueryNoMinimum trailing 12-month sales for the chosen property type (default 5)
sortqueryNoyield, 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)
limitqueryNoPage size (default 20, max 20; larger values are reduced to 20)
offsetqueryNoPagination 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

Recent sold properties in a suburb

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
NameInRequiredDescription
statepathYesTwo-letter state code
suburbpathYesSuburb name (URL-encoded)
monthsqueryNoLook-back window (default 12, max 60)
property_typequeryNoExact match, case-insensitive (House, Apartment, Townhouse, Unit, Villa)
min_bedsqueryNoMinimum bedrooms
max_bedsqueryNoMaximum bedrooms
min_pricequeryNoMinimum sold price (AUD). Hobby and above; 403 below it.
max_pricequeryNoMaximum sold price (AUD). Hobby and above; 403 below it.
limitqueryNoPage size (default 20, max 20). Use `cursor` for deeper walks.
cursorqueryNoOpaque cursor from a previous response's `pagination.next_cursor`. Recommended over `offset` for stable pagination.
offsetqueryNoLegacy 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

Current active listings in a suburb

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
NameInRequiredDescription
statepathYesTwo-letter state code
suburbpathYesSuburb name (URL-encoded)
property_typequeryNoExact match, case-insensitive (House, Apartment, Townhouse, Unit, Villa)
min_bedsqueryNoMinimum bedrooms
max_bedsqueryNoMaximum bedrooms
min_pricequeryNoMinimum asking price (AUD). Hobby and above; 403 below it.
max_pricequeryNoMaximum asking price (AUD). Hobby and above; 403 below it.
min_land_sizequeryNoMinimum 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_sizequeryNoMaximum 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_sincequeryNoISO 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.
limitqueryNoPage size (default 10, max 10). Use `cursor` for deeper walks.
cursorqueryNoOpaque cursor from a previous response's `pagination.next_cursor`. Recommended over `offset` for stable pagination.
offsetqueryNoLegacy 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

Search current listings across a whole state (Pro+ tier)

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
NameInRequiredDescription
statequeryYesTwo-letter state code (VIC, NSW, QLD, WA, SA, TAS, ACT, NT)
suburbqueryNoComma-separated suburb names, up to 25. Omit to search the whole state.
postcodequeryNoComma-separated 3-4 digit postcodes, up to 25.
property_typequeryNoExact match, case-insensitive (House, Apartment, Townhouse, Unit, Villa)
min_bedsqueryNoMinimum bedrooms
max_bedsqueryNoMaximum bedrooms
min_pricequeryNoMinimum asking price (AUD)
max_pricequeryNoMaximum asking price (AUD)
min_land_sizequeryNoMinimum land size in square metres
max_land_sizequeryNoMaximum land size in square metres
min_days_on_marketqueryNoMinimum days since added_at. Capped at 365.
max_days_on_marketqueryNoMaximum days since added_at. Use e.g. 7 or 14 for recently-listed only. Capped at 365.
modified_sincequeryNoISO 8601 timestamp. Only return listings whose record changed at or after it.
limitqueryNoPage size (default 10, max 10). Use `cursor` for deeper walks.
cursorqueryNoOpaque cursor from a previous response's `pagination.next_cursor`.
offsetqueryNoLegacy offset pagination (max 1000). Use `cursor` for a state-wide sweep.
signalqueryNoprice_drop (latest asking-price change was a cut) or relisted (withdrawn and advertised again).
signal_daysqueryNoWindow 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

Property event history

Chronological timeline of listing, price-change, and sold events for a property.

Parameters
NameInRequiredDescription
property_idpathYes8- 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

Most recent sold record (Hobby+ tier)

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
NameInRequiredDescription
property_idpathYes8- 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

Potential sell price confidence band

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
NameInRequiredDescription
property_idpathYes8- 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

Similar active listings

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
NameInRequiredDescription
property_idpathYes8- 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

Nearby active listings

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
NameInRequiredDescription
property_idpathYes8- or 12-char hex property id
radius_mqueryNoSearch radius in metres (default 1000, max 5000)
property_typequeryNoExact property type filter, case-insensitive
bedsqueryNoBedroom filter
limitqueryNoPage size (default 10, max 10; larger values are reduced to 10)
cursorqueryNoOpaque 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

Batch property lookup (Starter+ tier)

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
NameInRequiredDescription
Idempotency-KeyheaderNoOpaque, 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 off-market address: instant estimate or enhanced valuation (Hobby+ tier)

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
NameInRequiredDescription
Idempotency-KeyheaderNoOpaque, 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

Poll an off-market valuation job

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
NameInRequiredDescription
idpathYesJob 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

Valuation range for a locality, no address needed (Pro+ tier)

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

12-month outlook for house/unit price and rent (Pro+ tier)

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
NameInRequiredDescription
statepathYesState code (VIC, NSW, ...).
suburbpathYesSuburb name, case-insensitive.
postcodequeryNoDisambiguate 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

Development applications near a point (Pro+ tier)

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
NameInRequiredDescription
latqueryYesLatitude, Australia only (-44 to -9).
lngqueryYesLongitude, Australia only (112 to 154).
radius_kmqueryNoSearch radius in km. Default 2, max 5.
statusqueryNoall (default), decided, or pending.
limitqueryNoMax 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

Properties currently advertised for rent in a suburb (Hobby+ tier)

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
NameInRequiredDescription
statepathYesTwo-letter state code
suburbpathYesSuburb name, case-insensitive
property_typequeryNoExact match, case-insensitive (House, Unit, Townhouse, ...)
min_bedsqueryNoMinimum bedrooms
max_bedsqueryNoMaximum bedrooms
min_rentqueryNoMinimum weekly rent
max_rentqueryNoMaximum weekly rent
limitqueryNoPage size (default and max 10)
cursorqueryNoOpaque 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

Weekly rent estimate for a locality, no address needed (Pro+ tier)

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

Recently-modified event feed (Starter+ tier)

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
NameInRequiredDescription
sincequeryYesISO 8601 timestamp within the last 30 days
event_typequeryNoall (default), listing_update, sold, or withdrawn
statequeryNoOnly events in this state (e.g. NSW)
postcodequeryNoOnly events in these postcodes, comma-separated, up to 50 (e.g. 2500,2502)
limitqueryNoPage size (default 20, max 20; larger values are reduced to 20)
cursorqueryNoOpaque 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

Investment score + cashflow (Pro+ tier)

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
NameInRequiredDescription
property_idpathYes8- 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

Market cycle classification (Pro+ tier)

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
NameInRequiredDescription
statepathYesTwo-letter state code
suburbpathYesSuburb name (URL-encoded)
property_typequeryNohouses (default) or units
postcodequeryNo4-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 price/rent/yield time-series (Pro+ tier)

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
NameInRequiredDescription
statepathYesTwo-letter state code
suburbpathYesSuburb name (URL-encoded)
property_typequeryNohouses (default) or units
yearsqueryNoTrailing years (default 15, max 40). `months` is accepted as a legacy alias and treated as a count of years.
postcodequeryNo4-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

Market-heat time-series (Pro+ tier)

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
NameInRequiredDescription
statepathYesTwo-letter state code
suburbpathYesSuburb name (URL-encoded)
daysqueryNoTrailing window of days (default 90, max 365)
postcodequeryNo4-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

List macro regions (Growth+ tier)

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}

Region macro indicators (Growth+ tier)

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
NameInRequiredDescription
slugpathYesRegion 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

Suggest addresses as the user types

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
NameInRequiredDescription
qqueryYesThe address typed so far, e.g. `12 smith st rich`. 3 to 120 characters.
statequeryNoLimit to one state (VIC, NSW, QLD, WA, SA, TAS, ACT, NT).
postcodequeryNoLimit to one postcode.
limitqueryNoSuggestions to return (default 5, max 10).
sessionqueryNoYour 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

Turn free text into suburbs

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
NameInRequiredDescription
qqueryYesSuburb name, place, or postcode, optionally followed by a state code and postcode.
statequeryNoLimit to one state. Overrides a state code in `q`.
limitqueryNoSuburbs 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" }] }

Add-ons

Add-ons sit alongside your plan rather than inside it. They are billed on the same subscription and renew on the same cycle, prorated for the remainder of your current billing period when you add one. No plan includes them.

Agency attribution

Adds an attribution block to property and sold responses naming the agency that marketed the listing, together with agency_ref, a stable one-way reference for that agency.

Match on agency_ref, never on the name. Agencies rebrand and trade under several names, and a name match silently miscredits them when they do. The reference is stable across rebrands and across properties, so it is safe to group and count on for market-share work. It is one-way, and it is not anyone’s internal identifier.

Without the add-on the block is absent, not null and not empty, so responses are byte-identical to what you receive today and nothing in your integration changes.

$99/month, on top of any paid plan. Add it yourself from your dashboard: the first charge is prorated for the days left in your current billing period, and after that it renews on the same date as your plan. It applies to every key on the subscription.

Credits and rate limits

Two limits apply in parallel: a monthly allowance of credits, and a per-second token bucket that caps burst traffic so one client can't take out the API for the rest. They measure different things. Credits price how much work a request asks for; the burst bucket limits how fast requests arrive. One expensive request takes one burst token, the same as a cheap one.

What a request costs

Search, suburb statistics and history reads cost 1 credit. Reads that return a full property record or a page of listings cost more, because they do materially more work. Every response carries an X-Credits-Cost header stating exactly what that request cost, so you never have to infer it. A request rejected as invalid (400, 413, 422) costs nothing, any other client error such as a 404 costs at most 1 credit whatever the endpoint, and a server error (5xx) costs nothing.

OperationCostNotes
Most endpoints1 creditSuburb statistics and series, address search, property history and sold summaries.
POST /v1/properties/bulk2 credits per addressThe same price as looking each address up individually, in one round trip.
GET /v1/comparables2 creditsComparable-sales selection across the sold corpus.
GET /v1/properties/{id}2 creditsThe full property record: attributes, listing, valuation, rent estimate and last sale.
GET /v1/properties/recently-modified3 creditsThe sync feed. Poll it at a sensible interval; each call is charged.
GET /v1/suburbs/{state}/{suburb}/rentals3 creditsProperties currently advertised for rent. One call returns a page.
Live listings: suburb and state-wide listings, nearby, similar3 creditsGET /v1/suburbs/{state}/{suburb}/listings, GET /v1/properties/listings, GET /v1/properties/{id}/nearby and /similar. One call returns a page of listings.
Sold: GET /v1/suburbs/{state}/{suburb}/sold2 creditsOne call returns a page of recent sales.
GET /v1/suburbs/{state}/{suburb}/forecast2 credits12-month outlook for house and unit price and rent. Pro and above.
GET /v1/developments/nearby2 creditsDevelopment applications near a coordinate. Pro and above.
POST /v1/valuations (quick: true)2 creditsInstant value and weekly rent for an address, from PropRadar's own model.
POST /v1/valuations/estimate2 creditsValue range for a locality and property attributes. Pro and above.
GET /v1/properties/{id}/investment_analysis3 creditsInvestment score breakdown and cashflow model. Pro and above.
GET /v1/properties/{id}/psp_with_confidence2 creditsThe likely sell price with its confidence.
GET /v1/valuations/{id}, GET /v1/health and GET /v1/usageFreeHealth checks and usage reports cost nothing. Polling a valuation job is free once every 30 seconds per job; faster repeats return the cached status and cost 1 credit (see X-Poll-After).
POST /v1/suburbs/batch1 credit per suburb, 3 with soldThe same as calling each suburb's statistics (and sold page) singly, in one call and one rate-limit token.
POST /v1/rent/estimate2 creditsWeekly rent range for a locality and property attributes. Pro and above.
GET /v1/suburbs/rankings2 creditsRanked multi-suburb query.
GET /v1/suburbs/screen5 creditsMulti-criteria screen across every covered suburb.
POST /v1/valuations (enhance: true)25 creditsQueues the enhanced valuation. Also counts against your plan's daily enhancement limit. The instant estimate (quick: true) costs 2.

Credits are included with your plan. They are a unit of metered usage, not money: they have no monetary value, are not redeemable for cash, are not transferable, and do not roll over between months.

Per plan

PlanMonthly creditsSustained (rps)BurstBulk batch
Free50520-
Hobby5,000530-
Starter16,00010302
API Builder16,00010302
Pro80,000401003
Growth200,000752005
Scale500,00015040010
EnterpriseNegotiated5001,50025

Sustained is the token-bucket refill rate; burst is the bucket capacity. Bulk batch is the per-request item cap on /v1/properties/bulk (Starter+ only). Recently-modified requires Starter+; investment-analysis, market-cycle, and price-history require Pro+.

Response headers

  • - X-Credits-Cost: credits this request cost
  • - X-RateLimit-Limit: your plan's monthly credit allowance
  • - X-RateLimit-Remaining: credits remaining this period
  • - X-RateLimit-Reset: unix seconds when the monthly counter resets
  • - X-RateLimit-Burst: burst-bucket capacity
  • - X-RateLimit-Burst-Remaining: tokens left in the per-second bucket
  • - X-API-Plan: the plan tier the key is on
  • - Retry-After: seconds to back off (only sent on 429 responses)

Exhaust the monthly credit allowance and the API returns 429 quota_exceeded until the allowance resets: on your billing date for a paid plan, or on the 1st of the month (UTC) on the free tier. The X-RateLimit-Reset header gives the exact moment. On a paid plan you can buy a credit pack from the developer dashboard for a one-off top-up: its credits are used only once your monthly allowance runs out, and expire 3 months after purchase. Every response carries an X-Credits-Topup-Remaining header with your unused pack credits. The account owner is emailed once when a billing period's allowance passes 80%, and again when it runs out. To avoid running out at all, the owner can turn on auto top-up on the dashboard, which buys a chosen pack whenever remaining credits fall below 10% of the allowance, up to a limit they set. A request whose cost exceeds what you have left is refused whole rather than partly served, and the message names the cost. Exceed the per-second burst and you get 429 rate_limited with a Retry-After header. Wait the indicated seconds and retry. The bucket refills continuously at your plan's steady rate.

Pacing a batch of requests

Most throttling comes from sending a list of requests all at once. The list can be small and still drain the bucket, because what matters is how many are in flight at the same instant, not how long the list is. Cap the concurrency and honour Retry-After:

import time, requests
from concurrent.futures import ThreadPoolExecutor

BASE = "https://api.propradar.com.au/v1"
session = requests.Session()
session.headers["X-API-Key"] = "pr_live_..."
session.headers["User-Agent"] = "your-app/1.0 (you@example.com)"

def get(path, attempts=6):
    for attempt in range(attempts):
        r = session.get(BASE + path, timeout=30)
        if r.status_code != 429:
            r.raise_for_status()
            return r.json()
        # The server tells you how long to wait. Do not guess, and do not
        # retry immediately: an instant retry just spends another token.
        time.sleep(int(r.headers.get("Retry-After", "1")))
    raise RuntimeError("still throttled after %d attempts: %s" % (attempts, path))

# THIS LINE IS THE FIX. max_workers caps how many requests are in flight,
# which is the number the burst bucket actually reacts to. Start at 4 and
# raise it only while your throttled count stays at zero.
with ThreadPoolExecutor(max_workers=4) as pool:
    results = list(pool.map(get, paths))

Resolving addresses is the one case where you should not be issuing a list of requests at all. POST /v1/properties/bulk takes a batch in one request, costing one burst token instead of one per address, at the same credit cost per address as looking each one up on its own. The batch size is the Bulk batch column in the plan table above, from 2 on Starter to 25 on Enterprise, so this reduces your request rate by that factor rather than eliminating pacing entirely:

# One request and one burst token per batch, not per address.
# Each entry is an object: address plus postcode.
r = session.post(BASE + "/properties/bulk", json={
    "addresses": [
        {"address": "1/8 Bristol Road, Pascoe Vale", "postcode": "3044"},
        {"address": "12 Smith Street, Fitzroy",      "postcode": "3065"},
    ],
})
r.raise_for_status()
for match in r.json()["results"]:
    # Always branch on found before using property_id: a miss returns
    # null rather than a synthetic id that would 404 later.
    if match["found"]:
        print(match["input"], match["property_id"])
    else:
        print(match["input"], "no match")

The official Python and TypeScript clients already retry on Retry-After for you. They do not cap concurrency, because they cannot see how you are calling them, so the limit above is still yours to set.

Fair use

The numbers above are sustained throughput, not unbounded headroom. Per-call result sizes and pagination depth are also capped to protect shared infrastructure. Stay inside these and you'll never see an unexpected throttle.

Per-call caps

EndpointMax rows / callMax offsetNotes
/v1/comparables20100radius_m <= 5000, months <= 24. Deeper walks must use cursor.
/v1/suburbs/…/sold201,000months <= 60. Use cursor to walk a full suburb.
/v1/suburbs/…/listings101,000Walks current on-market only. Use cursor for deeper pages.
/v1/properties/listings101,000Pro+. Same as suburb listings, across a whole state. Use cursor for a state-wide sweep.
/v1/properties/search10 matches-Prefix match within a single postcode.
/v1/properties/{id}/history20 sold events-Listing-history events are bounded by source data.
/v1/properties/{id}/similar10 listings-Same suburb/type, +/-1 bed, +/-25% price band.
/v1/properties/{id}/nearby10 listings-Active listings within radius_m <= 5000. Use cursor for continuation.
/v1/properties/bulk2-25 per req-Plan-dependent: Starter/API Builder 2, Pro 3, Growth 5, Scale 10, Enterprise 25. 64KB body cap.
/v1/properties/recently-modified20 events-Starter+. since must be within 30 days; cursor for continuation. Emits listing_update, sold and withdrawn.
/v1/suburbs/…/price_history40 years-Pro+. Annual price/rent/yield series for houses or units.
/v1/suburbs/rankings50 suburbs-Pro+. Trust-gated top suburbs by yield, growth, heat, price, or days on market.
/v1/suburbs/screen20 suburbs1,000Pro+. Every suburb clearing your thresholds; page with offset.
/v1/suburbs/…/heat_history365 days-Pro+. Daily market-heat snapshots; depth increases as snapshots accrue.
/v1/regions17 regions-Growth+. Capital-city, state/territory, and national macro region list.
/v1/regions/{slug}1 region-Growth+. Macro market, economic, distribution, and quarterly history indicators.

A limit above an endpoint's cap is reduced to the cap, not rejected. The value actually applied comes back in pagination.limit, so read the page size from there and follow next_cursor rather than assuming the size you asked for.

An empty page from /v1/suburbs/…/sold carries has_sold_records, and from /v1/suburbs/…/listings carries has_listing_records. false means the suburb is outside our coverage, not that nothing sold or nothing is listed. Check coverage before a walk with GET /v1/suburbs?has=sold (or has=listings) or on the coverage page.

Patterns we throttle or block

  • - Walking pagination cursors with no observable downstream use (dataset enumeration).
  • - Calling the same endpoint with sequentially-varying inputs at high rates (scraping).
  • - Opening multiple keys or accounts to get around a plan's limits.
  • - Repeatedly hitting offset caps without switching to cursor.

See Terms Section 8A for the full fair-use, caching, AI, and resale terms. In short: cache responses and refresh data that can change (listing status, prices, valuations) within 48 hours; keep a local copy of what you use, and delete it within 30 days of your plan ending; credit visible data as "Property data provided by PropRadar"; don't train models on outputs, though passing retrieved data to a model to answer your own user's question is fine; don't resell raw data or build a public property-search site on it; and use cursor for deep walks.

Error codes

All errors return JSON in the shape {"error":{"code":"...","message":"..."}}.

StatusCodeMeaning
400invalid_property_idproperty_id is not an 8- or 12-char hex string
401missing_api_keyNo X-API-Key header or Authorization: Bearer
401invalid_api_key_formatKey does not start with pr_live_
401invalid_api_keyKey not recognised
401revoked_api_keyKey was revoked
402no_active_subscriptionSign up at /developers/dashboard
402subscription_expiredFree tier expired or paid plan lapsed
403plan_upgrade_requiredEndpoint (or a parameter) requires a higher plan. Hobby+: comparables, POST /valuations, and a property's history, nearby, similar and sold_summary, suburb rentals, and the min_price/max_price filters on suburb listings and sold. Starter+: bulk and recently-modified. Pro+: comparables by lat/lng, investment_analysis, properties/listings, suburb rankings, screen, market_cycle, price_history, heat_history and forecast, developments/nearby, valuations/estimate and rent/estimate. Growth+: region endpoints. Endpoint access is separate from the commercial-use licence, which starts at Pro. Credits are refunded so polling is free.
403account_suspendedThe account, or the team member who created the key, is suspended. Contact support.
400invalid_*A parameter or body field is invalid (the code names it, e.g. invalid_state, invalid_cursor, invalid_days). Not charged.
404not_foundProperty or suburb not in the dataset. For a suburb name, the error carries spelling `suggestions` when there are close matches
404no_estimate/rent/estimate: no estimate for that locality and property type (unknown suburb, or not a dwelling)
404insufficient_data/suburbs/.../market_cycle: not enough transaction sample for that property_type in that suburb. Try the other type or a higher-volume nearby suburb.
429quota_exceededMonthly credit allowance exhausted, or this operation costs more credits than you have left; upgrade for more
422idempotency_key_mismatchReused an Idempotency-Key with a different request body
429rate_limitedPer-second burst exceeded; back off Retry-After seconds and retry
500internal_errorSomething went wrong on our side. Not charged. Retry with backoff
503service_unavailableTemporarily unavailable (the API, or instant estimates). Not charged. Retry after the Retry-After interval
Shape the roadmap

Missing a field or endpoint?

Tell us what is missing for your workflow. We prioritise the API roadmap based on developer requests, and many small additions ship within weeks.

Request a feature

Ready to build?

Get a free key. 50 credits a month, no credit card.

See plans & get a key