Back to Blog

How to Build a Real Estate Property Search App with Python

A step-by-step tutorial for building a property search feature that fetches listings, photos, and pricing from a real estate API using Python.

How to Build a Real Estate Property Search App with Python

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:

Real Estate API Request Flow
Your App Python Backend API Key API Gateway Auth & Rate Limit Caching Layer Data Source Property Listings Agent Profiles Market Statistics JSON Response

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 requests library (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:

Terminal
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):

.env
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:

Python — api_client.py
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:

Python — api_client.py (continued)
    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:

Python — display.py
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

Python — main.py
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:

EndpointMethodDescription
/searchGETSearch listings by location and filters
/property/{id}GETGet full property details
/agentsGETSearch real estate agents
/agent/{id}GETGet agent profile and listings
/market-statsGETGet 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 limit and offset parameters 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.

Share this article:

Ready to Start Building?

Get your API key or deploy a Cloud VPS in minutes.