Fantasy

Fantasy

The Fantasy API turns a RaceHooks race weekend into fantasy points. It gives you an estimated points breakdown per driver for any completed Grand Prix or Sprint, a running score while a session is on track, and a pit-lane leaderboard — all keyed to the same canonical driver identity (TLA and team) as every other RaceHooks surface. Pair it with the drop-in fantasy scorer and you have a live leaderboard from race-event webhooks.

Base URLhttps://api.racehooks.io/v1
All three Fantasy endpoints require a paid plan — Developer tier or higher. On the Free tier they return 403 Forbidden. See plans & limits for the full tier matrix, and the pricing page to upgrade.

What you get

Three endpoints, all authenticated with a Bearer token and versioned under /v1:

FieldTypeDescription
races/:raceId/scoresGET · Developer+Estimated fantasy points per driver for a completed race. Grand Prix by default; ?session=sprint scores the Sprint. Returns a full per-driver breakdown.
session/:sessionId/scoresGET · Developer+Running (provisional) fantasy score for each driver while a session is live, computed from the current running order and retirements.
session/:sessionId/pit-timesGET · Developer+Pit-lane time leaderboard for a session, ranked fastest → slowest, with the fastest stop called out.

The scoring model

The race and session endpoints return RaceHooks' own estimated fantasy model — a transparent, deterministic points system computed from finishing position, qualifying, positions gained, fastest lap, teammate comparison, and retirements. Every entry ships the full breakdownso you can show your users exactly where each point came from, or re-map the components onto your own league's rules. (To score against official-style, DFS-style, or fully custom rule sets instead, use the fantasy scorer further down.)

Grand Prix finishing points

FinishPoints
P125
P218
P315
P412
P510
P68
P76
P84
P92
P101
P11+0

Sprint finishing points

A Sprint scores a compressed P1–P8 table. Qualifying, fastest-lap and quali-teammate components do not apply to a Sprint (their breakdown fields come back null).

FinishPoints
P18
P27
P36
P45
P54
P63
P72
P81
P9+0

Qualifying points

Grand Prix scoring adds qualifying position points (P1 = 10 down to P10 = 1, −1 per place) plus a segment bonus:

FieldTypeDescription
qualiPosPointsP1–P1010 points for pole, decreasing by one per place to 1 point at P10. P11+ scores 0.
q3Bonus+3 / +2+3 for reaching the final qualifying segment (P1–P10); +2 for the middle segment (P11–P15).

Bonuses & penalties

FieldTypeDescription
positionsGainedPoints+2 / placeTwo points for every position gained between the starting grid and the finish. Only net gains score; positions lost do not deduct.
fastestLapPoints+5Awarded to the driver credited with the race's fastest lap. Grand Prix only.
beatTeammateRace+3Finishing ahead of a running teammate.
beatTeammateQuali+2Out-qualifying a teammate. Grand Prix only.
dnfPenalty−15Applied to a retirement, disqualification, or a did-not-start (DNF / DSQ / DNS).

Reading the breakdown object

Every driver entry carries a breakdown with the same shape on both the race and live endpoints. A component is null when it does not apply (Sprint, or a live session before the flag) and 0 when it applies but scored nothing.

FieldTypeDescription
racePosPointsintegerFinishing-position points from the table above (Grand Prix or Sprint scale).
qualiPosPointsinteger | nullQualifying-position points. null when no qualifying result is available, or on a Sprint / live session.
q3Bonusinteger | nullQualifying-segment bonus (+3 / +2 / 0). null when qualifying data is unavailable.
positionsGainedinteger | nullRaw places gained (grid − finish); can be negative. null when grid or finish is unknown.
positionsGainedPointsintegerPoints awarded for net positions gained (positionsGained × 2, gains only).
fastestLapPointsinteger | null+5 if this driver set the fastest lap, else 0. null on a Sprint / live session.
beatTeammateRaceinteger+3 if finishing ahead of a running teammate, else 0.
beatTeammateQualiinteger | null+2 if out-qualifying a teammate, else 0. null on a Sprint / live session.
dnfPenaltyinteger−15 on a DNF / DSQ / DNS, else 0.
totalintegerSum of every component above — the driver's fantasy score for the session.

Race scores

GET/v1/fantasy/races/:raceId/scoresDEVELOPER+

