JerryGM Projections API · v1 Back to the app →

JerryGM Projections API — v1

Weekly and full-season NFL fantasy projections (QB/RB/WR/TE, plus kickers and team defenses from v1.1) from the JerryGM projection model. Backtested against held-out seasons; every response includes projected points and the projected stat line, under your league's own scoring.

All data is JerryGM model output derived from openly licensed nflverse data — licensed for external consumption.

Base URL: https://api.jerrygm.com/api/ext/v1 (staging: https://api-dev.jerrygm.com/api/ext/v1)

This page is served by the API itself at GET /api/ext/v1/docs — it always matches the deployed version. No API key needed to read it.

Try it live: the interactive console lets you build requests, see responses (with a results table and rate-limit info), and copy cURL commands. Bring your API key.

Machine-readable spec: GET /api/ext/v1/openapi.json (OpenAPI 3.0) — generated from the same constants the server validates against, so its enums cannot drift from the code. Point a client generator at it.

Authentication

Every request needs an API key:

X-API-Key: jgmk_<48 hex>

(Authorization: Bearer jgmk_… also works.) Keys are per-account, rate-limited per minute (responses carry X-RateLimit-Limit / X-RateLimit-Remaining; 429 + Retry-After when exceeded), and revocable.

Plans also carry a monthly call allowance. Every response includes X-Quota-Limit, X-Quota-Remaining and X-Quota-Reset (ISO timestamp of the first of next month, UTC); exceeding it is a 429 whose body names your plan, the limit, resetAt and an upgradeUrl. Some parameters are plan-gated (see 402 under Errors). GET /sources echoes your current plan and limits.

Getting a key. API access is enabled per account by JerryGM. Once your JerryGM account is enabled, create keys yourself in the app:

  1. Sign in at JerryGM and open Settings → System → JerryGM API. (The tab only appears once your account is enabled for API access.)
  2. Under Name your new key, give it a label (e.g. "JGM Projections") and click + Create key. You can rename a key later with the ✏️ button.
  3. Copy the key immediately — it is shown exactly once; only a hash is kept on our side. Revoking a key in the same screen disables it within a minute.

Don't have access yet, or lost a key? Contact JerryGM and we can enable your account, or issue and manage keys for you.

GET /projections

paramdefaultmeaning
seasoncurrent seasone.g. 2026
week*(omitted)*118 → that week's projection using data available before it; omit → full-season/preseason projection
scoringhalfppr | half | std, or a URL-encoded JSON weights object (below)
sourcejgm_baselinejgm_baseline | jgm_cofactor | nflverse_naive
idscomma list of player ids
idTypegsisnamespace for ids: gsis espn sleeper mfl yahoo cbs sportradar sdio pff fantasydata
names;-separated names; , also works when no "Last, First" entries. Team defenses match by franchise name, nickname, or code — see below
positionsallcomma list of QB RB WR TE K DST — narrows the pool (applied before any plan cap)

No ids/namesall active players (everyone the model projects, kickers and team defenses included).

Kickers and team defense (v1.1)

Kickers (position: "K") and team defenses (position: "DST", id DST-<team>, name <team> D/ST) are projected as the stat line their scoring needs, then scored under your tier weights:

  • K statLine: fgAtt, fgMade, and per distance bucket fgAtt0to19fgAtt50plus, fgMade…, fgMissed…, plus patAtt, patMade, patMissed. Attempts per bucket are a trailing blend; the make rate per bucket is the kicker's own, shrunk toward the league rate for that distance (breakdown.makeRates). Kicking data starts with the 2025 season.
  • DST statLine: defSacks, defInterceptions, defFumbleRecoveries, defFumblesForced, defTds, defSafeties, defBlocks, defTwoPtReturns, pointsAllowed (mean), yardsAllowed. Points allowed keeps a spread (breakdown.pointsAllowedSd), and the tier score is the expected value over that spread (breakdown.paTierExpectedPPG) — scoring the mean alone would snap every defense into one tier.

