Mirror and sync Australian property data
The recently-modified feed lets you incrementally synchronise a local copy of Australian listings and sold records without scraping, polling individual property IDs, or doing full-table dumps.
How the feed works
Pass a since= timestamp
Any ISO timestamp up to 30 days ago. On the first run, use 7 days ago. On subsequent runs, use the timestamp returned by your last call.
Page through events
Each response includes up to 20 events and a next_cursor. Follow the cursor until it's null - that's the end of the change window.
Store the high-water mark
Save the most recent event_date from the page. Use it as since= on your next sync run. No full dumps, no missed updates.
Apply events in order
Three types come back: listing_update, sold, and withdrawn. Apply them in event_date order and let the latest win. withdrawn is reversible, so a relisted property arrives later as a listing_update.
Complete Python example
A runnable sync script. Run it daily via cron or a scheduled job to keep your local copy current. The endpoint requires the Starter plan or higher; if you are syncing for a business, commercial use starts at Pro.
import requests
from datetime import datetime, timezone, timedelta
API_KEY = "pr_live_..."
BASE = "https://api.propradar.com.au/v1"
def sync_changes(since_iso: str) -> str:
"""
Pull all property changes since `since_iso`, upsert them locally,
and return the new high-water mark to store for the next run.
"""
cursor = None
latest_ts = since_iso
while True:
params = {
"since": since_iso,
"event_type": "all", # "listing_update" | "sold" | "withdrawn" | "all"
"limit": 20,
}
if cursor:
params["cursor"] = cursor
r = requests.get(
f"{BASE}/properties/recently-modified",
params=params,
headers={"X-API-Key": API_KEY},
timeout=30,
)
r.raise_for_status()
data = r.json()
for event in data["events"]:
upsert_locally(event) # your DB logic here
if event["event_date"] > latest_ts:
latest_ts = event["event_date"]
cursor = data["pagination"].get("next_cursor")
if not cursor:
break # all pages consumed
return latest_ts # save this as the new since= for next run
def upsert_locally(event: dict) -> None:
pid = event["property_id"]
event_type = event["event_type"] # "listing_update" | "sold" | "withdrawn"
summary = event["summary"]
# Example: insert/update a local `properties` table
print(f"[{event_type}] {pid} - {summary.get('suburb')} {summary.get('postcode')}")
# db.execute("INSERT INTO properties ... ON CONFLICT (property_id) DO UPDATE ...")
if __name__ == "__main__":
# First run: look back 7 days
since = (datetime.now(timezone.utc) - timedelta(days=7)).isoformat()
# Subsequent runs: load from your state store, e.g. a file or DB row
# since = load_last_run_timestamp()
new_since = sync_changes(since)
print(f"Sync complete. Next run: since={new_since}")
# save_last_run_timestamp(new_since)
Event types
| event_type | Meaning | Key summary fields |
|---|---|---|
| listing_update | An active listing was added or its price changed | asking_price_low, asking_price_high, suburb, postcode |
| sold | A property sold and was recorded in the dataset | sold_price, sold_date, suburb, postcode |
Key endpoint reference
/v1/properties/recently-modifiedListing updates, sold events and withdrawals since an ISO timestamp. Cursor-paginated, max 30-day lookback.
/v1/properties/{property_id}Fetch full detail for any property_id returned in the feed.
/v1/healthVerify your key and check remaining quota before a long sync run.
The recently-modified feed requires the Starter plan or higher, and commercial use requires Pro or higher. The 30-day lookback window means you can catch up after a weekend without gaps.
Upgrade to Pro