Estimated fantasy points for a completed session, sorted highest total first. Pass ?session=sprint to score the Sprint instead of the Grand Prix; the response sessionType (grand_prix or sprint) echoes which was scored. If a race has no qualifying data yet, qualiPosPoints and q3Bonus come back null and a dataQuality note is included.

FieldTypeDescription
raceIdstring (path)Race id slug, e.g. 2026-italian-r1. See the Historical API for how race ids are formed.
sessionstring (query)"race" (default) scores the Grand Prix; "sprint" scores the Sprint using the compressed points table.
bash
# Grand Prix scoring (default)
curl "https://api.racehooks.io/v1/fantasy/races/2026-italian-r1/scores" \
  -H "Authorization: Bearer ${TOKEN}"

# Sprint scoring — compressed points table
curl "https://api.racehooks.io/v1/fantasy/races/2026-italian-r1/scores?session=sprint" \
  -H "Authorization: Bearer ${TOKEN}"

# Response
{
  "data": {
    "raceId": "2026-italian-r1",
    "sessionType": "grand_prix",
    "scores": [
      {
        "driver": "max-verstappen",
        "tla": "VER",
        "team": "Red Bull Racing",
        "breakdown": {
          "racePosPoints": 25,
          "qualiPosPoints": 10,
          "q3Bonus": 3,
          "positionsGained": 0,
          "positionsGainedPoints": 0,
          "fastestLapPoints": 5,
          "beatTeammateRace": 3,
          "beatTeammateQuali": 2,
          "dnfPenalty": 0,
          "total": 48
        }
      }
    ]
  }
}

Live in-session scores

GET/v1/fantasy/session/:sessionId/scoresDEVELOPER+

A running, provisional score for each driver while a session is on track, derived from the live running order and retirements. Position, teammate and DNF components update lap by lap; the grid-derived (positions gained, qualifying) and fastest-lap components are only finalised once the session ends — until then they are null. For the final breakdown, call the race scores endpoint after the flag.

FieldTypeDescription
sessionIdstring (path)Session id, e.g. 2026-italian-r1_Race or 2026-italian-r1_Sprint.
livebooleantrue while a session is genuinely live for this id; false (with an empty scores list and a message) otherwise.
provisionalbooleanAlways true for this endpoint — scores are in-flight and will change.
currentLapintegerCurrent lap of the session.
totalLapsintegerScheduled total laps.
bash
# Running score while a session is on track
curl "https://api.racehooks.io/v1/fantasy/session/2026-italian-r1_Race/scores" \
  -H "Authorization: Bearer ${TOKEN}"

# Response — provisional; grid-derived + fastest-lap components stay null until the flag
{
  "data": {
    "sessionId": "2026-italian-r1_Race",
    "live": true,
    "provisional": true,
    "sessionType": "grand_prix",
    "currentLap": 34,
    "totalLaps": 53,
    "scores": [
      {
        "driver": "LEC", "tla": "LEC", "team": "Ferrari",
        "breakdown": {
          "racePosPoints": 25, "beatTeammateRace": 3, "dnfPenalty": 0,
          "qualiPosPoints": null, "q3Bonus": null,
          "positionsGained": null, "positionsGainedPoints": 0,
          "fastestLapPoints": null, "beatTeammateQuali": null,
          "total": 28
        }
      }
    ]
  }
}

# When nothing is live for this id:
# { "data": { ..., "live": false, "scores": [] },
#   "message": "No live session is currently active for this id. ..." }

Pit-stop time leaderboard

GET/v1/fantasy/session/:sessionId/pit-timesDEVELOPER+

Stationary pit-stop times for a session (the seconds the car is stopped in its box), ranked fastest to slowest, with the single fastest stop broken out as fastestStop. Stationary pit-stop time is the constructor scoring category in most fantasy games — feed it straight into a team leaderboard. This is the same pit picture surfaced by the strategy & tyre feeds.

FieldTypeDescription
sessionIdstring (path)Session id, e.g. 2026-italian-r1_Race.
source"live" | "historical"Where the ranking came from — an in-progress session vs. the completed record.
fastestStopobject | nullThe fastest single stop of the session (same shape as a stops[] entry), or null when none is recorded.
stops[].pitStopTimeSecnumberStationary pit-stop time in seconds (car stopped in its box). stops[] also carries driver (id slug), tla, team, and lap.
bash
curl "https://api.racehooks.io/v1/fantasy/session/2026-italian-r1_Race/pit-times" \
  -H "Authorization: Bearer ${TOKEN}"

