How to Analyze UAE Rental Yields Using API Data
Rental yield is the single most important metric for buy-to-let investors. It tells you what annual return you can expect from a property relative to its purchase price. In the UAE, gross rental yields typically range from 5% to 9%, varying significantly by location, property type, and bedroom count.
In this tutorial, we will build a Python tool that calculates and compares rental yields across UAE neighborhoods using real listing data from BayutAPI.
What Is Rental Yield?
Gross rental yield is calculated as:
Gross Yield (%) = (Annual Rental Income / Purchase Price) × 100
For example, if you buy an apartment for AED 1,000,000 and rent it for AED 70,000 per year, your gross yield is 7%.
Net rental yield subtracts expenses (maintenance fees, service charges, management fees, vacancy periods) from the rental income. We will focus on gross yield since the API provides listing prices, not expense data.
Setting Up the Yield Calculator
Start with our API client:
import requests
from statistics import median
from dataclasses import dataclass
API_URL = "https://bayut14.p.rapidapi.com/v2"
HEADERS = {
"x-rapidapi-host": "bayut14.p.rapidapi.com",
"x-rapidapi-key": "YOUR_API_KEY"
}
@dataclass
class YieldAnalysis:
area_name: str
property_type: str
bedrooms: int
sale_listings: int
rental_listings: int
median_sale_price: float
median_annual_rent: float
gross_yield_pct: float
price_to_rent_ratio: float
min_sale_price: float
max_sale_price: float
min_rent: float
max_rent: float
Fetching and Processing Listing Data
We need both sale and rental prices for the same area and property type:
def fetch_prices(
location_id: str,
purpose: str,
category: str = "apartments",
rooms: int | None = None,
max_pages: int = 4
) -> list[int]:
"""Fetch listing prices for a location and return as a sorted list."""
prices = []
for page in range(1, max_pages + 1):
params = {
"location_ids": location_id,
"purpose": purpose,
"property_type": category,
"page": str(page),
"sort": "latest"
}
if rooms is not None:
params["rooms"] = str(rooms)
response = requests.get(
f"{API_URL}/properties",
headers=HEADERS,
params=params
)
response.raise_for_status()
properties = response.json().get("data", {}).get("properties", [])
if not properties:
break
for hit in properties:
price = hit.get("price", 0)
if price > 0:
prices.append(price)
return sorted(prices)
def get_location_id(query: str) -> str | None:
"""Resolve location name to ID via autocomplete."""
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]["externalID"] if locations else None
Computing Rental Yield
The core analysis function:
def analyze_yield(
area_name: str,
location_id: str,
category: str = "apartments",
rooms: int = 1
) -> YieldAnalysis | None:
"""Calculate rental yield for a specific area and property type."""
# Fetch sale prices
sale_prices = fetch_prices(location_id, "for-sale", category, rooms)
if len(sale_prices) < 5:
print(f" Insufficient sale data for {area_name} ({len(sale_prices)} listings)")
return None
# Fetch rental prices (annual)
rent_prices = fetch_prices(location_id, "for-rent", category, rooms)
if len(rent_prices) < 5:
print(f" Insufficient rental data for {area_name} ({len(rent_prices)} listings)")
return None
median_sale = median(sale_prices)
median_rent = median(rent_prices)
# Gross yield
gross_yield = (median_rent / median_sale) * 100 if median_sale > 0 else 0
# Price-to-rent ratio (how many years of rent to equal purchase price)
price_to_rent = median_sale / median_rent if median_rent > 0 else 0
return YieldAnalysis(
area_name=area_name,
property_type=category,
bedrooms=rooms,
sale_listings=len(sale_prices),
rental_listings=len(rent_prices),
median_sale_price=median_sale,
median_annual_rent=median_rent,
gross_yield_pct=round(gross_yield, 2),
price_to_rent_ratio=round(price_to_rent, 1),
min_sale_price=sale_prices[0],
max_sale_price=sale_prices[-1],
min_rent=rent_prices[0],
max_rent=rent_prices[-1]
)
Running the Analysis Across Neighborhoods
Now let’s compare yields across popular Dubai areas:
def compare_yields(
areas: list[str],
category: str = "apartments",
rooms: int = 1
) -> list[YieldAnalysis]:
"""Compare rental yields across multiple areas."""
results = []
for area_name in areas:
print(f"Analyzing {area_name}...")
location_id = get_location_id(area_name)
if not location_id:
print(f" Location not found: {area_name}")
continue
analysis = analyze_yield(area_name, location_id, category, rooms)
if analysis:
results.append(analysis)
# Sort by yield (highest first)
results.sort(key=lambda x: x.gross_yield_pct, reverse=True)
return results
def print_yield_report(results: list[YieldAnalysis]) -> None:
"""Print a formatted yield comparison report."""
if not results:
print("No results to display.")
return
bedrooms = results[0].bedrooms
prop_type = results[0].property_type
print(f"\n{'='*85}")
print(f" Rental Yield Analysis — {bedrooms}BR {prop_type.title()} in Dubai")
print(f"{'='*85}\n")
header = (
f"{'Area':<25} {'Median Sale':>14} {'Annual Rent':>13} "
f"{'Yield':>7} {'P/R Ratio':>10} {'Data':>6}"
)
print(header)
print("-" * 85)
for r in results:
line = (
f"{r.area_name:<25} "
f"AED {r.median_sale_price:>10,.0f} "
f"AED {r.median_annual_rent:>9,.0f} "
f"{r.gross_yield_pct:>6.2f}% "
f"{r.price_to_rent_ratio:>9.1f}x "
f"{r.sale_listings + r.rental_listings:>5}"
)
print(line)
print(f"\n{'='*85}")
# Highlight best and worst
best = results[0]
worst = results[-1]
print(f"\n Highest yield: {best.area_name} at {best.gross_yield_pct}%")
print(f" Lowest yield: {worst.area_name} at {worst.gross_yield_pct}%")
print(f" Spread: {best.gross_yield_pct - worst.gross_yield_pct:.2f} percentage points")
if __name__ == "__main__":
areas = [
"Dubai Marina",
"Downtown Dubai",
"Jumeirah Village Circle",
"Business Bay",
"Dubai Hills Estate",
"Jumeirah Lake Towers",
"Dubai Silicon Oasis",
"International City",
"Sports City",
"Al Furjan"
]
results = compare_yields(areas, category="apartments", rooms=1)
print_yield_report(results)
Sample Output
Running this analysis produces a report like:
=====================================================================================
Rental Yield Analysis — 1BR Apartments in Dubai
=====================================================================================
Area Median Sale Annual Rent Yield P/R Ratio Data
-------------------------------------------------------------------------------------
International City AED 380,000 AED 28,000 7.37% 13.6x 98
Dubai Silicon Oasis AED 520,000 AED 36,000 6.92% 14.4x 112
Sports City AED 480,000 AED 32,000 6.67% 15.0x 85
Jumeirah Village Circle AED 750,000 AED 48,000 6.40% 15.6x 156
Jumeirah Lake Towers AED 850,000 AED 52,000 6.12% 16.3x 134
Al Furjan AED 680,000 AED 40,000 5.88% 17.0x 72
Business Bay AED 1,100,000 AED 62,000 5.64% 17.7x 148
Dubai Marina AED 1,200,000 AED 65,000 5.42% 18.5x 162
Dubai Hills Estate AED 1,050,000 AED 55,000 5.24% 19.1x 94
Downtown Dubai AED 1,500,000 AED 75,000 5.00% 20.0x 128
=====================================================================================
Highest yield: International City at 7.37%
Lowest yield: Downtown Dubai at 5.00%
Spread: 2.37 percentage points
Interpreting the Results
Several patterns typically emerge from UAE rental yield data:
Affordable areas have higher yields. Neighborhoods like International City, Dubai Silicon Oasis, and Sports City often show yields above 6.5%. The entry price is lower, but rents remain relatively high because of strong demand from budget-conscious tenants.
Premium areas have lower yields. Downtown Dubai, Palm Jumeirah, and similar locations show lower yields (4.5-5.5%) because purchase prices are heavily influenced by prestige and appreciation potential, not just rental income.
The price-to-rent ratio tells a story. A ratio below 15 means buy-to-let is attractive. Above 20 means the area may be overpriced relative to rental income. Most Dubai neighborhoods fall in the 13-20 range.
Bedroom count matters. Studios and 1-bedrooms typically have higher yields than 3-bedrooms because smaller units have a larger pool of potential tenants.
Extending the Analysis
Compare Across Bedroom Counts
for rooms in [0, 1, 2, 3]: # 0 = studio
label = "Studio" if rooms == 0 else f"{rooms}BR"
print(f"\n\nAnalyzing {label} apartments...")
results = compare_yields(
["Dubai Marina", "JVC", "Business Bay"],
category="apartments",
rooms=rooms
)
print_yield_report(results)
Track Yield Changes Over Time
import json
from datetime import date
def save_yield_snapshot(results: list[YieldAnalysis]) -> None:
"""Save yield data for historical comparison."""
snapshot = {
"date": date.today().isoformat(),
"yields": [
{
"area": r.area_name,
"bedrooms": r.bedrooms,
"median_sale": r.median_sale_price,
"median_rent": r.median_annual_rent,
"gross_yield": r.gross_yield_pct
}
for r in results
]
}
filename = f"yield_snapshot_{date.today().isoformat()}.json"
with open(filename, "w") as f:
json.dump(snapshot, f, indent=2)
print(f"Snapshot saved to {filename}")
Include Villa Yields
Villas typically have different yield profiles than apartments:
apartment_yields = compare_yields(areas, category="apartments", rooms=3)
villa_yields = compare_yields(areas, category="villas", rooms=3)
print("\n3BR Apartments vs 3BR Villas:")
for apt, villa in zip(apartment_yields, villa_yields):
print(f" {apt.area_name}: Apt {apt.gross_yield_pct}% vs Villa {villa.gross_yield_pct}%")
Limitations to Keep in Mind
This analysis uses listing prices (asking prices), not transaction prices. In practice:
- Sale prices — Actual transaction prices may be 5-10% lower than listing prices after negotiation.
- Rental prices — Actual rents may vary based on lease terms, number of cheques, and tenant negotiation.
- Vacancy — Gross yield assumes 100% occupancy. In reality, expect 2-4 weeks of vacancy between tenants. A 5% vacancy adjustment is common.
- Expenses — Service charges in Dubai typically run AED 10-25 per square foot per year. This significantly impacts net yield, especially for larger units.
Despite these limitations, relative comparisons between areas are reliable. If Area A shows 7% gross yield and Area B shows 5%, the difference will hold even after accounting for transaction discounts and expenses.
Conclusion
Rental yield analysis turns raw property data into actionable investment insights. With BayutAPI, you can automate this analysis across any combination of areas, property types, and bedroom counts in the UAE.
The investors who succeed in Dubai real estate are the ones who make data-driven decisions. This tool gives you the foundation to do exactly that. Run it monthly, track the trends, and let the numbers guide your investment strategy.
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.