Looking a defense up by name. Every platform spells a team defense differently, so names= accepts all of them and resolves to the one row:

Los Angeles Chargers D/ST   Chargers D/ST   Chargers   LAC D/ST   LAC

all return DST-LAC, as do the box-score spelling LA Chargers and the glued form LACDST. Full franchise name, nickname, city where it is unambiguous, and any code your platform uses (ESPN WSH, MFL KCC, the old OAK/SD/STL) are accepted, with or without a D/ST / DST / DEF / Defense marker, spaced or glued to the code. Three rules worth knowing: a shared city alone (Los Angeles, New York) is ambiguous and comes back in unmatched rather than guessed; the bare code LA is the Rams (it is their nflverse code), so LA, LA D/ST and LADST all return DST-LA — write LAC or LA Chargers for the Chargers; and a *player* whose name contains a city word (Dallas Goedert) is always matched as a player. Our own canonical spelling is <TEAM> D/ST with the nflverse code (LA for the Rams, WAS for Washington, JAX), which is also what ids.gsis carries as DST-<TEAM>.

Scoring: presets use standard unit scoring. Override with scoring.kicker and/or scoring.defense inside the weights object (GET /sources returns the defaults under kickerDefaults / defenseDefaults):

{"rec":1,"passTd":4,
 "kicker":{"fg0to19":3,"fg20to29":3,"fg30to39":3,"fg40to49":4,"fg50to59":5,"fg60plus":6,"fgMiss":-1,"pat":1,"patMiss":-1},
 "defense":{"sack":1,"int":2,"fumRec":2,"defTd":6,"safety":2,"blockKick":2,
            "paTiers":[[0,10],[6,7],[13,4],[20,1],[27,0],[34,-1],[999,-4]]}}

