← All guides
Product Release

Snap Share Is Now in the API

Released September 2026 · Available on every plan, no version bump required

Every fantasy-relevant player now carries per-week offensive snap share back to 2020 — the share of his team's offensive plays he was actually on the field for. It's the earliest honest signal of a role change, and it's live on a new /snaps endpoint plus joined onto the stats you already pull.

Why snap share

Fantasy points are a lagging indicator. A running back can take over a backfield in Week 9 and still post a quiet line because the game script went sideways. Snap share doesn't have that problem — if the coach is putting him on the field for 85% of plays instead of 40%, the role has already changed, and the points follow.

Chase Brown's 2024 season is the clean version of this. Here's what the API returns for him, alongside his PPR output:

WeekOpponentSnap shareSnapsPPR
1NE33%175.3
4CAR40%2723.2
8PHI48%2811.4
9LV80%5926.7
10BAL87%7124.4
14DAL83%5924.3
15TEN93%6426.3

Weeks 1–8 he hovered between 20% and 48%. From Week 9 on he never dropped below 80%. If you were watching snap share, the takeover was visible the Sunday it happened — not three box scores later.

What shipped

Coverage: 2020–2025 regular season (weeks 1–18) for every fantasy-relevant player in the dataset — roughly 1,200 players and 3,900 player-seasons. Percentages are returned as 0–100. Sourced from nflverse / Pro-Football-Reference game-level snap counts, which begin in 2013 league-wide.

1. Pull one player

curl -H "x-api-key: YOUR_KEY" \
  "https://api.gridirondata.com/api/v1/snaps/Chase%20Brown%23RB?season=2024&trend=4"
{
  "player_id": "Chase Brown#RB",
  "player_name": "Chase Brown",
  "position": "RB",
  "season": "2024",
  "games": 16,
  "games_with_snaps": 16,
  "season_snap_pct_avg": 62.8,
  "recent_snap_pct_avg": 88.8,
  "trend_weeks": 4,
  "trend_delta": 26.0,
  "totals": {
    "games": 16,
    "offense_snaps": 686,
    "offense_pct_avg": 62.8,
    "st_snaps": 82,
    "st_pct_avg": 18.6
  },
  "weekly_snaps": [
    { "week": 1, "offense_snaps": 17, "snap_pct": 33.0, "st_snaps": 9,
      "st_pct": 43.0, "team": "CIN", "opponent": "NE" }
  ],
  "source": "nflverse / Pro-Football-Reference game-level snap counts"
}

The three fields that do the work:

Weeks with zero offensive snaps are inactives, not "played but never on the field," so they're excluded from both averages. That keeps a bye or a healthy scratch from faking a collapse in the trend.

2. Read snaps and production together

You don't need a second request to correlate the two. Snap share is joined onto the stats endpoint you're probably already calling:

curl -H "x-api-key: YOUR_KEY" \
  "https://api.gridirondata.com/api/v1/stats/Chase%20Brown%23RB/2024"
{
  "weekly_stats": {
    "9": { "points": 26.7, "opponent": "LV", "snap_pct": 80.0, "offense_snaps": 59 }
  },
  "snap_counts": {
    "weekly": { ... },
    "totals": { "games": 16, "offense_snaps": 686, "offense_pct_avg": 62.8 }
  }
}
⚠️ Weeks played ≠ weeks with snaps. A blocking tight end can log snaps with no fantasy line, and a player can score on a single special-teams return week with no offensive snaps. Weeks missing from one side omit the corresponding fields rather than reporting a misleading zero — so check for the key rather than trusting a 0.

3. Scan a whole position for role changes

The real payoff is running this across a position group and sorting by trend_delta. This finds the players whose roles are expanding right now:

import requests
from urllib.parse import quote

API = "https://api.gridirondata.com/api/v1"
H = {"x-api-key": "YOUR_KEY"}
SEASON = "2024"
MIN_GAMES = 8       # ignore small samples
TREND = 4           # trailing window

