Intermediate
20 min

Compare Airbnb Markets for Investment

Score and rank cities by occupancy, ADR, RevPAR, and supply growth to identify the best short-term rental markets for investment.

1

Search for Markets

Use the /markets/search endpoint to look up any city by name. Each result includes the full market hierarchy (country, region, locality, district), the native currency, and the count of active Airbnb listings. Search for multiple cities to build your comparison set.

Python

import requests

API_KEY = "your_api_key"
BASE_URL = "https://api.airroi.com/v1"
headers = {"X-API-KEY": API_KEY}

# Search for markets
cities = ["austin", "nashville", "denver", "tampa", "boise"]
markets = []

for city in cities:
    response = requests.get(
        f"{BASE_URL}/markets/search?query={city}",
        headers=headers
    )
    results = response.json()
    if results:
        market = results[0]
        markets.append(market)
        print(f"Found: {market['full_name']} "
              f"({market['active_listings_count']:,} listings)")

print(f"\nSearching {len(markets)} markets")

2

Pull Summary Metrics

Call /markets/summary for each market to get the key performance indicators: occupancy rate, average daily rate, RevPAR, total revenue, booking lead time, average length of stay, and active listing count. Loop over your markets to collect all the data.

Python

# Pull summary metrics for each market
summaries = {}

for market in markets:
    payload = {
        "market": {
            "country": market["country"],
            "region": market["region"],
            "locality": market["locality"]
        },
        "currency": "usd",
        "num_months": 12
    }

    response = requests.post(
        f"{BASE_URL}/markets/summary",
        json=payload,
        headers={**headers, "Content-Type": "application/json"}
    )
    summary = response.json()
    summaries[market["full_name"]] = summary

    print(f"{market['full_name']}:")
    print(f"  Occupancy: {summary['occupancy']:.0%}")
    print(f"  ADR: ${summary['average_daily_rate']:.0f}")
    print(f"  RevPAR: ${summary['rev_par']:.0f}")
    print(f"  Revenue: ${summary['revenue']:,.0f}")
    print(f"  Listings: {summary['active_listings_count']:,}")
    print()

3

Build a Comparison Table

Arrange all five markets side by side in a clean table showing occupancy, ADR, RevPAR, revenue, and supply. This gives you an at-a-glance view of each market’s performance profile.

Python

# Build a side-by-side comparison table
print(f"{'Market':<25} {'Occ%':>6} {'ADR':>8} {'RevPAR':>8} "
      f"{'Revenue':>10} {'Supply':>8}")
print("-" * 70)

for name, s in summaries.items():
    print(f"{name:<25} {s['occupancy']:>5.0%} "
          f"${s['average_daily_rate']:>7,.0f} "
          f"${s['rev_par']:>7,.0f} "
          f"${s['revenue']:>9,.0f} "
          f"{s['active_listings_count']:>7,}")

4

Time-Series Trends

For your top two markets (by RevPAR), pull 12 months of month-over-month data using /markets/metrics/all. Compare occupancy and ADR trends to see which market has stronger momentum and more consistent performance.

Python

# Pull time-series trends for top 2 markets
# (pick the 2 with highest RevPAR)
ranked = sorted(summaries.items(),
                key=lambda x: x[1]["rev_par"], reverse=True)
top_2 = ranked[:2]

trends = {}
for name, _ in top_2:
    market = next(m for m in markets if m["full_name"] == name)
    payload = {
        "market": {
            "country": market["country"],
            "region": market["region"],
            "locality": market["locality"]
        },
        "num_months": 12,
        "currency": "usd"
    }

    response = requests.post(
        f"{BASE_URL}/markets/metrics/all",
        json=payload,
        headers={**headers, "Content-Type": "application/json"}
    )
    trends[name] = response.json()

# Compare month-over-month occupancy
for name, months in trends.items():
    print(f"\n{name} - Monthly Occupancy:")
    for m in months:
        occ = m["occupancy"]["avg"]
        print(f"  {m['date']}: {occ:.0%}")

5

Score and Rank Markets

Create a weighted composite score using four dimensions: occupancy (30%), revenue growth year-over-year (25%), RevPAR (25%), and supply stability (20%). Normalize each metric to a 0-100 scale, apply the weights, and rank markets by their composite score.

Python

