Stop drafting off someone else's cheat sheet. In ~30 lines of Python you'll rank every player by Value Over Replacement Player (VORP) using live 2026 PPR projections — tuned to your exact league.
/api/v1/players?position={POS} — every player at a position, with seasons.2026.season_projectionsEvery player profile carries season-long projections under seasons.2026.season_projections. MISC_FPTS is the projected total in PPR (the default), with MISC_FPTS_HALF and MISC_FPTS_STD for half-PPR and standard leagues (for K and DST the base field is FPTS, with matching _HALF / _STD). Pick the one that matches your league.
curl -H "x-api-key: YOUR_KEY" \
"https://api.gridirondata.com/api/v1/players?position=RB"
Each player looks like this (trimmed):
{
"player_id": "Bijan Robinson#RB",
"player_name": "Bijan Robinson",
"position": "RB",
"seasons": {
"2026": {
"team": "ATL",
"season_projections": {
"MISC_FPTS": 373.3, // PPR (default)
"MISC_FPTS_HALF": 344.9, // half-PPR
"MISC_FPTS_STD": 316.4, // standard
"RUSHING_YDS": 1380, ...
}
}
}
}
Prefer the API do the picking? GET /api/v1/projections/{week}?scoring=half returns
weekly projections already resolved to your scoring (ppr default, or half / standard).
A 280-point RB and a 280-point QB are not equal. There are far more startable QBs than RBs, so the RB is scarcer — and scarcity is what wins drafts. VORP measures each player against the last startable player at their position:
VORP = player_points − replacement_points[position]
"Replacement" is the player ranked at roughly the number of starters your league rosters at that position. For an 8-team league we use these ranks (tune to your league):
REPLACEMENT_RANK = {"QB": 14, "RB": 30, "WR": 30, "TE": 10, "K": 8, "DST": 8}
import requests
API = "https://api.gridirondata.com/api/v1"
KEY = "YOUR_KEY"
H = {"x-api-key": KEY}
REPLACEMENT_RANK = {"QB": 14, "RB": 30, "WR": 30, "TE": 10, "K": 8, "DST": 8}
# "ppr" (default), "half", or "std" — match your league.
SCORING = "ppr"
SUFFIX = {"ppr": "", "half": "_HALF", "std": "_STD"}[SCORING]
def points(p):
sp = p["seasons"]["2026"]["season_projections"]
base = "MISC_FPTS" if "MISC_FPTS" in sp else "FPTS"
return float(sp.get(base + SUFFIX, sp.get(base, 0)))
# 1. Pull every position
players = []
for pos in REPLACEMENT_RANK:
rows = requests.get(f"{API}/players", headers=H, params={"position": pos}).json()
rows = rows.get("players", rows) if isinstance(rows, dict) else rows
for p in rows:
if p.get("seasons", {}).get("2026", {}).get("season_projections"):
players.append({"id": p["player_id"], "pos": pos, "pts": points(p)})
# 2. Replacement baseline per position
repl = {}
for pos, rank in REPLACEMENT_RANK.items():
ranked = sorted((p["pts"] for p in players if p["pos"] == pos), reverse=True)
repl[pos] = ranked[min(rank - 1, len(ranked) - 1)]
# 3. VORP + sorted board
for p in players:
p["vorp"] = round(p["pts"] - repl[p["pos"]], 1)
board = sorted(players, key=lambda x: -x["vorp"])
for i, p in enumerate(board[:24], 1):
print(f"{i:2}. {p['id']:24} {p['pos']:3} proj {p['pts']:6.1f} VORP {p['vorp']:6.1f}")
/api/v1/stats/{player_id}/2025 to fade injury-prone or unproven players, or change REPLACEMENT_RANK for a 10- or 12-team league. Add an OP/superflex slot and QB value jumps.