def snaps(player_id):
    r = requests.get(f"{API}/snaps/{quote(player_id, safe='')}",
                     headers=H, params={"season": SEASON, "trend": TREND})
    return r.json() if r.status_code == 200 else None

# Every RB/WR/TE in the dataset.
pool = []
for pos in ("RB", "WR", "TE"):
    r = requests.get(f"{API}/players", headers=H, params={"position": pos})
    pool += [p["player_id"] for p in r.json()["players"]]

risers = []
for pid in pool:
    d = snaps(pid)
    # 404 simply means no snap data for that player-season.
    if not d or d["games_with_snaps"] < MIN_GAMES:
        continue
    risers.append((d["trend_delta"], pid, d["position"],
                   d["season_snap_pct_avg"], d["recent_snap_pct_avg"]))

risers.sort(reverse=True)
print(f"{'DELTA':>6}  {'PLAYER':26} {'POS':4} {'SEASON':>7} {'LAST 4':>7}")
for delta, pid, pos, season_avg, recent in risers[:10]:
    print(f"{delta:+6.1f}  {pid.rsplit('#', 1)[0]:26} {pos:4} "
          f"{season_avg:7.1f} {recent:7.1f}")

Run against 2024, that returns:

 DELTA  PLAYER                     POS   SEASON  LAST 4
 +37.2  Daniel Bellinger           TE      33.1    70.2
 +33.6  Payne Durham               TE      39.7    73.2
 +32.0  Parker Washington          WR      55.3    87.2
 +28.8  Malik Washington           WR      44.7    73.5
 +28.8  Cedric Tillman             WR      54.9    83.8
 +26.0  Chase Brown                RB      62.8    88.8
 +25.0  Isaac Guerendo             RB      24.5    49.5
 +24.6  Nick Westbrook-Ikhine      WR      69.9    94.5
 +24.2  Stone Smartt               TE      20.6    44.8
 +23.7  Olamide Zaccheaus          WR      42.1    65.8

Note what this list is and isn't. It surfaces role change, not value — Daniel Bellinger at the top was a blocking tight end whose snaps ballooned without the targets to match. That's the honest read: snap share tells you a player is on the field, and you still have to ask what he's doing there. Cross-reference against /stats targets and you separate the Chase Browns from the Bellingers.

4. A practical waiver-wire filter

Combining the two signals is a handful of lines. A player worth a claim is one whose snap share jumped and whose production followed:

def worth_a_claim(player_id, season=SEASON):
    s = snaps(player_id)
    if not s or s["games_with_snaps"] < MIN_GAMES:
        return None

    r = requests.get(f"{API}/stats/{quote(player_id, safe='')}/{season}",
                     headers=H, params={"scoring": "ppr"})
    weekly = r.json()["weekly_stats"]

    # Split the season at the snap-share inflection point.
    recent_weeks = sorted((int(w) for w in weekly), reverse=True)[:TREND]
    recent_pts = [float(weekly[str(w)]["points"]) for w in recent_weeks]
    all_pts = [float(v["points"]) for v in weekly.values()]

    ppg_now = sum(recent_pts) / len(recent_pts)
    ppg_season = sum(all_pts) / len(all_pts)

    return {
        "player": player_id,
        "snap_delta": s["trend_delta"],
        "ppg_delta": round(ppg_now - ppg_season, 1),
        # Role expanding AND the points are showing up.
        "claim": s["trend_delta"] > 10 and ppg_now > ppg_season,
    }

A positive snap_delta with a flat ppg_delta is the interesting edge case: the role arrived but the production hasn't. Historically that's the buy window, and it closes fast.

Ask Claude instead

If you use the MCP server, snap share landed there too as get_snap_share. Update to the latest server and you can just ask:

"Which RBs gained the most snap share over the last four weeks of 2024,
 and did their PPR points follow?"

Notes and caveats

Next steps