1
DSCR (Debt Service Coverage Ratio) lenders need third-party market data to validate short-term rental income projections. Unlike traditional mortgages that rely on W-2 income, DSCR loans qualify based on the property's ability to service its own debt.
Formula
Minimum
Haircut
How DSCR is calculated:
1. Start with gross STR income (use P50 median as base case)
2. Apply 20% reduction for conservatism: Adjusted Income = Gross x 0.80
3. Subtract operating expenses (insurance, tax, HOA, maintenance, utilities)
4. Result is Net Operating Income (NOI)
5. DSCR = NOI / Annual Debt Service (mortgage principal + interest)
2
Call GET /calculator/estimate with the subject property address and specifications. The response includes revenue at all percentiles (avg, p25, p50, p75, p90). Use P50 (median) as the base case and P25 as the stress case for conservative underwriting.
Python
import requests
response = requests.get(
"https://api.airroi.com/calculator/estimate",
headers={"X-API-KEY": "YOUR_API_KEY"},
params={
"address": "742 Evergreen Terrace, Nashville, TN 37203",
"bedrooms": 3,
"baths": 2,
"guests": 6,
"currency": "usd",
},
)
data = response.json()
percentiles = data["percentiles"]["revenue"]
print(f"Average Revenue: ${percentiles['avg']:,.0f}")
print(f"P25 (Stress): ${percentiles['p25']:,.0f}")
print(f"P50 (Base Case): ${percentiles['p50']:,.0f}")
print(f"P75 (Upside): ${percentiles['p75']:,.0f}")
print(f"P90 (Top Perf.): ${percentiles['p90']:,.0f}")3
The /calculator/estimate response includes up to 25 comparable listings. Extract their TTM revenue, occupancy, and ADR to verify that the estimate is grounded in real comparable performance. This gives lenders confidence that the projection is data-driven, not speculative.
Python
# The /calculator/estimate response includes comparable listings
comps = data.get("comparable_listings", [])
print(f"Comparable Listings Used: {len(comps)}")
print(f"{'ID':<12} {'Revenue':>10} {'Occ':>6} {'ADR':>8} {'Dist':>6}")
print("-" * 48)
for comp in comps[:10]:
print(
f"{comp['listing_id']:<12} "
f"${comp['revenue']:>8,.0f} "
f"{comp['occupancy']:>5.0%} "
f"${comp['average_daily_rate']:>6,.0f} "
f"{comp.get('distance_miles', 0):>4.1f}mi"
)
# Calculate comp statistics
revenues = [c["revenue"] for c in comps]
print(f"\nComp Revenue Range: ${min(revenues):,.0f} - ${max(revenues):,.0f}")
print(f"Comp Revenue Median: ${sorted(revenues)[len(revenues)//2]:,.0f}")4
Call POST /markets/summary for the local market to contextualize the property estimate. This reveals whether the market is healthy (high occupancy, stable ADR), growing (increasing listings), or at risk of oversaturation. Lenders want to see that the subject property operates in a viable market.
Python
import requests
response = requests.post(
"https://api.airroi.com/markets/summary",
headers={
"X-API-KEY": "YOUR_API_KEY",
"Content-Type": "application/json",
},
json={
"market": {
"country": "us",
"region": "tennessee",
"locality": "nashville",
},
"currency": "usd",
},
)
market = response.json()
print(f"Market: Nashville, TN")
print(f"Active Listings: {market['active_listings_count']:,}")
print(f"Avg Occupancy: {market['occupancy']:.0%}")
print(f"Avg ADR: ${market['average_daily_rate']:,.0f}")
print(f"Avg Revenue: ${market['revenue']:,.0f}")
print(f"Avg RevPAR: ${market['revpar']:,.0f}")
# Market health indicators
print(f"\nMarket Health Indicators:")
print(f" Occupancy {'above' if market['occupancy'] > 0.55 else 'below'} "
f"55% threshold: {'Healthy' if market['occupancy'] > 0.55 else 'Caution'}")5
Now assemble the DSCR calculation. Start with the P50 gross revenue, apply the 20% haircut, subtract operating expenses to get NOI, then divide by annual debt service.
| Line Item | Amount |
|---|---|
| Gross STR Revenue (P50) | $85,000 |
| 20% Haircut | -$17,000 |
| Adjusted Income | $68,000 |
| Operating Expenses | -$20,000 |
| Net Operating Income (NOI) | $48,000 |
| Annual Debt Service ($450K at 7.25%, 30yr) | $36,848 |
| DSCR | 1.30 |
Python
# DSCR Calculation
# Industry standard: 20% reduction to gross STR income
gross_str_revenue = percentiles["p50"] # Base case: $85,000
haircut = 0.80 # 20% reduction
adjusted_income = gross_str_revenue * haircut
# Operating expenses
expenses = {
"Insurance": 2400,
"Property Tax": 6000,
"HOA": 3600,
"Maintenance": 3000,
"Utilities": 3600,
"Platform Fees": 1400, # ~2% of adjusted income
}
total_expenses = sum(expenses.values())
# Net Operating Income
noi = adjusted_income - total_expenses
# Debt service (annual mortgage P&I)
loan_amount = 450000
interest_rate = 0.0725
loan_term_months = 360
monthly_payment = (
loan_amount
* (interest_rate / 12)
* (1 + interest_rate / 12) ** loan_term_months
/ ((1 + interest_rate / 12) ** loan_term_months - 1)
)
annual_debt_service = monthly_payment * 12
# DSCR
dscr = noi / annual_debt_service
print(f"Gross STR Revenue (P50): ${gross_str_revenue:,.0f}")
print(f"20% Haircut Applied: ${gross_str_revenue - adjusted_income:,.0f}")
print(f"Adjusted Income: ${adjusted_income:,.0f}")
print(f"Operating Expenses: ${total_expenses:,.0f}")
print(f"Net Operating Income: ${noi:,.0f}")
print(f"Annual Debt Service: ${annual_debt_service:,.0f}")
print(f"DSCR: {dscr:.2f}")
print(f"Meets 1.25 Threshold: {'Yes' if dscr >= 1.25 else 'No'}")6
Show how DSCR changes across revenue scenarios (P25, P50, P75) and interest rates. This helps lenders understand the risk profile and identify the break-even point where the property no longer meets the 1.25x DSCR threshold.
| Rate | P25 ($62K) | P50 ($85K) | P75 ($102K) |
|---|---|---|---|
| 6.50% | 0.86 | 1.36 | 1.73 |
| 7.00% | 0.83 | 1.31 | 1.67 |
| 7.25% | 0.81 | 1.30 | 1.64 |
| 7.50% | 0.79 | 1.28 | 1.62 |
| 8.00% | 0.76 | 1.24 | 1.56 |
Key Findings:
P25 (stress case) fails at all rates tested — the property cannot service debt at bottom-quartile performance.
P50 (base case) passes at rates up to ~7.75% before breaking the 1.25x threshold.
P75 (upside) comfortably passes at all rates tested.
Python
import itertools
# Revenue scenarios from API percentiles
revenue_scenarios = {
"P25 (Stress)": percentiles["p25"], # $62,000
"P50 (Base)": percentiles["p50"], # $85,000
"P75 (Upside)": percentiles["p75"], # $102,000
}
# Interest rate scenarios
rate_scenarios = [0.065, 0.070, 0.0725, 0.075, 0.080]
# Fixed assumptions
loan_amount = 450000
loan_term = 360
total_expenses = 20000
haircut = 0.80
print(f"{'Scenario':<16} {'Rate':>6} {'Revenue':>10} {'NOI':>10} "
f"{'Debt':>10} {'DSCR':>6} {'Pass':>5}")
print("-" * 70)
for rev_name, revenue in revenue_scenarios.items():
for rate in rate_scenarios:
adjusted = revenue * haircut
noi = adjusted - total_expenses
monthly = (
loan_amount * (rate / 12)
* (1 + rate / 12) ** loan_term
/ ((1 + rate / 12) ** loan_term - 1)
)
debt = monthly * 12
dscr = noi / debt
print(
f"{rev_name:<16} {rate:>5.1%} ${revenue:>8,.0f} "
f"${noi:>8,.0f} ${debt:>8,.0f} {dscr:>5.2f} "
f"{'Yes' if dscr >= 1.25 else 'NO':>5}"
)
print()
# Identify break-even
for rev_name, revenue in revenue_scenarios.items():
adjusted = revenue * haircut
noi = adjusted - total_expenses
for rate in [r / 1000 for r in range(50, 100)]:
monthly = (
loan_amount * (rate / 12)
* (1 + rate / 12) ** loan_term
/ ((1 + rate / 12) ** loan_term - 1)
)
if noi / (monthly * 12) < 1.25:
print(f"{rev_name} breaks 1.25 DSCR at {rate:.1%} rate")
break7
Assemble all data points into a lender-ready market rent analysis. This document should include: subject property details, revenue projection with source citation, comparable properties table, market health indicators, DSCR calculation, and risk factors.
Address, specs, location
P25/P50/P75/P90 with source
10-25 comp listings with metrics
Occupancy, ADR, supply trends
NOI / Debt Service with breakdown
Regulatory, seasonal, market risks
Python
def build_market_rent_analysis(property_data, estimate, market, comps):
"""Generate a lender-ready market rent analysis document."""
report = []
report.append("=" * 60)
report.append("MARKET RENT ANALYSIS - SHORT-TERM RENTAL")
report.append("=" * 60)
# Section 1: Subject Property
report.append("\n1. SUBJECT PROPERTY")
report.append(f" Address: {property_data['address']}")
report.append(f" Bedrooms: {property_data['bedrooms']}")
report.append(f" Bathrooms: {property_data['baths']}")
report.append(f" Max Guests: {property_data['guests']}")
# Section 2: Revenue Projection
p = estimate["percentiles"]["revenue"]
report.append("\n2. REVENUE PROJECTION")
report.append(f" Source: AirROI Enterprise API (airroi.com)")
report.append(f" Date: {datetime.now().strftime('%Y-%m-%d')}")
report.append(f" P25 (Conservative): ${p['p25']:,.0f}")
report.append(f" P50 (Median): ${p['p50']:,.0f}")
report.append(f" P75 (Above Avg): ${p['p75']:,.0f}")
report.append(f" P90 (Top Perf): ${p['p90']:,.0f}")
# Section 3: Comparable Properties
report.append("\n3. COMPARABLE PROPERTIES")
report.append(f" {'ID':<12} {'Rev':>10} {'Occ':>6} {'ADR':>8}")
for c in comps[:10]:
report.append(
f" {c['listing_id']:<12} "
f"${c['revenue']:>8,.0f} "
f"{c['occupancy']:>5.0%} "
f"${c['average_daily_rate']:>6,.0f}"
)
# Section 4: Market Health
report.append("\n4. MARKET HEALTH INDICATORS")
report.append(f" Market: {market.get('market_name', 'N/A')}")
report.append(f" Active Listings: {market['active_listings_count']:,}")
report.append(f" Avg Occupancy: {market['occupancy']:.0%}")
report.append(f" Avg ADR: ${market['average_daily_rate']:,.0f}")
report.append(f" Avg Revenue: ${market['revenue']:,.0f}")
# Section 5: DSCR Calculation
report.append("\n5. DSCR CALCULATION")
report.append(f" Gross Revenue (P50): ${p['p50']:,.0f}")
report.append(f" 20% Haircut: ${p['p50'] * 0.20:,.0f}")
report.append(f" Adjusted Income: ${p['p50'] * 0.80:,.0f}")
# Section 6: Risk Factors
report.append("\n6. RISK FACTORS")
report.append(" - STR regulatory changes in municipality")
report.append(" - Seasonal demand fluctuation")
report.append(" - Market supply growth / saturation")
report.append(" - Platform policy changes")
report.append("\n" + "=" * 60)
report.append("Data sourced from AirROI Enterprise API (airroi.com)")
report.append("Tracking 20M+ properties across 190+ countries")
report.append("=" * 60)
return "\n".join(report)
# Generate the report
report = build_market_rent_analysis(
property_data={"address": "742 Evergreen Terrace, Nashville, TN",
"bedrooms": 3, "baths": 2, "guests": 6},
estimate=data,
market=market,
comps=comps,
)
print(report)Keep exploring the AirROI API with these related tutorials.
Yes. Many DSCR and non-QM lenders accept third-party market rent analyses from platforms like AirROI as supporting documentation for STR income projections. The key is providing transparent data sources, comparable properties, and clear methodology. Always confirm specific documentation requirements with the lending institution.
Cite as: "Revenue projections sourced from AirROI Enterprise API (airroi.com), a third-party STR analytics platform tracking 20M+ properties across 190+ countries. Data retrieved [date]." Include the specific endpoint used, percentile level cited, and number of comparable properties analyzed.
Refresh data at each milestone: initial screening, formal application, and closing. If more than 30 days have passed since the last pull, refresh to capture any market shifts. AirROI data is updated on a rolling basis, so each API call returns the most current available data.
Yes. Use a loop to call /calculator/estimate for each property address, then call /markets/summary for each unique market. The API supports up to 10 requests per second, so a portfolio of 50 properties can be screened in under a minute.
Market rent for STR is the projected revenue based on comparable properties in the area, which is what the AirROI API provides. Actual rent is what a specific listing has earned historically. For underwriting, market rent (specifically the P50 median with a 20% haircut) is the conservative standard because it removes operator-specific performance variation.
The AirROI API returns occupancy rates that already reflect actual vacancy. When using P50 occupancy, you are using the median performance including real vacancy. The 20% haircut applied to gross revenue provides additional conservatism beyond the embedded vacancy. Do not double-count vacancy by both using P25 occupancy and applying a haircut.
STR operating expenses typically range from 25-40% of gross revenue depending on the management model. Self-managed properties run 20-25% (lower management fees, owner handles some tasks). Professionally managed properties run 35-40% (full management fee of 20-25%, plus all service costs). Key line items: property management, cleaning, maintenance, insurance, property tax, utilities, platform fees, and supplies.
Always research local STR regulations before underwriting. Check if the municipality requires permits/licenses, has occupancy limits, enforces minimum stay requirements, or has announced regulatory changes. Factor regulatory risk into your sensitivity analysis by modeling scenarios where STR income is partially or fully restricted. The market data from AirROI reflects current active listings, which indicates the existing regulatory environment.
Stay ahead of the curve
Join our newsletter for exclusive insights and updates. No spam ever.