Real estate applications are some of the most data-intensive products on the web. Zillow, Redfin, and Realtor.com process millions of property listings, photos, and pricing data every day.
But you don't need to build a data pipeline from scratch. Real estate APIs let you access property listings, agent profiles, market statistics, and photos through simple HTTP requests.
In this tutorial, you'll learn how to build a property search feature from scratch using Python. We'll cover connecting to a real estate API, searching listings by location, fetching property details, and displaying results — all with clean, production-ready code.
What You'll Build
By the end of this tutorial, you'll have a working property search that can:
- Search listings by city, state, or ZIP code
- Filter results by price range, bedrooms, and property type
- Fetch detailed property information including photos
- Display results in a clean, structured format
- Handle API errors and rate limits gracefully
How Real Estate APIs Work
Before writing code, it's important to understand how real estate APIs are structured. Most follow a similar pattern:
You send an HTTP request with your API key, search parameters (city, price range, etc.), and the API returns a JSON response with matching listings.
Prerequisites
Before starting, make sure you have:
- Python 3.8 or later installed
- The
requestslibrary (pip install requests) - An API key from your chosen real estate data provider
- A basic understanding of HTTP requests and JSON
Step 1: Set Up Your Project
Create a new project directory and install the dependencies:
mkdir property-search && cd property-search
python3 -m venv venv
source venv/bin/activate
pip install requests python-dotenv
Store your API key in a .env file (never hardcode API keys in your source code):
REAL_ESTATE_API_KEY=your_api_key_here
REAL_ESTATE_API_URL=https://api.example.com/v1
Step 2: Create the API Client
Build a reusable API client class that handles authentication, requests, and error handling:
import os
import requests
from dotenv import load_dotenv
load_dotenv()
class PropertyAPI:
def __init__(self):
self.api_key = os.environ["REAL_ESTATE_API_KEY"]
self.base_url = os.environ["REAL_ESTATE_API_URL"]
self.session = requests.Session()
self.session.headers.update({
"X-API-Key": self.api_key,
"Accept": "application/json"
})
def _get(self, endpoint, params=None):
"""Make a GET request with error handling."""
try:
response = self.session.get(
f"{self.base_url}{endpoint}",
params=params,
timeout=15
)
response.raise_for_status()
return response.json()
except requests.exceptions.HTTPError as e:
status = e.response.status_code
if status == 429:
print("Rate limit exceeded. Wait before retrying.")
elif status == 401:
print("Invalid API key. Check your credentials.")
elif status == 404:
print("Resource not found.")
else:
print(f"HTTP error {status}: {e}")
return None
except requests.exceptions.Timeout:
print("Request timed out. Try again.")
return None
except requests.exceptions.ConnectionError:
print("Connection failed. Check your internet.")
return None
Step 3: Implement the Search Function
Add a search method to the client that accepts filters:
def search_properties(self, city, state, **filters):
"""
Search for property listings by location.
Args:
city: City name (e.g., "Austin")
state: State code (e.g., "TX")
**filters: Optional filters:
- price_min (int): Minimum price
- price_max (int): Maximum price
- beds_min (int): Minimum bedrooms
- property_type (str): "single_family", "condo", etc.
- limit (int): Number of results (default 20)
Returns:
List of property dictionaries or empty list
"""
params = {
"city": city,
"state_code": state,
"status": "for_sale",
"limit": filters.get("limit", 20)
}
# Add optional filters
for key in ["price_min", "price_max", "beds_min", "property_type"]:
if key in filters and filters[key] is not None:
params[key] = filters[key]
data = self._get("/search", params=params)
if data and "data" in data:
return data["data"].get("results", [])
return []
def get_property(self, property_id):
"""Fetch full details for a specific property."""
data = self._get(f"/property/{property_id}")
if data and "data" in data:
return data["data"]
return None
Step 4: Format and Display Results
Create a display function that formats the raw API data into a readable format:
def display_listings(listings):
"""Display a list of property listings in a readable format."""
if not listings:
print("No properties found matching your criteria.")
return
print(f"\\nFound {len(listings)} properties:\\n")
print(f"{'Price':<15} {'Beds':<6} {'Baths':<7} {'Sqft':<8} {'Address'}")
print("-" * 70)
for prop in listings:
price = f"${prop.get('list_price', 0):,.0f}"
beds = prop.get("beds", "N/A")
baths = prop.get("baths", "N/A")
sqft = f"{prop.get('sqft', 0):,}" if prop.get("sqft") else "N/A"
address = prop.get("address", {}).get("full", "Unknown")
print(f"{price:<15} {beds:<6} {baths:<7} {sqft:<8} {address}")
def display_property_detail(prop):
"""Display full details for a single property."""
if not prop:
print("Property not found.")
return
print(f"\\n{'=' * 60}")
print(f" {prop.get('address', {}).get('full', 'Unknown')}")
print(f"{'=' * 60}")
print(f" Price: ${prop.get('list_price', 0):,.0f}")
print(f" Beds: {prop.get('beds', 'N/A')}")
print(f" Baths: {prop.get('baths', 'N/A')}")
print(f" Sqft: {prop.get('sqft', 'N/A'):,}")
print(f" Year Built: {prop.get('year_built', 'N/A')}")
print(f" Status: {prop.get('status', 'N/A')}")
if prop.get("description"):
print(f"\\n {prop['description'][:300]}...")
photos = prop.get("photos", [])
if photos:
print(f"\\n Photos: {len(photos)} available")
Step 5: Put It All Together
from api_client import PropertyAPI
from display import display_listings, display_property_detail
def main():
api = PropertyAPI()
# Search for homes in Austin, TX under $500K
print("Searching for homes in Austin, TX...")
listings = api.search_properties(
city="Austin",
state="TX",
price_max=500000,
beds_min=3,
limit=10
)
display_listings(listings)
# Get details for the first result
if listings:
first_id = listings[0].get("property_id")
if first_id:
print("\\nFetching details for first property...")
details = api.get_property(first_id)
display_property_detail(details)
if __name__ == "__main__":
main()
Common API Endpoints
Most real estate APIs follow a similar structure. Here are the standard endpoints you'll work with:
| Endpoint | Method | Description |
|---|---|---|
/search | GET | Search listings by location and filters |
/property/{id} | GET | Get full property details |
/agents | GET | Search real estate agents |
/agent/{id} | GET | Get agent profile and listings |
/market-stats | GET | Get market statistics for an area |
Best Practices for Production
Before deploying your property search to production, keep these best practices in mind:
- Cache responses — Property data doesn't change every second. Cache search results for 5-15 minutes to reduce API calls and improve speed
- Implement retry logic — If a request fails with a 429 (rate limit) or 503 (server error), wait and retry with exponential backoff
- Validate user input — Sanitize search parameters before passing them to the API to prevent injection attacks
- Use pagination — Don't fetch all results at once. Use
limitandoffsetparameters to paginate through large result sets - Store your API key securely — Never expose it in frontend code. Always proxy API calls through your backend
What to Do Next
You now have a working property search application. Here are some ideas to extend it:
- Add a web interface — Use Flask or FastAPI to create a browser-based search with a map view
- Build market comparisons — Fetch data for multiple cities and compare median prices, inventory, and days on market
- Set up saved searches — Let users save search criteria and notify them when new listings match
- Create an investment calculator — Use property data to estimate rental yields, mortgage payments, and ROI
The foundation you've built here — a clean API client with error handling and formatted output — is the same pattern used by production real estate applications. Scale it from here.