1
Use POST /markets/metrics/all with a market identifier and num_months: 24. This returns monthly time-series data with occupancy, ADR, RevPAR, and revenue -- each broken down by percentile (avg, p25, p50, p75, p90).
Python
import requests
API_KEY = "your_api_key_here"
BASE_URL = "https://api.airroi.com"
# Pull 24 months of market-level metrics for Miami
response = requests.post(
f"{BASE_URL}/markets/metrics/all",
headers={
"X-API-KEY": API_KEY,
"Content-Type": "application/json"
},
json={
"market": {
"country": "us",
"region": "florida",
"locality": "miami"
},
"num_months": 24,
"currency": "usd"
}
)
data = response.json()
metrics = data["metrics"] # Array of 24 monthly data points
print(f"Retrieved {len(metrics)} months of data")
print(f"Date range: {metrics[0]['date']} to {metrics[-1]['date']}")Response structure (each monthly data point):
json
// Each monthly data point looks like:
{
"date": "2024-01",
"occupancy": {
"avg": 0.68, "p25": 0.52, "p50": 0.70, "p75": 0.82, "p90": 0.91
},
"average_daily_rate": {
"avg": 215, "p25": 155, "p50": 210, "p75": 280, "p90": 385
},
"revpar": {
"avg": 146, "p25": 82, "p50": 147, "p75": 215, "p90": 335
},
"revenue": {
"avg": 4530, "p25": 2540, "p50": 4560, "p75": 6670, "p90": 10380
},
"booking_lead_time": {
"avg": 28, "p25": 7, "p50": 21, "p75": 42, "p90": 75
},
"length_of_stay": {
"avg": 4.2, "p25": 2, "p50": 3, "p75": 5, "p90": 8
},
"active_listings_count": 8420
}2
Group the time-series data by month-of-year and average across years. This transforms 24 raw data points into a clean 12-month seasonal profile that shows the typical performance for each calendar month.
Python
from collections import defaultdict
# Group metrics by month-of-year (1-12) and average across years
monthly_profile = defaultdict(lambda: {"occupancy": [], "adr": [], "revenue": []})
for m in metrics:
month_num = int(m["date"].split("-")[1]) # Extract month number
monthly_profile[month_num]["occupancy"].append(m["occupancy"]["p50"])
monthly_profile[month_num]["adr"].append(m["average_daily_rate"]["p50"])
monthly_profile[month_num]["revenue"].append(m["revenue"]["p50"])
# Build the 12-month seasonal profile
month_names = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
print(f"\n{'Month':<6} {'Occupancy':>10} {'ADR':>10} {'Revenue':>10}")
print("-" * 40)
for month_num in range(1, 13):
data = monthly_profile[month_num]
avg_occ = sum(data["occupancy"]) / len(data["occupancy"])
avg_adr = sum(data["adr"]) / len(data["adr"])
avg_rev = sum(data["revenue"]) / len(data["revenue"])
print(f" {month_names[month_num-1]:<6} {avg_occ:>9.0%} ${avg_adr:>8,.0f} ${avg_rev:>8,.0f}")3
Classify each month into Peak (occupancy above 70%), Shoulder (50-70%), or Low (below 50%) season. This gives you a clear framework for pricing, marketing, and capacity planning decisions.
Python
# Classify months into Peak, Shoulder, and Low seasons
# Based on median occupancy thresholds
seasonal_classification = {}
for month_num in range(1, 13):
data = monthly_profile[month_num]
avg_occ = sum(data["occupancy"]) / len(data["occupancy"])
if avg_occ > 0.70:
season = "Peak"
elif avg_occ > 0.50:
season = "Shoulder"
else:
season = "Low"
seasonal_classification[month_num] = {
"season": season,
"occupancy": avg_occ,
"adr": sum(data["adr"]) / len(data["adr"]),
"revenue": sum(data["revenue"]) / len(data["revenue"]),
}
# Display seasonal calendar
print("\nSeasonal Classification:")
print(f"{'Month':<6} {'Season':<10} {'Occupancy':>10} {'ADR':>8} {'Revenue':>10}")
print("-" * 48)
for month_num in range(1, 13):
s = seasonal_classification[month_num]
print(
f" {month_names[month_num-1]:<6} {s['season']:<10} "
f"{s['occupancy']:>9.0%} ${s['adr']:>6,.0f} ${s['revenue']:>8,.0f}"
)
# Summarize seasons
for season_type in ["Peak", "Shoulder", "Low"]:
months_in = [month_names[m-1] for m, s in seasonal_classification.items()
if s["season"] == season_type]
print(f"\n{season_type} months: {', '.join(months_in)}")4
Compare the same months across years to spot trends. Is occupancy growing or declining? Are nightly rates rising faster than inflation? YoY analysis reveals whether the market is strengthening, plateauing, or softening.
Python
# Year-over-year comparison: same month across different years
# Requires 24+ months of data
print("\nYear-over-Year Comparison:")
print(f"{'Month':<10} {'Year 1 Occ':>10} {'Year 2 Occ':>10} {'YoY Change':>12}")
print("-" * 48)
for month_num in range(1, 13):
data = monthly_profile[month_num]
if len(data["occupancy"]) >= 2:
yr1_occ = data["occupancy"][0]
yr2_occ = data["occupancy"][1]
yoy_change = (yr2_occ - yr1_occ) / yr1_occ
yr1_adr = data["adr"][0]
yr2_adr = data["adr"][1]
adr_change = (yr2_adr - yr1_adr) / yr1_adr
print(
f" {month_names[month_num-1]:<10} "
f"{yr1_occ:>9.0%} {yr2_occ:>9.0%} {yoy_change:>+11.1%}"
)
# Overall YoY trends
yr1_months = [m for m in metrics if m["date"][:4] == metrics[0]["date"][:4]]
yr2_months = [m for m in metrics if m["date"][:4] != metrics[0]["date"][:4]]
yr1_avg_occ = sum(m["occupancy"]["p50"] for m in yr1_months) / len(yr1_months)
yr2_avg_occ = sum(m["occupancy"]["p50"] for m in yr2_months) / len(yr2_months)
yr1_avg_adr = sum(m["average_daily_rate"]["p50"] for m in yr1_months) / len(yr1_months)
yr2_avg_adr = sum(m["average_daily_rate"]["p50"] for m in yr2_months) / len(yr2_months)
print(f"\nAnnual averages:")
print(f" Occupancy: {yr1_avg_occ:.0%} -> {yr2_avg_occ:.0%} ({(yr2_avg_occ/yr1_avg_occ - 1):+.1%})")
print(f" ADR: ${yr1_avg_adr:,.0f} -> ${yr2_avg_adr:,.0f} ({(yr2_avg_adr/yr1_avg_adr - 1):+.1%})")5
Pull individual listing metrics via GET /listings/metrics/all and compare them to the market medians month by month. This reveals which months a listing outperforms or underperforms the market, highlighting opportunities for improvement.
Python
# Compare an individual listing's metrics to the market
listing_id = "your_listing_id_here"
# Pull listing-level time series
listing_resp = requests.get(
f"{BASE_URL}/listings/metrics/all",
headers={"X-API-KEY": API_KEY},
params={
"id": listing_id,
"currency": "usd",
"num_months": 24
}
)
listing_metrics = listing_resp.json()["metrics"]
# Compare listing vs market for each month
print(f"\n{'Month':<10} {'Listing Occ':>12} {'Market Occ':>12} {'Diff':>8}")
print("-" * 46)
for lm in listing_metrics:
# Find matching market month
market_month = next((m for m in metrics if m["date"] == lm["date"]), None)
if market_month:
listing_occ = lm["occupancy"]
market_occ = market_month["occupancy"]["p50"]
diff = listing_occ - market_occ
date_label = lm["date"]
status = "+" if diff > 0 else ""
print(f" {date_label:<10} {listing_occ:>11.0%} {market_occ:>11.0%} {status}{diff:>6.0%}")
# Summary: months outperforming vs underperforming
outperform = sum(1 for lm in listing_metrics
for mm in metrics if mm["date"] == lm["date"]
and lm["occupancy"] > mm["occupancy"]["p50"])
total = len(listing_metrics)
print(f"\nOutperforms market: {outperform}/{total} months ({outperform/total:.0%})")6
Use the seasonal classification to set base nightly rates. Peak months get 120% of the annual average ADR, shoulder months stay at 100%, and low months drop to 80%. This simple multiplier approach captures the majority of seasonal pricing value.
Python
# Build a seasonal pricing calendar based on market data
# Use seasonal classification from Step 3
# Calculate annual average ADR as baseline
annual_avg_adr = sum(
s["adr"] for s in seasonal_classification.values()
) / 12
# Apply seasonal multipliers
multipliers = {"Peak": 1.20, "Shoulder": 1.00, "Low": 0.80}
print("\nSeasonal Pricing Calendar")
print("=" * 55)
print(f"Annual average ADR: ${annual_avg_adr:,.0f}")
print(f"\n{'Month':<6} {'Season':<10} {'Multiplier':>11} {'Suggested Rate':>15}")
print("-" * 45)
total_revenue = 0
for month_num in range(1, 13):
s = seasonal_classification[month_num]
multiplier = multipliers[s["season"]]
suggested_rate = annual_avg_adr * multiplier
# Estimate monthly revenue: rate * occupancy * 30 days
est_monthly = suggested_rate * s["occupancy"] * 30
total_revenue += est_monthly
print(
f" {month_names[month_num-1]:<6} {s['season']:<10} "
f"{multiplier:>10.0%} ${suggested_rate:>13,.0f}"
)
print(f"\nProjected annual revenue: ${total_revenue:,.0f}")
print(f"\nPricing strategy summary:")
print(f" Peak months: ${annual_avg_adr * 1.20:,.0f}/night (120% of avg)")
print(f" Shoulder months: ${annual_avg_adr * 1.00:,.0f}/night (100% of avg)")
print(f" Low months: ${annual_avg_adr * 0.80:,.0f}/night (80% of avg)")Keep exploring the AirROI API with these related tutorials.
The /markets/metrics/all endpoint supports up to 60 months (5 years) of historical data via the num_months parameter. For seasonality analysis, 24 months is recommended because it gives you two full seasonal cycles to identify reliable patterns and year-over-year trends.
Each monthly data point includes occupancy, average daily rate (ADR), RevPAR, revenue, booking lead time, length of stay, and active listings count. Occupancy, ADR, RevPAR, and revenue include percentile breakdowns (avg, p25, p50, p75, p90) so you can see the full distribution, not just averages.
Use the /markets/search endpoint to find the exact market identifier. Markets are structured hierarchically: country, region (state), and locality (city). For example, Miami is {country: 'us', region: 'florida', locality: 'miami'}. You can search by name to find the correct identifiers.
Yes. Pull market metrics for multiple markets and compare their seasonal profiles side by side. This is useful for diversification analysis -- markets with opposite seasonal patterns (e.g., beach vs. ski) can smooth out annual revenue when held together in a portfolio.
Market-level metrics (via /markets/metrics/all) aggregate data across all listings in a geographic area and include percentile distributions. Listing-level metrics (via /listings/metrics/all) show the actual performance of a single specific listing. Comparing the two reveals whether a listing outperforms or underperforms its market.
Some smaller or emerging markets may have fewer active listings, which can make seasonal patterns less reliable. Check the active_listings_count field in each monthly data point. Markets with consistently fewer than 20 active listings may show more volatile patterns. Consider using broader geographic areas or longer time windows for more stable analysis.
Stay ahead of the curve
Join our newsletter for exclusive insights and updates. No spam ever.