How to Track Off-Plan Projects in Dubai Programmatically
Off-plan properties — units in developments that are still under construction or not yet built — are a major segment of the Dubai real estate market. Developers like Emaar, DAMAC, Nakheel, and Sobha regularly launch new projects, and investors who act early often secure the best prices and payment plans.
Tracking these launches manually means checking multiple websites daily. In this tutorial, we will build a Python tool that monitors new off-plan projects using BayutAPI and notifies you when new developments appear.
Understanding the Search New Projects Endpoint
BayutAPI provides a dedicated endpoint for off-plan and new project data:
GET https://bayut14.p.rapidapi.com/search-new-projects
Key parameters:
- location_ids — Filter by area (use autocomplete to get IDs)
- page — Page number (starts from 1, 24 results per page)
- property_type — Filter by type (residential, apartments, villas, etc.)
Let’s start by exploring what data is available:
import requests
API_URL = "https://bayut14.p.rapidapi.com/v2"
HEADERS = {
"x-rapidapi-host": "bayut14.p.rapidapi.com",
"x-rapidapi-key": "YOUR_API_KEY"
}
def search_new_projects(location_id: str, page: int = 1) -> dict:
"""Search for off-plan/new projects in a specific location."""
response = requests.get(
f"{API_URL}/search-new-projects",
headers=HEADERS,
params={
"location_ids": location_id,
"page": str(page)
}
)
response.raise_for_status()
return response.json()
# Search for new projects in Dubai (location_ids: 5002)
data = search_new_projects("5002")
print(f"Total projects found: {data.get('data', {}).get('total', 0)}")
for project in data.get("data", {}).get("properties", [])[:5]:
print(f"\n{project.get('title', {}).get('en', 'Untitled')}")
print(f" Developer: {project.get('developerName', 'N/A')}")
print(f" Location: {project.get('locationName', 'N/A')}")
A typical project response includes:
{
"title": { "en": "Riviera Azure at MBR City" },
"externalID": "np-12345",
"developerName": "Azizi Developments",
"locationName": "Meydan, Dubai",
"coverPhoto": { "url": "https://..." },
"location": [
{ "name": "UAE", "slug": "uae" },
{ "name": "Dubai", "slug": "dubai" },
{ "name": "Meydan", "slug": "meydan" }
],
"startingPrice": 850000,
"completionStatus": "under-construction"
}
Building the Project Tracker
Our tracker will:
- Fetch all current off-plan projects for specified areas
- Compare against previously seen projects
- Identify new additions
- Save state between runs
import json
import time
from pathlib import Path
from datetime import datetime
STATE_FILE = Path("project_tracker_state.json")
def load_state() -> dict:
"""Load previously seen projects."""
if STATE_FILE.exists():
return json.loads(STATE_FILE.read_text())
return {"seen_projects": {}, "last_check": None}
def save_state(state: dict) -> None:
"""Save current state."""
STATE_FILE.write_text(json.dumps(state, indent=2))
def get_location_id(query: str) -> str | None:
"""Resolve a location name to its ID."""
response = requests.get(
f"{API_URL}/autocomplete",
headers=HEADERS,
params={"query": query, "purpose": "for-sale"}
)
response.raise_for_status()
locations = response.json().get("data", {}).get("locations", [])
return locations[0].get("externalID") if locations else None
def fetch_all_projects(location_id: str) -> list[dict]:
"""Fetch all projects for a location across all pages."""
all_projects = []
page = 1
while True:
data = search_new_projects(location_id, page=page)
properties = data.get("data", {}).get("properties", [])
if not properties:
break
all_projects.extend(properties)
total = data.get("data", {}).get("total", 0)
if len(all_projects) >= total:
break
page += 1
time.sleep(0.5) # Be respectful with rate limits
return all_projects
def check_for_new_projects(areas: list[str]) -> list[dict]:
"""Check multiple areas for new projects."""
state = load_state()
seen = state["seen_projects"]
new_projects = []
for area_name in areas:
print(f"Checking {area_name}...")
location_id = get_location_id(area_name)
if not location_id:
print(f" Could not find location: {area_name}")
continue
projects = fetch_all_projects(location_id)
print(f" Found {len(projects)} total projects")
for project in projects:
project_id = project.get("externalID", "")
if project_id and project_id not in seen:
seen[project_id] = {
"title": project.get("title", {}).get("en", "Untitled"),
"developer": project.get("developerName", "N/A"),
"location": area_name,
"first_seen": datetime.now().isoformat(),
"starting_price": project.get("startingPrice")
}
new_projects.append(project)
state["seen_projects"] = seen
state["last_check"] = datetime.now().isoformat()
save_state(state)
return new_projects
Formatting Alerts
When new projects are found, format them into clear notifications:
def format_project_alert(project: dict) -> str:
"""Format a project into a readable alert message."""
title = project.get("title", {}).get("en", "Untitled Project")
developer = project.get("developerName", "Unknown Developer")
location_parts = [
level.get("name", "")
for level in project.get("location", [])
]
location = " > ".join(location_parts) if location_parts else "N/A"
starting_price = project.get("startingPrice")
price_str = f"AED {starting_price:,.0f}" if starting_price else "Price TBA"
completion = project.get("completionStatus", "N/A")
return (
f"NEW PROJECT: {title}\n"
f" Developer: {developer}\n"
f" Location: {location}\n"
f" Starting Price: {price_str}\n"
f" Status: {completion}\n"
)
def run_tracker():
"""Main tracker execution."""
areas_to_track = [
"Dubai Marina",
"Downtown Dubai",
"Business Bay",
"Jumeirah Village Circle",
"Dubai Hills Estate",
"Mohammed Bin Rashid City",
"Dubai Creek Harbour",
"Palm Jumeirah"
]
print(f"Off-Plan Project Tracker — {datetime.now().strftime('%Y-%m-%d %H:%M')}")
print("=" * 60)
new_projects = check_for_new_projects(areas_to_track)
if new_projects:
print(f"\n{'='*60}")
print(f" {len(new_projects)} NEW PROJECT(S) FOUND!")
print(f"{'='*60}\n")
for project in new_projects:
print(format_project_alert(project))
else:
print("\nNo new projects since last check.")
state = load_state()
print(f"\nTotal tracked projects: {len(state['seen_projects'])}")
if __name__ == "__main__":
run_tracker()
Adding Email Notifications
For a production setup, send email alerts when new projects appear:
import smtplib
from email.mime.text import MIMEText
def send_email_alert(new_projects: list[dict]) -> None:
"""Send an email alert for new projects."""
subject = f"{len(new_projects)} New Off-Plan Project(s) in Dubai"
body = "New off-plan projects detected:\n\n"
for project in new_projects:
body += format_project_alert(project) + "\n"
msg = MIMEText(body)
msg["Subject"] = subject
msg["From"] = "tracker@yourdomain.com"
msg["To"] = "you@yourdomain.com"
with smtplib.SMTP("smtp.yourdomain.com", 587) as server:
server.starttls()
server.login("tracker@yourdomain.com", "your-password")
server.send_message(msg)
Scheduling the Tracker
Run the tracker automatically using cron (Linux/Mac) or Task Scheduler (Windows):
# Run every 6 hours
0 */6 * * * cd /path/to/project && python tracker.py >> tracker.log 2>&1
Or use a simple Python scheduler:
import schedule
import time
schedule.every(6).hours.do(run_tracker)
while True:
schedule.run_pending()
time.sleep(60)
Analyzing Project Trends
Once you have accumulated data over several weeks, you can analyze trends:
def analyze_trends():
"""Analyze project launch trends from stored data."""
state = load_state()
projects = state["seen_projects"]
# Count projects by developer
developers = {}
for pid, info in projects.items():
dev = info.get("developer", "Unknown")
developers[dev] = developers.get(dev, 0) + 1
print("\nProjects by Developer:")
for dev, count in sorted(developers.items(), key=lambda x: -x[1])[:10]:
print(f" {dev}: {count} projects")
# Count projects by area
areas = {}
for pid, info in projects.items():
area = info.get("location", "Unknown")
areas[area] = areas.get(area, 0) + 1
print("\nProjects by Area:")
for area, count in sorted(areas.items(), key=lambda x: -x[1]):
print(f" {area}: {count} projects")
Use Cases for Off-Plan Tracking
Automated off-plan tracking is valuable for several audiences:
- Real estate investors who want early access to new launches at pre-launch pricing
- Property portals that want to feature new projects as soon as they appear
- Market analysts tracking the pace of new development across Dubai
- Real estate agents who want to advise clients on upcoming opportunities
The data you collect over time also becomes valuable on its own — launch frequency, developer activity, and pricing trends across different areas tell a compelling story about where the Dubai market is heading.
Next Steps
You now have a working off-plan project tracker. To take it further, consider:
- Adding a web dashboard to visualize tracked projects on a map
- Building price trend charts for specific developers
- Cross-referencing with existing listings to see how off-plan prices compare to ready properties
- Setting up Telegram or Slack notifications instead of email
The BayutAPI search-new-projects endpoint gives you the raw data. What you build with it is up to you.
Ready to Build with UAE Real Estate Data?
Get your API key and start making requests in minutes. Free tier available with 900 requests per month.