Polling Best Practices
Polling Best Practices
The RotoWire API is a REST polling API — there are no webhooks or push events. This guide explains how to poll efficiently, avoid redundant work, and handle errors reliably.
Poll Intervals by Data Type
Different data types update at different frequencies. Polling too aggressively wastes quota and slows your app; polling too infrequently means stale data.
| Data Type | Off-Day | Game Day (Pre-Game) | Game Day (In-Game / Post-Game) |
|---|---|---|---|
| Injuries | Every 30–60 min | Every 10–15 min | Every 5 min |
| News | Every 30–60 min | Every 10 min | Every 5 min |
| Lineups | Every 60 min | Every 1 min (15–30 min before lineup lock) | Every 10 min |
| Depth Charts | Every 4–6 hrs | Every 60 min | Every 60 min |
| DFS Projections | Once daily | Once after lineup lock | Not applicable |
| Players / Rosters | Every 4–6 hrs | Every 4–6 hrs | Every 4–6 hrs |
Lineup lock windows: For NBA, poll every minute in the 15–30 minutes before lineup lock. For NFL, poll every minute immediately after inactives are announced (~90 minutes before kickoff).
Transactions are reported as player news and appear in the standard news and injury feeds — there is no separate transactions endpoint.
Delta-Pull with the hours Parameter
hours ParameterMost news and injury endpoints support an hours parameter. Use it to fetch only updates published within the last N hours — far more efficient than fetching the full dataset every poll.
# Fetch only updates from the last 30 minutes
curl "https://api.rotowire.com/Basketball/get-nba-news-updates.php?key=YOUR_API_KEY&hours=0.5"
# Fetch updates from the last 2 hours
curl "https://api.rotowire.com/Basketball/get-nba-injuries.php?key=YOUR_API_KEY&hours=2"Use
hours=48on your first poll of the day. RotoWire editors sometimes update or correct notes from the previous day. A shorter window will miss those edits — 48 hours ensures you capture them.
import requests, time
API_KEY = "YOUR_API_KEY"
POLL_INTERVAL_SECONDS = 300
while True:
resp = requests.get(
"https://api.rotowire.com/Basketball/get-nba-injuries.php",
params={"key": API_KEY, "hours": 0.1}
)
updates = resp.json().get("Updates", [])
for u in updates:
process_update(u)
time.sleep(POLL_INTERVAL_SECONDS)const API_KEY = 'YOUR_API_KEY';
const POLL_MS = 5 * 60 * 1000;
async function poll() {
const resp = await fetch(
`https://api.rotowire.com/Basketball/get-nba-injuries.php?key=${API_KEY}&hours=0.1`
);
const { Updates = [] } = await resp.json();
for (const update of Updates) processUpdate(update);
}
setInterval(poll, POLL_MS);
poll();Deduplication by Update ID
Every update has a unique Id. Deduplicate before processing to avoid handling the same update twice across polls.
seen_ids = set()
def process_updates(updates):
for update in updates:
if update["Id"] in seen_ids:
continue
seen_ids.add(update["Id"])
handle_update(update)const seenIds = new Set();
function processUpdates(updates) {
for (const update of updates) {
if (seenIds.has(update.Id)) continue;
seenIds.add(update.Id);
handleUpdate(update);
}
}Store seen IDs in a database — not just memory — so you don't reprocess on restart.
Error Handling & Backoff
import requests, time
def fetch_with_retry(url, params, max_retries=5):
delay = 5
for attempt in range(max_retries):
try:
resp = requests.get(url, params=params, timeout=10)
if resp.status_code == 200:
return resp.json()
elif resp.status_code in (429,) or resp.status_code >= 500:
print(f"HTTP {resp.status_code}. Retrying in {delay}s...")
else:
resp.raise_for_status()
except requests.exceptions.RequestException as e:
print(f"Request failed: {e}. Retrying in {delay}s...")
time.sleep(delay)
delay = min(delay * 2, 120)
raise Exception("Max retries exceeded")async function fetchWithRetry(url, maxRetries = 5) {
let delay = 5000;
for (let i = 0; i < maxRetries; i++) {
try {
const resp = await fetch(url);
if (resp.ok) return await resp.json();
if (resp.status === 429 || resp.status >= 500) {
console.warn(`HTTP ${resp.status} — retrying in ${delay}ms`);
} else throw new Error(`Non-retryable: ${resp.status}`);
} catch (e) {
if (i === maxRetries - 1) throw e;
}
await new Promise(r => setTimeout(r, delay));
delay = Math.min(delay * 2, 120000);
}
}Avoiding Redundant Calls
- Only poll sports and leagues relevant to your application
- Use
dateto scope requests where supported - Respect game schedules — pause or reduce frequency during off-seasons
- Cache large slow-changing payloads (depth charts, rosters) locally
Summary
- Use the
hoursparameter for delta-pulls to avoid fetching the full dataset on every poll - Use
hours=48on your first poll of the day to catch any prior-day edits - Deduplicate by
Idbefore processing and persist seen IDs across restarts - Poll lineups every minute during the lock window — 15–30 min before NBA lineup lock, immediately after NFL inactives are announced
- Transactions appear in the standard news and injury feeds — there is no separate transactions endpoint
- Implement exponential backoff on 429 and 5xx responses with a 10s request timeout
- Cache large slow-changing payloads (depth charts, rosters) locally and refresh infrequently
- Pause or reduce polling frequency during off-seasons for inactive leagues
- Never expose your API key in client-side or public code
Updated 3 months ago
