Scan your roster every morning for breaking injury, role, and transaction news — the stuff that flips a start/sit call after projections are already set. If a player has no coverage yet, ask the API to go fetch it, then check back.
/api/v1/news/{player_id}?impact=injury&days=7 — recent headlines for a player, filterable by impact/api/v1/news/{player_id}/request — queue an on-demand scrape when a player has no news yetEach news item carries an impact tag — injury, transaction, role, or general — so you can surface only what changes a lineup decision. A player ID is "{Player Name}#{POS}", e.g. Malik Nabers#WR.
curl -H "x-api-key: YOUR_KEY" \
"https://api.gridirondata.com/api/v1/news/Malik%20Nabers%23WR?days=7"
{
"player_id": "Malik Nabers#WR",
"days": 7,
"count": 1,
"news": [
{
"headline": "Nabers (knee) full participant at Wednesday practice",
"impact": "injury",
"source": "espn-nfl",
"url": "https://www.espn.com/nfl/story/_/id/00000000",
"published_at": "2026-09-10T17:22:00Z"
}
]
}
Track a deep-bench player or a just-promoted backup the dataset hasn't picked up yet? A count: 0 response hands you a ready-made link to request one. POST to it and the API scrapes that player on demand — and adds them to the automated scraper so they're covered from then on.
{
"player_id": "Max Bredeson#RB",
"count": 0,
"news": [],
"message": "No news found for Max Bredeson#RB in the last 14 days.",
"request_scrape": {
"method": "POST",
"href": "https://api.gridirondata.com/api/v1/news/Max%20Bredeson%23RB/request"
}
}
curl -X POST -H "x-api-key: YOUR_KEY" \
"https://api.gridirondata.com/api/v1/news/Max%20Bredeson%23RB/request"
# 202 Accepted — poll GET /news/{player_id} again shortly after
import requests
API = "https://api.gridirondata.com/api/v1"
H = {"x-api-key": "YOUR_KEY"}
ROSTER = [
"Josh Allen#QB", "Bijan Robinson#RB", "Malik Nabers#WR",
"CeeDee Lamb#WR", "Trey McBride#TE", "Max Bredeson#RB",
]
# Impact tags that can actually change a start/sit call.
LINEUP_MOVING = {"injury", "role", "transaction"}
def player_news(pid, days=7):
r = requests.get(f"{API}/news/{requests.utils.quote(pid, safe='')}",
headers=H, params={"days": days})
return r.json()
alerts = []
for pid in ROSTER:
data = player_news(pid)
# No coverage yet? Fire off an on-demand scrape and move on.
if data.get("count", 0) == 0:
link = data.get("request_scrape")
if link:
requests.request(link["method"], link["href"], headers=H)
print(f"· requested scrape for {pid}")
continue
# Keep only the headlines that matter for a lineup.
for item in data["news"]:
if item["impact"] in LINEUP_MOVING:
alerts.append((pid, item))
print("\n=== LINEUP-MOVING NEWS ===")
for pid, item in sorted(alerts, key=lambda a: a[1]["published_at"], reverse=True):
name = pid.rsplit("#", 1)[0]
print(f"[{item['impact'].upper():11}] {name:22} {item['headline']}")
print(f" {item['url']}")
POST /news/{player_id}/request is added to the automated scraper, so your first lookup seeds coverage and every run after that stays fresh — no extra work on your end.Run it each morning with cron and post alerts to a Slack incoming webhook, or trigger it Sunday pre-kickoff. Because on-demand requests are asynchronous, a good pattern is: request scrapes on the first pass, then re-poll the request_scrape players a minute later to catch anything fresh.