# Score and rank markets with weighted scoring
def score_market(name, summary, trend_data=None):
    scores = {}

    # Occupancy score (30%) — higher is better, normalize to 0-100
    scores["occupancy"] = summary["occupancy"] * 100

    # RevPAR score (25%) — normalize against max in set
    scores["revpar"] = summary["rev_par"]

    # Revenue growth YoY (25%) — calculate from trend if available
    if trend_data and len(trend_data) >= 12:
        recent_rev = sum(m["revenue"]["avg"] for m in trend_data[-6:])
        older_rev = sum(m["revenue"]["avg"] for m in trend_data[:6])
        growth = (recent_rev - older_rev) / older_rev if older_rev > 0 else 0
        scores["growth"] = growth * 100
    else:
        scores["growth"] = 0

    # Supply stability (20%) — lower growth = more stable
    scores["supply_stability"] = summary["active_listings_count"]

    return scores

# Calculate scores for all markets
market_scores = {}
for name, summary in summaries.items():
    trend = trends.get(name)
    market_scores[name] = score_market(name, summary, trend)

# Normalize and apply weights
max_revpar = max(s["revpar"] for s in market_scores.values())
max_supply = max(s["supply_stability"] for s in market_scores.values())

weights = {"occupancy": 0.30, "growth": 0.25, "revpar": 0.25, "supply_stability": 0.20}

final_scores = {}
for name, scores in market_scores.items():
    weighted = (
        weights["occupancy"] * scores["occupancy"] +
        weights["growth"] * max(scores["growth"], 0) +
        weights["revpar"] * (scores["revpar"] / max_revpar * 100) +
        weights["supply_stability"] * (1 - scores["supply_stability"] / max_supply) * 100
    )
    final_scores[name] = round(weighted, 1)

# Rank
ranked = sorted(final_scores.items(), key=lambda x: x[1], reverse=True)
print("Market Rankings:")
for i, (name, score) in enumerate(ranked, 1):
    print(f"  {i}. {name}: {score}/100")

6

Deep Dive the Winning Market

For the top-scoring market, pull listing-level data via /listings/search/market to understand the neighborhood breakdown, property types, and pricing distribution. This gives you ground-level insight into what makes that market strong and where opportunities exist.

Python

# Deep dive into the top-scoring market
winner_name = ranked[0][0]
winner_market = next(m for m in markets if m["full_name"] == winner_name)

print(f"Deep diving into: {winner_name}\n")

# Pull listing-level data
payload = {
    "market": {
        "country": winner_market["country"],
        "region": winner_market["region"],
        "locality": winner_market["locality"]
    },
    "pagination": {"page_size": 10, "offset": 0},
    "currency": "usd"
}

response = requests.post(
    f"{BASE_URL}/listings/search/market",
    json=payload,
    headers={**headers, "Content-Type": "application/json"}
)
listings = response.json()

# Analyze property mix and pricing distribution
for listing in listings.get("results", []):
    print(f"  {listing.get('title', 'N/A')}")
    print(f"    Bedrooms: {listing.get('bedrooms', 'N/A')}, "
          f"Rate: ${listing.get('average_daily_rate', 0):.0f}/night, "
          f"Occupancy: {listing.get('occupancy', 0):.0%}")
    print()

Continue Learning

Keep exploring the AirROI API with these related tutorials.

Frequently Asked Questions

There is no hard limit on how many markets you can compare. The /markets/search endpoint returns results for any query, and you can call /markets/summary for each one. In practice, comparing 5-10 markets gives the best analytical balance between breadth and depth. This tutorial shows a loop pattern that works for any number of markets.

The /markets/summary endpoint returns occupancy rate, average daily rate (ADR), RevPAR (revenue per available rental), total revenue, booking lead time, average length of stay, minimum nights, and active listings count. You can use any combination of these metrics for scoring and ranking.

Yes. AirROI covers 190+ countries. Use the currency parameter (e.g., currency:'usd') to normalize all financial metrics to the same currency when comparing markets across different countries. The /markets/search endpoint works with city names in any country.

This tutorial provides a weighted scoring model: occupancy (30%), revenue growth YoY (25%), RevPAR (25%), and supply stability (20%). You can adjust these weights based on your investment priorities. For example, cash-flow investors may weight occupancy higher, while growth investors may prioritize revenue trends.

Supply stability measures how much the active listing count has changed over the past 12 months. A market with stable or slowly growing supply (under 10% growth) is more favorable than one where supply is exploding (30%+ growth), because rapid supply growth can depress occupancy and rates for existing listings.

Market summary data is updated monthly and reflects the most recent complete calendar month. Time-series data via /markets/metrics/all provides month-by-month historical data going back up to 60 months, so you can analyze long-term trends and year-over-year comparisons.

Yes. This tutorial includes a Google Sheets track that uses Apps Script to call the API and populate a comparison table directly in your spreadsheet. This is particularly useful for sharing results with partners or clients who prefer working in spreadsheets.

Ready to Build?

Get your API key and start making calls in minutes.
made with