# Response — ranked fastest → slowest, plus the fastest stop of the session
{
  "data": {
    "sessionId": "2026-italian-r1_Race",
    "source": "historical",
    "fastestStop": { "driver": "lando-norris", "tla": "NOR", "team": "McLaren", "pitStopTimeSec": 2.104, "lap": 21 },
    "stops": [
      { "driver": "lando-norris", "tla": "NOR", "team": "McLaren", "pitStopTimeSec": 2.104, "lap": 21 },
      { "driver": "max-verstappen", "tla": "VER", "team": "Red Bull Racing", "pitStopTimeSec": 2.418, "lap": 19 }
    ]
  }
}

Build a fantasy app

For a live leaderboard driven by your own scoring rules, subscribe to the events.race feed and pipe its payloads into the open-source racehooks-f1-fantasy-scorer package. The scorer is zero-dependency, fully typed, and deterministic — every point is logged with a reason code — and ships official-style, DFS-style, and custom rule sets, plus a budget-aware roster optimizer.

GitHub repository →npm package →

1. Install

bash
npm install racehooks racehooks-f1-fantasy-scorer

2. Subscribe a roster and wire the scorer

Create one events.race webhook filtered to your league's drivers, and point a FantasyScorer at the same roster:

setup.ts
import { RaceHooks } from "racehooks";
import { FantasyScorer, OfficialF1ScoringRules } from "racehooks-f1-fantasy-scorer";

const rh = new RaceHooks({
  clientId:     process.env.RACEHOOKS_CLIENT_ID!,
  clientSecret: process.env.RACEHOOKS_CLIENT_SECRET!,
});

// 1. Subscribe to the race-events feed, filtered to your league's roster.
const { webhook, webhookSecret } = await rh.webhooks.create({
  feedId:     "events.race",
  webhookUrl: "https://your-app.com/fantasy-hook",
  filters:    { drivers: ["VER", "NOR", "LEC", "HAM", "RUS"] },
});
console.log("Save this signing secret:", webhookSecret);

// 2. Spin up a scorer for the same roster.
const scorer = new FantasyScorer({
  rules:  OfficialF1ScoringRules,
  roster: ["VER", "NOR", "LEC", "HAM", "RUS"],
});

// 3. Push a fresh leaderboard whenever a point changes.
scorer.on("scoreUpdate", (scores, event) => {
  console.log(`${event.driver} ${event.points > 0 ? "+" : ""}${event.points} (${event.reason})`);
  renderLeaderboard(scores); // { VER: 35, NOR: 39, LEC: 32, ... }
});

3. Score deliveries as they arrive

Verify each delivery's signature, then hand the payload to scorer.ingest() — it routes each event to the right handler and finalises the classification automatically when the session completes:

server.ts
import express from "express";
import { verifySignature } from "racehooks";

const app = express();
app.use("/fantasy-hook", express.raw({ type: "application/json" }));

app.post("/fantasy-hook", (req, res) => {
  const sig = req.headers["x-racehooks-signature"] as string;
  if (!verifySignature(req.body, sig, process.env.WEBHOOK_SECRET!)) {
    return res.status(401).send("Bad signature");
  }

  // ingest() routes any RaceHooks payload to the right handler and
  // auto-finalises the classification when the session completes.
  scorer.ingest(JSON.parse(req.body.toString()));
  res.sendStatus(200);
});
Want to test before race day? Replay any historical session through your webhook with Simulate — your scorer receives the exact events.race payloads it would in a live race, so you can validate a full weekend of scoring against a known result on a quiet afternoon.

How race events map to scoring

The scorer draws its points from these events.race events:

FieldTypeDescription
driver_finishedeventFinishing-position points, plus positions gained or lost against the starting grid.
session.completeeventTriggers final classification from the last observed order (the scorer's finalize step).
overtakeeventOvertake points (handles cumulative overtake counts).
fastest.lapeventFastest-lap bonus, for rule sets that award one.
pit.stop.completeeventConstructor pit-stop time points, plus fastest-stop and record-stop bonuses.
retirementeventDNF penalty — race vs. sprint aware.
driver_of_the_dayeventDriver of the Day bonus.
RaceHooks is an independent analytics platform and is not affiliated with, endorsed by, or sponsored by Formula 1, the FIA, FOM, or any official fantasy game. Fantasy points are RaceHooks' own estimates and the scorer's rule sets are provided for you to configure — they are not official data or an official scoring service.
← Race Events feedrh.fantasy SDK →