From solo investors screening ZIP codes in a spreadsheet to startups building property apps, here are six real-world use cases with complete working code. Each example shows which endpoints to call, how to combine them, and what the output looks like.
Investment Property Screener
Endpoints: /property-estimate · /rental-yield · /market-stats
The most common use case: you have a list of target ZIP codes and want to rank them by investment attractiveness before spending time on deeper due diligence. This script pulls all three data points per ZIP and exports a ranked CSV.
What it does
- Queries /property-estimate for cap rate, cash flow, and cash-on-cash return
- Cross-checks with /market-stats for market temperature and inventory tightness
- Pulls /rental-yield for the raw HUD rent figure
- Ranks by a composite score: cap rate (40%), cash-on-cash return (40%), market temperature (20%)
- Exports to screener_results.csv
Python — screener.py
"""
Investment property screener — ranks ZIP codes by investment attractiveness.
Requires: pip install requests pandas
Usage: RAPIDAPI_KEY=your_key python screener.py
"""
import requests, os, time, csv, sys
from dataclasses import dataclass, asdict
API_KEY = os.environ.get("RAPIDAPI_KEY")
if not API_KEY:
sys.exit("Set RAPIDAPI_KEY environment variable first.")
HOST = "real-estate-market-data.p.rapidapi.com"
BASE = f"https://{HOST}"
HEADERS = {"X-RapidAPI-Key": API_KEY, "X-RapidAPI-Host": HOST}
# ── Your target ZIP codes ─────────────────────────────────────────────────────
TARGET_ZIPS = [
"30301", # Atlanta, GA
"37201", # Nashville, TN
"35203", # Birmingham, AL
"28201", # Charlotte, NC
"45201", # Cincinnati, OH
"43201", # Columbus, OH
"32801", # Orlando, FL
"70112", # New Orleans, LA
"55401", # Minneapolis, MN
"64101", # Kansas City, MO
]
TEMP_SCORE = {
"Hot 🔥": 5,
"Warm": 4,
"Balanced": 3,
"Cool": 2,
"Buyer's Market": 1
}
def get(endpoint, params):
"""GET with simple retry on 429."""
for attempt in range(3):
r = requests.get(f"{BASE}/{endpoint}", params=params,
headers=HEADERS, timeout=10)
if r.status_code == 429:
time.sleep(2 ** attempt)
continue
r.raise_for_status()
return r.json()
raise RuntimeError(f"Failed after retries: {endpoint} {params}")
rows = []
for zip_code in TARGET_ZIPS:
print(f" Fetching {zip_code}...", end=" ", flush=True)
try:
est = get("property-estimate", {"zip_code": zip_code})
market = get("market-stats", {"zip_code": zip_code})
rent = get("rental-yield", {"zip_code": zip_code})
m = est["investment_metrics"]
# Composite score (normalised to 0–100)
cap_score = min(m["cap_rate_pct"] / 8 * 40, 40) # 8% cap = full score
coc_score = min(m["cash_on_cash_return_pct"] / 10 * 40, 40) # 10% CoC = full score
temp_score = TEMP_SCORE.get(market["market_temperature"], 3) / 5 * 20
composite = round(cap_score + coc_score + temp_score, 1)
rows.append({
"zip_code": zip_code,
"price": est["estimated_value"],
"monthly_rent": m["estimated_monthly_rent"],
"gross_yield_pct": est["gross_yield_pct"],
"cap_rate_pct": m["cap_rate_pct"],
"cash_flow_monthly": m["monthly_cash_flow"],
"coc_return_pct": m["cash_on_cash_return_pct"],
"investment_rating": m["investment_rating"],
"down_payment": m["down_payment_20pct"],
"market_temp": market["market_temperature"],
"dom": market["median_days_on_market"],
"yoy_change_pct": round(market["yoy_price_change"] * 100, 1),
"inventory_months": market["inventory_months"],
"composite_score": composite,
"data_source": est["data_source"],
})
print(f"cap {m['cap_rate_pct']}% coc {m['cash_on_cash_return_pct']}% score {composite}")
except Exception as e:
print(f"ERROR: {e}")
time.sleep(0.5) # stay within rate limits
# Sort by composite score descending
rows.sort(key=lambda r: r["composite_score"], reverse=True)
# Print ranked table
print()
print(f"{'Rank':<5} {'ZIP':<8} {'Price':>10} {'Cap%':>7} {'CoC%':>7} "
f"{'CF/mo':>8} {'Score':>6} {'Rating'}")
print("─" * 68)
for i, r in enumerate(rows, 1):
print(
f"{i:<5} {r['zip_code']:<8} ${r['price']:>9,} "
f"{r['cap_rate_pct']:>6.1f}% {r['coc_return_pct']:>6.1f}% "
f"${r['cash_flow_monthly']:>+7,} {r['composite_score']:>6.1f} "
f"{r['investment_rating']}"
)
# Export CSV
with open("screener_results.csv", "w", newline="") as f:
writer = csv.DictWriter(f, fieldnames=rows[0].keys())
writer.writeheader()
writer.writerows(rows)
print(f"\nExported {len(rows)} results to screener_results.csv")
Expected console output
Fetching 30301... cap 4.89% coc 11.02% score 78.4
Fetching 37201... cap 4.12% coc 7.84% score 63.1
...
Rank ZIP Price Cap% CoC% CF/mo Score Rating
─────────────────────────────────────────────────────────────────────
1 30301 $310,000 4.9% 11.0% +$284 78.4 Excellent
2 35203 $245,000 5.8% 9.1% +$188 74.2 Excellent
3 43201 $295,000 4.3% 7.2% +$112 62.8 Good
...
Exported 10 results to screener_results.csv
Pro tip: widen your search
Run this script against a list of 50–100 ZIP codes for cities you're targeting. On the free tier (100 req/day) you can screen ~33 ZIP codes/day (3 calls each). Upgrade to Pro for 1,000 req/day to screen entire metro areas in one run.
Relocation Affordability Comparison
Endpoint: /affordability
Planning a move? This script compares housing affordability across your shortlist of cities, personalised to your actual income. It shows how many years it would take to save a down payment and what your debt-to-income ratio would be at each location.
Python — relocate.py
"""
Relocation affordability comparison.
Usage: RAPIDAPI_KEY=your_key python relocate.py
"""
import requests, os
API_KEY = os.environ["RAPIDAPI_KEY"]
HEADERS = {
"X-RapidAPI-Key": API_KEY,
"X-RapidAPI-Host": "real-estate-market-data.p.rapidapi.com"
}
YOUR_INCOME = 95_000 # ← change to your household income
METROS = [
"Austin",
"Denver",
"Nashville",
"Raleigh",
"Phoenix",
"Charlotte",
"Tampa",
"Columbus",
]
results = []
for metro in METROS:
resp = requests.get(
"https://real-estate-market-data.p.rapidapi.com/affordability",
params={"metro": metro, "income": YOUR_INCOME},
headers=HEADERS,
timeout=10
)
d = resp.json()
p = d.get("personalized", {})
results.append({
"metro": d["metro"],
"index": d["affordability_index"],
"label": d["interpretation"],
"home_price": d["median_home_price"],
"pti": d["price_to_income_ratio"],
"down_payment": d["down_payment_20pct"],
"yrs_to_save": d["years_to_save_down_payment"],
"monthly_pmt": d["monthly_payment_estimate"],
"dti": p.get("debt_to_income_pct", 0),
"verdict": p.get("affordability_verdict", "N/A"),
"max_price": p.get("max_home_price_28dti", 0),
})
# Sort by affordability index descending (most affordable first)
results.sort(key=lambda r: r["index"], reverse=True)
print(f"\nAffordability comparison for ${YOUR_INCOME:,} household income\n")
print(f"{'Metro':<12} {'Index':>6} {'Home Price':>11} {'DTI':>5} {'Verdict':<12} {'Yrs to Down':>11}")
print("─" * 66)
for r in results:
print(
f"{r['metro']:<12} {r['index']:>6.0f} ${r['home_price']:>10,} "
f"{r['dti']:>4.0f}% {r['verdict']:<12} {r['yrs_to_save']:>10.1f}y"
)
# Highlight the most affordable choice
best = results[0]
print(f"\nBest value for your income: {best['metro']}")
print(f" Affordability index: {best['index']:.0f} ({best['label']})")
print(f" Monthly payment: ${best['monthly_pmt']:,}")
print(f" Max home at 28% DTI: ${best['max_price']:,}")
print(f" Down payment needed: ${best['down_payment']:,} "
f"(~{best['yrs_to_save']:.1f} years to save at 15% savings rate)")
Expected output
Affordability comparison for $95,000 household income
Metro Index Home Price DTI Verdict Yrs to Down
──────────────────────────────────────────────────────────────────
Columbus 124 $305,000 39% Stretched 4.8y
Tampa 108 $398,000 49% Difficult 8.4y
Charlotte 102 $425,000 52% Difficult 9.0y
Nashville 92 $478,000 59% Difficult 10.1y
Phoenix 88 $490,000 60% Difficult 10.4y
Raleigh 85 $510,000 63% Difficult 10.8y
Denver 74 $598,000 74% Difficult 12.7y
Austin 71 $685,000 84% Difficult 14.5y
Best value for your income: Columbus
Affordability index: 124 (Affordable)
Monthly payment: $1,625
Max home at 28% DTI: $418,285
Down payment needed: $61,000 (~4.8 years to save at 15% savings rate)
Automated Weekly Market Report
Endpoints: /market-stats · /price-history
Set this up as a cron job to send yourself (or your clients) a formatted market summary every Monday morning. It pulls the latest stats for your target markets and emails an HTML report using Python's standard SMTP library.
Python — weekly_report.py (schedule with cron: 0 8 * * 1)
"""
Weekly real estate market report.
Schedule: cron 0 8 * * 1 (every Monday at 8am)
Requires: pip install requests
Env vars: RAPIDAPI_KEY, SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS, REPORT_TO
"""
import requests, os, smtplib, time
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from datetime import datetime
API_KEY = os.environ["RAPIDAPI_KEY"]
HEADERS = {
"X-RapidAPI-Key": API_KEY,
"X-RapidAPI-Host": "real-estate-market-data.p.rapidapi.com"
}
# ── Markets to monitor ────────────────────────────────────────────────────────
WATCHLIST = [
{"zip": "78701", "label": "Austin, TX (Downtown)"},
{"zip": "30301", "label": "Atlanta, GA"},
{"zip": "37201", "label": "Nashville, TN"},
{"zip": "85001", "label": "Phoenix, AZ"},
]
def fetch_market(zip_code):
r = requests.get(
"https://real-estate-market-data.p.rapidapi.com/market-stats",
params={"zip_code": zip_code},
headers=HEADERS, timeout=10
)
r.raise_for_status()
return r.json()
def trend_arrow(value):
return "▲" if value > 0 else ("▼" if value < 0 else "—")
def build_html_report(markets):
rows = ""
for m in markets:
d = m["data"]
yoy = d["yoy_price_change"] * 100
arrow = trend_arrow(yoy)
rows += f"""
{m['label']}
ZIP {m['zip']}
|
${d['median_sale_price']:,}
|
{d['median_days_on_market']} days
|
{arrow} {abs(yoy):.1f}%
|
{d['market_temperature']}
|
"""
return f"""
ZipMarketData Weekly Report
{datetime.now().strftime("%A, %B %-d %Y")}
| MARKET |
MED. PRICE |
DOM |
YoY |
TEMP |
{rows}
Data sourced from Redfin MLS, updated daily. Not financial advice.
"""
def send_report(html_body):
msg = MIMEMultipart("alternative")
msg["Subject"] = f"Weekly Market Report — {datetime.now().strftime('%b %-d')}"
msg["From"] = os.environ["SMTP_USER"]
msg["To"] = os.environ["REPORT_TO"]
msg.attach(MIMEText(html_body, "html"))
with smtplib.SMTP_SSL(os.environ["SMTP_HOST"], int(os.environ["SMTP_PORT"])) as server:
server.login(os.environ["SMTP_USER"], os.environ["SMTP_PASS"])
server.send_message(msg)
# ── Main ──────────────────────────────────────────────────────────────────────
markets = []
for item in WATCHLIST:
print(f"Fetching {item['label']}...")
item["data"] = fetch_market(item["zip"])
markets.append(item)
time.sleep(0.5)
html = build_html_report(markets)
send_report(html)
print(f"Report sent to {os.environ['REPORT_TO']}")
Deploy as a cron job
On a Linux server: crontab -e then add 0 8 * * 1 cd /path/to/scripts && RAPIDAPI_KEY=xxx python weekly_report.py. This uses only 4 API calls per run — well within the free tier's daily limit.
Rental Portfolio Yield Tracker
Endpoints: /rental-yield · /market-stats
If you own multiple rental properties, tracking how the market yield in each ZIP compares to your actual rents helps you spot where you're under-market (time to raise rents) or where appreciation has eroded your yield (time to sell).
Python — portfolio_tracker.py
"""
Rental portfolio yield tracker.
Define your properties below and run to see market yield vs your actual yield.
"""
import requests, os, time
API_KEY = os.environ["RAPIDAPI_KEY"]
HEADERS = {
"X-RapidAPI-Key": API_KEY,
"X-RapidAPI-Host": "real-estate-market-data.p.rapidapi.com"
}
# ── Your portfolio ─────────────────────────────────────────────────────────────
PORTFOLIO = [
{"address": "123 Main St, Austin TX", "zip": "78701", "bedrooms": 2,
"purchase_price": 520000, "actual_monthly_rent": 2400},
{"address": "456 Oak Ave, Nashville TN", "zip": "37201", "bedrooms": 3,
"purchase_price": 340000, "actual_monthly_rent": 1950},
{"address": "789 Pine Rd, Atlanta GA", "zip": "30301", "bedrooms": 2,
"purchase_price": 275000, "actual_monthly_rent": 1700},
]
def fetch_yield(zip_code, bedrooms):
r = requests.get(
"https://real-estate-market-data.p.rapidapi.com/rental-yield",
params={"zip_code": zip_code, "bedrooms": bedrooms},
headers=HEADERS, timeout=10
)
r.raise_for_status()
return r.json()
print(f"\n{'Property':<35} {'Purchase':>10} {'Mkt Value':>10} "
f"{'Your Rent':>10} {'Mkt Rent':>9} {'Your Yield':>11} {'Mkt Yield':>10} {'Signal'}")
print("─" * 110)
for prop in PORTFOLIO:
d = fetch_yield(prop["zip"], prop["bedrooms"])
market_rent = d["estimated_monthly_rent"]
market_val = d["median_sale_price"]
# Your actual yield based on purchase price
your_yield = round(prop["actual_monthly_rent"] * 12 / prop["purchase_price"] * 100, 2)
# Market yield at current prices
market_yld = d["gross_yield_pct"]
# Under/over market rent
rent_delta = prop["actual_monthly_rent"] - market_rent
rent_signal = (
"RAISE RENT" if rent_delta < -100 else
"AT MARKET" if abs(rent_delta) <= 100 else
"ABOVE MKT"
)
print(
f"{prop['address']:<35} ${prop['purchase_price']:>9,} ${market_val:>9,} "
f"${prop['actual_monthly_rent']:>9,} ${market_rent:>8,} "
f"{your_yield:>10.1f}% {market_yld:>9.1f}% {rent_signal}"
)
time.sleep(0.5)
print("\nGross yields only — deduct ~35-45% operating expenses for net yield.")
Expected output
Property Purchase Mkt Value Your Rent Mkt Rent Your Yield Mkt Yield Signal
──────────────────────────────────────────────────────────────────────────────────────────────────────────────
123 Main St, Austin TX $520,000 $685,000 $2,400 $2,180 5.5% 3.8% ABOVE MKT
456 Oak Ave, Nashville TN $340,000 $412,000 $1,950 $2,180 6.9% 6.3% AT MARKET
789 Pine Rd, Atlanta GA $275,000 $310,000 $1,700 $2,100 7.4% 8.1% RAISE RENT
Embeddable ZIP Code Widget
Endpoint: /market-stats (via server-side proxy)
Add a live market stats lookup to any webpage. This React component calls a thin Next.js API route that forwards requests to ZipMarketData — keeping your RapidAPI key server-side and out of the browser.
Never expose your API key in the browser
Always proxy through a server-side route. The examples below show a Next.js API route that keeps the key secret. Direct browser calls would expose your key to anyone viewing your page source.
Next.js API route — pages/api/market-stats.js
// Server-side proxy — keeps RAPIDAPI_KEY secret
export default async function handler(req, res) {
const { zip_code } = req.query;
if (!/^\d{5}$/.test(zip_code)) {
return res.status(400).json({ error: "Invalid ZIP code" });
}
const url = new URL(
"https://real-estate-market-data.p.rapidapi.com/market-stats"
);
url.searchParams.set("zip_code", zip_code);
const response = await fetch(url, {
headers: {
"X-RapidAPI-Key": process.env.RAPIDAPI_KEY, // server-side env only
"X-RapidAPI-Host": "real-estate-market-data.p.rapidapi.com"
}
});
const data = await response.json();
// Cache for 24 hours (data updates daily)
res.setHeader("Cache-Control", "s-maxage=86400, stale-while-revalidate");
res.status(response.status).json(data);
}
React component — components/MarketStatsWidget.jsx
import { useState } from "react";
const TEMP_COLOR = {
"Hot 🔥": "#c83800",
"Warm": "#e07a00",
"Balanced": "#15602f",
"Cool": "#1a3a7a",
"Buyer's Market": "#1a3a7a"
};
function Stat({ label, value }) {
return (
<div style={{ textAlign: "center", padding: "12px 16px" }}>
<div style={{ fontSize: 11, fontFamily: "monospace", color: "#82796f",
textTransform: "uppercase", letterSpacing: 1, marginBottom: 4 }}>
{label}
</div>
<div style={{ fontSize: 20, fontWeight: 700, fontFamily: "serif" }}>
{value}
</div>
</div>
);
}
export default function MarketStatsWidget() {
const [zip, setZip] = useState("");
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
async function lookup() {
if (!/^\d{5}$/.test(zip)) {
setError("Enter a valid 5-digit ZIP code.");
return;
}
setLoading(true);
setError(null);
try {
const res = await fetch(`/api/market-stats?zip_code=${zip}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
setData(await res.json());
} catch (e) {
setError("Could not fetch data — please try again.");
} finally {
setLoading(false);
}
}
return (
<div style={{ fontFamily: "sans-serif", maxWidth: 480,
border: "1px solid #e0d9cf", borderRadius: 12, overflow: "hidden",
background: "#fff" }}>
{/* Search bar */}
<div style={{ padding: "16px 20px", background: "#111009",
display: "flex", gap: 10 }}>
<input
value={zip}
onChange={e => setZip(e.target.value)}
onKeyDown={e => e.key === "Enter" && lookup()}
placeholder="Enter ZIP code"
maxLength={5}
style={{ flex: 1, padding: "8px 14px", borderRadius: 7,
border: "1px solid #3a3832", background: "#1e1c18",
color: "#e2dfd8", fontFamily: "monospace", fontSize: 14 }}
/>
<button
onClick={lookup}
disabled={loading}
style={{ padding: "8px 18px", borderRadius: 7, border: "none",
background: "#c83800", color: "#fff", cursor: "pointer",
fontFamily: "monospace", fontSize: 12 }}>
{loading ? "..." : "Look up →"}
</button>
</div>
{/* Error */}
{error && (
<div style={{ padding: "12px 20px", color: "#c83800",
fontFamily: "monospace", fontSize: 12 }}>{error}</div>
)}
{/* Results */}
{data && (
<>
<div style={{ padding: "14px 20px", borderBottom: "1px solid #e0d9cf",
display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<span style={{ fontFamily: "monospace", fontSize: 11, color: "#82796f" }}>
ZIP {data.zip_code} · {data.data_date}
</span>
<span style={{ fontSize: 11, fontFamily: "monospace", fontWeight: 600,
color: TEMP_COLOR[data.market_temperature] || "#111009" }}>
{data.market_temperature}
</span>
</div>
<div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)",
borderBottom: "1px solid #e0d9cf" }}>
<Stat label="Median Price"
value={`$${(data.median_sale_price / 1000).toFixed(0)}k`} />
<Stat label="Days on Mkt" value={data.median_days_on_market} />
<Stat label="YoY Change"
value={`${(data.yoy_price_change * 100).toFixed(1)}%`} />
</div>
<div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)" }}>
<Stat label="Inventory" value={`${data.inventory_months}mo`} />
<Stat label="Listings" value={data.active_listings} />
<Stat label="Reduced"
value={`${(data.price_reduced_pct * 100).toFixed(0)}%`} />
</div>
</>
)}
</div>
);
}
Price Trend Alert System
Endpoints: /market-stats · /price-history
Get notified the moment a target market crosses your investment threshold — for example, when YoY appreciation falls below 3% (potential entry point) or days on market exceeds 45 (cooling market). This script stores the last known state and sends an alert only when something changes.
Python — trend_alerts.py (run daily via cron)
"""
Price trend alert system. Sends notifications when market conditions change.
Run daily: 0 9 * * * python trend_alerts.py
State is stored in alerts_state.json to avoid duplicate notifications.
"""
import requests, os, json, smtplib, time
from email.mime.text import MIMEText
from pathlib import Path
from datetime import datetime
API_KEY = os.environ["RAPIDAPI_KEY"]
HEADERS = {
"X-RapidAPI-Key": API_KEY,
"X-RapidAPI-Host": "real-estate-market-data.p.rapidapi.com"
}
STATE_FILE = Path("alerts_state.json")
# ── Alert thresholds ─────────────────────────────────────────────────────────
WATCHLIST = [
{
"zip": "78701",
"label": "Austin TX Downtown",
"alerts": [
{"field": "yoy_price_change", "op": "<", "threshold": 0.03,
"message": "YoY appreciation dropped below 3% — possible entry signal"},
{"field": "median_days_on_market", "op": ">", "threshold": 45,
"message": "DOM exceeded 45 days — market cooling"},
{"field": "inventory_months", "op": ">", "threshold": 4.0,
"message": "Inventory exceeded 4 months — buyer's market forming"},
]
},
{
"zip": "30301",
"label": "Atlanta GA",
"alerts": [
{"field": "yoy_price_change", "op": ">", "threshold": 0.10,
"message": "YoY appreciation exceeded 10% — strong appreciation"},
{"field": "median_days_on_market", "op": "<", "threshold": 15,
"message": "DOM under 15 days — hot market, act fast"},
]
},
]
def check(value, op, threshold):
if op == "<": return value < threshold
if op == ">": return value > threshold
return False
def load_state():
if STATE_FILE.exists():
return json.loads(STATE_FILE.read_text())
return {}
def save_state(state):
STATE_FILE.write_text(json.dumps(state, indent=2))
def send_alert(subject, body):
msg = MIMEText(body)
msg["Subject"] = subject
msg["From"] = os.environ["SMTP_USER"]
msg["To"] = os.environ["ALERT_EMAIL"]
with smtplib.SMTP_SSL(os.environ["SMTP_HOST"], int(os.environ["SMTP_PORT"])) as s:
s.login(os.environ["SMTP_USER"], os.environ["SMTP_PASS"])
s.send_message(msg)
state = load_state()
alerts_sent = []
for market in WATCHLIST:
r = requests.get(
"https://real-estate-market-data.p.rapidapi.com/market-stats",
params={"zip_code": market["zip"]},
headers=HEADERS, timeout=10
)
data = r.json()
key = market["zip"]
for alert in market["alerts"]:
field = alert["field"]
value = data.get(field, 0)
triggered = check(value, alert["op"], alert["threshold"])
alert_key = f"{key}_{field}"
was_triggered = state.get(alert_key, False)
if triggered and not was_triggered:
# New trigger — send alert
msg = (
f"ALERT: {market['label']} (ZIP {market['zip']})\n"
f"Condition: {alert['message']}\n"
f"Current value: {field} = {value}\n"
f"Threshold: {alert['op']} {alert['threshold']}\n"
f"Checked: {datetime.now().strftime('%Y-%m-%d %H:%M')}\n"
)
alerts_sent.append(msg)
print(f" ALERT: {market['label']} — {alert['message']}")
state[alert_key] = triggered
time.sleep(0.5)
save_state(state)
if alerts_sent:
body = "\n\n---\n\n".join(alerts_sent)
send_alert(f"Market Alert — {len(alerts_sent)} condition(s) triggered", body)
print(f"Sent {len(alerts_sent)} alert(s).")
else:
print("No new alerts. State saved.")