paTiers is [[upperBound, points], …] ascending — the first tier whose bound is ≥ points allowed scores; use 999 for the open top tier. yaTiers (yards allowed) has the same shape; paPerPoint / yaPerYard are the linear alternatives. Per-bucket miss weights (fgMiss40to49 …) override the flat fgMiss. fgYd / fgYdOver30 (points per made field-goal yard, Sleeper's fgm_yds) are scored from the projected made yardage per bucket (statLine.fgMadeYds, fgMadeYdsOver30).

Custom league scoring

scoring accepts a JSON object of per-stat weights (unknown keys are a 400):

passYd passTd passInt rushYd rushTd rec tePremiumRec recYd recTd
fumbleLost twoPt specialTd passFd rushFd recFd
passCmp sackTaken retYd pickSix

passFd / rushFd / recFd are points per first down (passing, rushing, receiving), for PPFD leagues; the projected per-game first downs ride on statLine as passFd, rushFd, recFd.

passCmp is points per completion, sackTaken per sack the passer takes (the offense's — a defense's sacks are scoring.defense.sack), retYd per kick or punt return yard (one weight; statLine.retYds is their sum), and pickSix per interception the passer throws that is returned for a touchdown — a Yahoo league's -2 sacks / -6 pick-six / 1 per 20 return yards is {"sackTaken": -2, "pickSix": -6, "retYd": 0.05}. Completions and sacks project from the passer's own line; return yards belong to whoever returns kicks, which the depth chart decides, so they are the noisiest number on the line. A pick-six is rare enough (~30 a season league-wide) that a passer's own history proves little: statLine.pickSix is projected attempts × a per-attempt rate shrunk hard toward the league rate (about a third of the way toward his own after a full season), and breakdown.pickSix shows the rate, the league rate, and the attempts and pick-sixes it was built from.

These four ride on every statLine as raw components — passCmp, sacks, retYds, pickSix — whether or not you pass the weights, so a client that applies its own league scoring to our component line can read them directly with no scoring object at all.

Long plays (v1.2)

longPlays prices a single play by its length — *+1 for each 40+ yard completion*, *+2 for each 40+ yard touchdown*. It rides inside the scoring object beside bonuses, and the threshold is yours to choose:

"longPlays": [
  {"type": "passCmp", "min": 40, "points": 1},
  {"type": "passTd",  "min": 40, "points": 2},
  {"type": "recTd",   "min": 40, "points": 2},
  {"type": "rushTd",  "min": 40, "points": 2}
]
typecountscredited to
passCmpcompletions of min+ yardsthe passer
passTdpassing touchdowns of min+ yardsthe passer
recreceptions of min+ yardsthe receiver
recTdreceiving touchdowns of min+ yardsthe receiver
rushrushes of min+ yardsthe rusher
rushTdrushing touchdowns of min+ yardsthe rusher

A rule is worth points × the player's expected number of such plays per game — a rate, not a probability, because one game can hold two. The rate comes from that player's own long plays (recent games weighted more, last season behind them), shrunk toward the positional rate, so a receiver with a quiet stretch is not priced at zero. breakdown.longPlays shows each rule with its perGame rate and the ppg it added, and breakdown.expectedLongPlayPPG is their sum.

  • Any threshold from 20 yards up. min is a yard count, not a bucket: 37 works. Below 20 is a 400 — play-by-play is only kept from there up, so a lower threshold would quietly undercount rather than be wrong loudly.
  • max closes a band. {"type":"passCmp","min":40,"max":49,"points":4} plus {"type":"passCmp","min":50,"max":59,"points":5} pays a 55-yard completion 5, once. Without max a rule is open-ended and overlapping rules stack (see below). 999 means open, so a provider's 40-999 is the same rule as a hand-written 40+ and shares its cached board.
  • One 42-yard completion feeds two rules: passCmp for the quarterback, rec for the receiver. A 42-yard touchdown feeds the Td types as well, so a league paying for both the play and the score pays twice, as it should.
  • Overlapping thresholds stack. Paying 1 at 40+ and 2 at 50+ scores 3 on a 55-yarder. If your league means them exclusively, add max — the response's scoringWarnings will point this out whenever rules overlap.
  • Play-by-play covers 1999 on. A player with no long plays in the sample is priced at the positional rate, not zero.

Example — 6-pt pass TD, full PPR, +0.5 TE premium:

GET /projections?season=2026&week=3&scoring={"passYd":0.04,"passTd":6,"passInt":-2,"rushYd":0.1,"rushTd":6,"rec":1,"tePremiumRec":0.5,"recYd":0.1,"recTd":6,"fumbleLost":-2,"twoPt":2,"specialTd":6}

(URL-encode the JSON.) GET /sources returns the preset-equivalent weight objects to start from.

#### Threshold bonuses

Add a bonuses array to the weights object for rules like *+5 for 300 passing yards* or *+3 for 100 rushing/receiving yards*. Any threshold works — min is yours to set:

{"passYd":0.04,"passTd":4,"rushYd":0.1,"recYd":0.1,"rec":1,
 "bonuses":[{"stat":"passYds","min":300,"points":5},
            {"stat":"rushRecYds","min":100,"points":3},
            {"stat":"passTds","min":3,"points":2}]}

stat is any statLine key, plus rushRecYds (rushing + receiving yards combined); GET /sources lists them under bonusStats. Up to 12 per object.

Add max to close a band, for leagues that pay tiers rather than a single threshold. 100-149 pays only in that range, so consecutive tiers each pay once instead of stacking:

"bonuses":[{"stat":"recYds","min":100,"max":149,"points":2},
           {"stat":"recYds","min":150,"max":199,"points":3},
           {"stat":"recYds","min":200,"points":5}]

Add positions to scope a rule, for leagues that tier the same stat by position — a tight end at 75 receiving yards where a receiver needs 100:

"bonuses":[{"stat":"recYds","min":75,"points":2,"positions":["TE"]},
           {"stat":"recYds","min":100,"points":2,"positions":["WR"]}]

Listing every position is the same as omitting positions, and a rule without it applies to everyone.

Overlapping rules stack, and unknown keys are refused. Two rules that both match a game each pay — 100+ worth 2 and 150+ worth 3 pay 5 on a 160-yard game. That is deliberate: plenty of leagues write the upper tier as an extra on top. If yours means exclusive tiers, give the lower rule a max. When rules overlap the response carries a scoringWarnings array saying so; nothing is silently reinterpreted. Any key we do not recognize is a 400 rather than a quiet drop, because a discarded max turns exclusive bands into stacking ones and overpays the big games without telling you.

How it's scored. A bonus is worth points × P(stat ≥ min) per game, and that probability is *not* derivable from the mean stat line — two players projected at 260 yards can have very different 300-yard rates — so it is modeled from each player's game-to-game spread (his own per-game variance, shrunk toward his position's when the sample is thin). Count stats treat "3 or more" as "more than 2.5". Every rule's probability and expected points appear in breakdown.bonuses, and their sum in breakdown.expectedBonusPPG. With bonuses, projectedPPG = your weights applied to statLine plus expectedBonusPPG.

Per-*play* rules (a 40+ yard completion) are a different thing — see Long plays below.

Boards are cached per rule set, so a second caller asking for the same thresholds gets the cached board.

Matching players by name

Name matching is case-, punctuation-, suffix-, and word-order-insensitive: Allen, Josh, josh allen, and st brown amon-ra all resolve. If your roster source has only proprietary ids + first/last names (e.g. balldontlie.io), join by names= on first sync, then cache our ids.gsis per player — it's stable — and use ids=/idType=gsis afterwards. Requested ids/names that don't resolve come back in unmatched (never an error).

Response

{
  "season": 2026, "week": 3, "scoring": "half", "source": "jgm_baseline",
  "count": 612,
  "unmatched": [ { "type": "name", "value": "Fake Person" } ],   // only if any
  "players": [
    {
      "gsisId": "00-0034857", "name": "Josh Allen", "position": "QB", "team": "BUF",
      "projectedPPG": 20.06,
      "seasonTotal": 341.0,                       // projectedPPG × 17
      "statLine": {                                // per-game, points-consistent
        "passAtt": 33.1, "passCmp": 21.9, "passYds": 249.8, "passTds": 1.9,
        "passInts": 0.7, "rushAtt": 6.8, "rushYds": 38.2, "rushTds": 0.4,
        "targets": 0, "receptions": 0, "recYds": 0, "recTds": 0,
        "fumblesLost": 0.1, "twoPt": 0.1, "stTds": 0
      },
      "gamesSampled": { "current": 8, "prior": 16 },
      "ageMultiplier": 1,
      "breakdown": {
        "trailingPPG": 21.4, "positionMean": 15.2, "shrinkWeight": 0.75,
        "cofactors": [                             // jgm_cofactor source only
          { "key": "tdRegression", "delta": -0.8,
            "detail": "Scoring 1.3 more TDs than usage supports — regression expected" }
        ]
      },
      "ids": { "gsis": "00-0034857", "espn": "3918298", "sleeper": "4984",
               "mfl": "13593", "yahoo": "30977", "cbs": "2071510", "sportradar": "…" }
    }
  ],
  "attribution": "JerryGM projections. Derived from openly licensed nflverse data (nflverse.com)."
}

Field reference (what the numbers mean)

fieldmeaning
projectedPPGThe headline number: fantasy points we expect per game played, under the scoring you requested. It does not try to predict missed games — check injuryStatus (below) for availability.
seasonTotalprojectedPPG × 17 — a full-season pace number for rankings and auction math, not a games-played prediction.
statLineThe projected per-game box score behind the points: pass/rush/receiving attempts, yards, TDs, receptions, turnovers. Always consistent with projectedPPG — score the line under your weights and you get the same number. Useful if you want raw stats instead of (or as well as) points.
gamesSampledHow much real evidence the projection rests on: current = games this season, prior = games last season. Low numbers mean a projection that will move quickly as new games arrive.
ageMultiplierAge discount applied (1 = none). Older players at age-sensitive positions (RB from 27, WR from 30, TE 31, QB 36) are trimmed a few percent per year.
breakdown.trailingPPGThe player's recency-weighted scoring rate before any adjustments — "what he's been doing lately," with recent games counting more.
breakdown.positionMeanThe typical scoring rate at the position in our pool. Small samples get pulled toward this so two hot games don't read as a superstar.
breakdown.shrinkWeightHow much of the projection comes from the player's own data (1 = all his own, lower = more position-average). Rises with games played.
breakdown.cofactors(jgm_cofactor source only) Adjustments with plain-English reasons. Currently: a preseason market prior that fades as real games arrive ("Preseason market (ADP 26) implies ~20.7 PPG — weighted 50% until real games take over"), TD-luck regression ("Scoring 1.3 more TDs than usage supports — regression expected"), and, for weekly calls, Vegas implied team totals ("Vegas implies ~29.5 team points (+31% vs average) — pace-up spot") and current depth-chart slot ("Listed #2 on the current depth chart — limited role expected"). Every cofactor must beat our held-out backtests to ship.
breakdown.bonuses / breakdown.expectedBonusPPGPresent when your scoring carries bonuses: one row per rule — {stat, min, points, probability, expectedPPG} — and their sum, which is already included in projectedPPG.
breakdown.rookiePriorPresent for pre-debut rookies: the projection comes from the draft-capital prior (see below) rather than NFL games. Values: r1, r2, r3, late.
injuryStatusWeekly calls only: the official injury-report designation for that week — Out, Doubtful, Questionable, or null (not listed / no designation). This is the availability gate to pair with projectedPPG. Full-season calls return null.
injuryNote / practiceStatusThe listed injury (e.g. Hamstring) and practice participation for the same report. null when not listed.
idsThe player's ids across ecosystems. gsis is our anchor: the NFL's own Game Statistics & Information System id (00-00XXXXX), assigned by the league, career-stable, and never reused — cache this one. The others (espn, sleeper, mfl, yahoo, cbs, sportradar) are for joining your data.

Rookies

Rookies are always projected. Before their first NFL snap they get a draft-capital prior: the average rookie-season per-game stat line for their position and draft slot (1st round, 2nd, 3rd, later/undrafted), fitted from the last four rookie classes and priced under your league's scoring — e.g. a 1st-round QB opens around 15 PPG, a 1st-round WR around 11 PPR PPG. These rows are flagged with breakdown.rookiePrior and show gamesSampled: {current: 0, prior: 0} so you can treat them with appropriate uncertainty. The moment a rookie plays a real game, his actual usage takes over and the prior is dropped.

Notes

  • Players are sorted by projectedPPG descending.
  • "All active" = everyone the model can project: players with NFL data this season or last, plus the current draft class via rookie priors. The Free plan receives the top 100 of that list (see Plans below).
  • Positions: QB, RB, WR, TE, K, DST (K and DST since v1.1; positions= narrows). DST rows carry a synthetic ids.gsis of DST-<team>.
  • week uses only data available before that week — historical calls (e.g. season=2025&week=10) return what the model *would have said*, which is also how we backtest it.
  • Responses are gzip-compressed when the request sends Accept-Encoding: gzip (every mainstream HTTP client does by default). The all-active payload is ~500 KB raw; send the header and you move a fraction of that.

GET /players

The whole projected pool's identity and id crosswalk — no projections. Every plan, never truncated, one call. Ids are free on purpose: join once against your own roster (by name or by the id you already have), cache ids.gsis, and call /projections by id from then on. The numbers are the product; the crosswalk is the on-ramp.

paramdefaultmeaning
seasoncurrent seasonwhich season's pool
{
  "season": 2026, "count": 650,
  "players": [
    { "gsisId": "00-0034857", "name": "Josh Allen", "position": "QB", "team": "BUF",
      "ids": { "gsis": "00-0034857", "espn": "3918298", "sleeper": "4984",
               "mfl": "13593", "yahoo": "30977", "cbs": "2071510", "sportradar": "…" } }
  ],
  "attribution": "JerryGM. Player ids derived from openly licensed nflverse data (nflverse.com)."
}

Sorted by name. Missing ids in a namespace are null, never omitted.

GET /sources

Lists projection sources, the preset scoring weight objects, valid weight keys, and valid idTypes.

Errors

codemeaning
400bad param — message names the offending key/value
401missing/invalid/revoked key
402not included in your plan (a source, custom scoring weights, a prior season, or more ids/names than the plan allows per request) — body names requiredPlan and an upgradeUrl
429per-minute rate limit (retry after Retry-After seconds) or monthly quota (body carries resetAt)
503key verification temporarily unavailable

Plans, licensing & attribution

FreeStarterProPartner
Calls / month1,00050,000500,000unmetered
Requests / minute60120600as agreed
Sourcesjgm_baselineallallall
Custom scoring weights
Prior seasons (point-in-time)
All-active call (no ids/names)top 100 by projectedPPGevery playerevery playerevery player
ids + names per request25unlimitedunlimitedunlimited
GET /players (id crosswalk, no numbers)every playerevery playerevery playerevery player
Active keys1310
Attribution requiredwaivedas agreed
Commercial redistributionas agreed

A call is one HTTP request, whatever it returns — the all-active call counts once. On the Free plan that call returns the top 100 players by projectedPPG; the response then carries truncated: true, total (how many the model projects) and a truncatedNote. Requests that name players by ids or names are never truncated on any plan, but on Free one request may name at most 25 (a 402 names the plan without that limit); GET /players lists every id regardless. Past your allowance, requests return 429 until the month rolls over (UTC); there is no overage billing. Plans are bought and managed in the app under Settings → System → JerryGM API; a canceled or lapsed plan drops to Free limits and keys are never revoked.

Attribution. Every response carries an attribution string. On Free and Starter, show it wherever numbers derived from this API are displayed. Pro waives it and licenses display of JerryGM-derived numbers inside your own commercial product. No plan permits reselling or redistributing the raw feed, presenting the output as your own model, or using it to train a competing projection product. Full terms: jerrygm.com/terms §11.

Stability & deprecation policy

  • Additive within a version. Under /api/ext/v1, fields and parameters may be *added* at any time. Nothing is removed, renamed, or re-typed, and existing defaults do not change. Parse responses accordingly: ignore fields you do not know.
  • Breaking changes get a new path. Anything that would break a correct v1 client ships as /api/ext/v2. Holders of active keys get at least 90 days' notice by email before v1 is retired, and v1 keeps serving through that window.
  • Limits. A reduction to an existing paid plan's limits takes effect no sooner than 30 days after notice. Increases are immediate.
  • Announcements land in the changelog below and, for anything you must act on, in email to the address on your JerryGM account.

Changelog

15 entries
  • 2026-09-11 — Completions, sacks taken, return yards, pick-sixes. Four new linear weights: passCmp, sackTaken, retYd, pickSix; statLine gains passCmp (already there), sacks, retYds, pickSix, and breakdown.pickSix explains the shrunk rate behind that last one. Sacks and pick-sixes are also valid bonus stats. Additive; a scoring object without them is scored exactly as before. Built for a Yahoo league in beta with 1/completion, −2/sack, −6/pick-six, 1 per 20 return yards.
  • 2026-09-11 — Defenses match by any name. names= resolves a team defense from its full franchise name, nickname, city, or any platform's code, with or without a D/ST-style marker — Los Angeles Chargers D/ST and LAC D/ST are the same row. Ambiguous cities alone stay unmatched. Reported by a Yahoo integrator during beta. positions= accepts Defense and PK too.
  • 2026-09-08 — OpenAPI spec, and the API root no longer lists internal routes. GET /api/ext/v1/openapi.json describes the public endpoints, generated from the server's own constants. The service root now returns identity, docs links and how to sign up, instead of enumerating every registered route; 404s no longer return the route list either. All those routes were auth-gated, so nothing was readable — but publishing the map had no upside.
  • 2026-09-08 — Tier bands (max), and unknown keys are now errors. bonuses and longPlays rules take an optional max closing the band, so a league paying 4 for a 40-49 yard completion and 5 for a 50-59 pays a 55-yarder once. 999 normalizes to open. Overlapping rules still stack, and the response now carries scoringWarnings when they do. Breaking for malformed input only: a rule key we do not recognize is a 400 instead of being dropped — previously a stray max was discarded and the tiers scored cumulatively, which is a wrong answer with no error.
  • 2026-09-08 — Bonuses can be scoped by position. bonuses[].positions (e.g. ["TE"]) prices a rule for those positions only, for leagues that tier the same stat differently. Omitted means everyone, as before.
  • 2026-09-08 — v1.2: long plays. scoring.longPlays prices a single play by its length — [{type, min, points}] over passCmp, passTd, rec, recTd, rush, rushTd, at any threshold from 20 yards up, counted from nflverse play-by-play. breakdown.longPlays explains each rule. GET /sources gains longPlayTypes and longPlayMinYards. Additive: a scoring object without longPlays is scored exactly as before.
  • 2026-09-08 — v1.1: kickers and team defenses. K and DST rows join the pool (all sources except nflverse_naive), projected as the stat line their scoring needs — FG attempts/makes per distance bucket with shrunk make rates; defensive stats plus a points-allowed distribution whose tier score is an expectation over the spread. scoring.kicker / scoring.defense weight objects; positions= filter; /sources gains positions, kickerDefaults, defenseDefaults. Additive: existing clients see new rows with new position values; filter with positions= if you only want skill players.
  • 2026-09-08 — Points per first down. New weights passFd, rushFd, recFd; statLine gains the projected per-game first downs under the same names (also usable as bonus stats). Additive; rows without first-down data read 0.
  • 2026-09-08 — Threshold bonuses. scoring.bonuses = [{stat, min, points}] — any threshold on any statLine stat (plus rushRecYds). Scored as points × P(stat ≥ min) from each player's game-to-game spread; the per-rule probability and expected points land in breakdown.bonuses and their sum in breakdown.expectedBonusPPG, already inside projectedPPG. GET /sources gains bonusStats and bonusExample. Additive. Long-play (40+ yard) bonuses are not yet covered.
  • 2026-09-07 — Free plan per-request cap. A Free request may name at most 25 players across ids and names; beyond that the response is a 402 carrying maxPerRequest, requested and requiredPlan. Paid plans unchanged.
  • 2026-09-07 — GET /players. New endpoint: the projected pool's identity + 8-way id crosswalk with no projections — every plan, never truncated, one call. Additive.
  • 2026-09-07 — Free plan all-active cap. A call with no ids/names on the Free plan returns the top 100 players by projectedPPG, with truncated: true, total and truncatedNote added to the response. Paid plans unchanged; requests naming players unchanged on every plan. Additive fields; Free-plan clients reading the whole board should move to Starter.
  • 2026-09-07 — Plans and quotas. Self-serve plans (Free / Starter / Pro). Monthly allowance surfaced as X-Quota-Limit / X-Quota-Remaining / X-Quota-Reset; 429 for an exhausted allowance carries resetAt and upgradeUrl. New 402 for parameters outside your plan (source, custom scoring, prior seasons), naming requiredPlan. GET /sources echoes your plan and limits. All responses gzip-encoded. Additive; no v1 client changes required.
  • 2026-08-29 — JerryGM release (breaking, pre-policy). Source ids jt_baseline / jt_cofactor became jgm_baseline / jgm_cofactor (a jt_* source returns 400 naming its replacement); keys were re-issued as jgmk_… and jtk_ keys return 401 with instructions. This was the last change of its kind — the policy above applies from this date.
  • 2026-08 — v1 launch. GET /projections (weekly + full-season, presets and custom weights, name and id matching, injury fields, rookie draft-capital priors, 8-way id crosswalk), GET /sources, this docs page, and the interactive console.