Skip to content

In-Game Predictions#

This feature is BETA

Quarter4's In-game predictions use the power of our AI to give predicted outcomes on a play-by-play level, for each available game. Be among the first to experience our In-game next generation prediction model and help us make it better. Check back here for changes as new features are added to the In-Game APIs.

Realtime APIs are available at the following endpoint:

https://api.quarter4.io/{sport}/v2/realtime/socket.io

where {sport} is one of hockey, basketball, or american-football.

The API uses the socket.io client libraries which use a WebSocket connection or, in the case a WebSocket connection is not possible, falls back to HTTP long-polling. It will also automatically try to reconnect if the connection is lost.

Message delay

Results coming into the realtime API are typically delayed about 8-15 seconds from live game play.

The Realtime APIs allow you to follow live prediction updates during broadcast games. Not all games are available live. Most professional level games are available and some NCAA level games. You can check using use the sport's Events Data API to retrieve the information about an event and look for the coverage attribute with a value of full.

Supported Events

Only events with full coverage will have live predictions available.

Expected Data Flow#

Your API connection is secured by an API key. This key is secret and must not be shared with external parties or users. Always check to be sure the code you write does not inadvertently expose your API key.

Danger

The API key is a secret key associated with your account. Do not share it or expose it in public web pages. Doing so would allow anyone to make queries as if they were you. If you think your API key may have been compromised, contact your Quarter4 Account representative immediately to disable your current key and receive a new key.

To load in-game data and distribute it to your users, you must:

  1. Connect the Quarter4 Push API on a server-to-server layer of your application,
  2. Listen for the incoming messages and process them as appropriate for your business logic,
  3. Send messages to your users using your own socket based push apis.

Here is a sequence diagram that outlines the general process:

sequenceDiagram participant Q4 as Quarter4<BR>Push Socket participant Server as Your Quarter4<br>Socket Client participant BL as Your Business Logic participant SS as Your Messaging Server<br>Sockets, etc. participant App as Your App/Website<br>Messaging Clients Server->>+Q4: Connect using API key. Q4->>+Server: emit `connect` on success loop For each In-Game Supported Event Server->>+Q4: emit `join` with event UUID Q4->>+Server: Successful `history` response rect rgb(245, 245, 245) Note over Server,App: Your Business Logic / Message Layer<br>Your requirements and implementation will vary Server->>+BL: Q4 Game History {...} loop BL->>BL: Process History end BL->>+SS: Your New<br>Message loop For each client SS->>+App: Your New<br>Message end end loop New messages as they occur in-game (see types below) Q4->>+Server: In-Game Message {...} rect rgb(245, 245, 245) Note over Server,App: Your Business Logic / Message Layer<br>Your requirements and implementation will vary Server->>+BL: Q4 Game Message {...} loop BL->>BL: Process Message end BL->>+SS: Your New<br>Message loop For each client SS->>+App: Your New<br>Message end end end end

This can also be illustrated by the following data flowchart where the message starts in the Quarter4 In-Game Push api but is then processed by your custom business logic and distributed to your users though yor own messaging service:

flowchart TD A[Quarter4 Push API]:::Q4 --> B[Your Backend Server]:::Client A[Quarter4 Push API] --> B[Your Backend Server] A[Quarter4 Push API] --> B[Your Backend Server] B --> BL(Your Business Logic):::Client BL --> |Your Message| C{Your Messaging<br>Services}:::Client C -->|Your Message| D[fa:fa-exclamation Website]:::Client C -->|Your Message| E[fa:fa-exclamation-circle App]:::Client C -->|Your Message| F[fa:fa-comment Other]:::Client classDef Q4 color:#fff,fill:#18032d,stroke:#333,stroke-width:1px classDef Client color:#000,fill:#eee,stroke:#333,stroke-width:1px

Quickstart#

To connect to the Quarter4 In-Game Push feed using socket.io, you'll need:

  1. Your API key.
  2. An Event UUID you'd like to connect to.
  3. An appropriate socket.io client library such as:

For example, using Node.js (server-to-server) you would do the following to make a new connection and join the feed for an event:

io = require("socket.io-client"),

const sport = 'basketball'
const apiKey = 'YOUR_API_KEY'
const eventUuid = 'EVENT_UUID'

const socket = io(server, {
  path: "/" + sport + "/v2/realtime/socket.io",
  auth: {
    api_key: apiKey,
  },
});

socket.on("connect", () => {
  socket.emit(
    "join",
    eventUuid
    (history) => {
      console.log('history for ' + eventUuid, history);
    }
  );
});

socket.on("play", (item) => {
  console.log("play", item, item.message);
});

socket.on("clock", (item) => {
  console.log("clock", item, item.message);
});

socket.on("prediction", (item) => {
  console.log("prediction", item, item.message);
});

// etc...

socket.connect();

Danger

You should never include your private API key in public source code. In a production environment you would connect the socket stream server side and then feed the results to your users though your own messaging layers. This includes web applications, native mobile applications or any situation where the API key could be intercepted by a third party.

In the above example, you connect to the socket at the endpoint and provide your API Key:

const socket = io(server, {
  path: "/" + sport + "/v2/realtime/socket.io",
  auth: {
    api_key: apiKey,
  },
});

Then, when the connection succeeds the connect event will occur on the socket and you can then join the event you'd like messages for:

socket.on("connect", () => {
  socket.emit(
    "join",
    eventUuid
    (history) => {
      console.log('history for ' + eventUuid, history);
    }
  );
});

When emitting the join message you can include a callback that will receive an object that contains the current message history for the event.

You then listen for the incoming play messages and process the message accordingly:

socket.on("play", (item) => {
  console.log("play", item, item.message);
});

Message Types#

There are several types of messages in the push feeds. These can be received though the socket by listening for the specific type, for example:

socket.on("play", (item) => {
  console.log("play", item, item.message);
});

or by checking the messageType attribute of the message:

socket.onAny((item) => {
  console.log(item.messageType);
});

Warning

Additional message types may be added in the future. If you are using any sort fo catchall for socket messages, your code should check the message type and ignore those that it doesn't understand so that error do not occur when new message types are added.

The play Message Type#

Supported Leagues: NBA NFL NHL NCAAM

Note

This message type was formerly message. Both message and play will appear in the feed but are identical.

This message type describes the last play and includes a prediction of what to expect for the next play.

Example play message
{
    "uuid": "4dc3347c-9568-458c-a9a4-cd09f0bb12b7",
    "sequence": 1675210264293,
    "message": "Heat lineup change (Jimmy Butler, Tyler Herro, Caleb Martin, Kyle Lowry, Bam Adebayo). Predicting the Heat to score next (69.85% chance) for 2 points (63.01% chance).",
    "prediction": {
        "is_home_next_score": "Heat",
        "home_next_score": 0.3015226721763611,
        "away_next_score": 0.6984773278236389,
        "free_throw_make": "No free throws.",
        "free_throw_made": 0.3643263578414917,
        "free_throw_miss": 0.6356736421585083,
        "next_basket_3pt": 0.3699267506599426,
        "next_basket_2pt": 0.6300732493400574,
        "is_home_win": 0.5882874727249146,
        "is_away_win": 0.41171252727508545,
        "description": "Predicting the Heat to score next (69.85% chance) for 2 points (63.01% chance)."
    },
    "play": {
        "description": "Heat lineup change (Jimmy Butler, Tyler Herro, Caleb Martin, Kyle Lowry, Bam Adebayo)"
    },
    "game": {
        "id": "585ad812-108f-49ba-a3f8-b632f6f435f3",
        "period": 1,
        "period_type": "quarter",
        "period_sequence": 1,
        "clock": "12:00",
        "home": {
            "id": "bb980b2c-88df-442b-b2d2-97e73c301c1f",
            "image": "https://avatar.api.quarter4.io/basketball/avatar/bb980b2c-88df-442b-b2d2-97e73c301c1f/256/logo.png",
            "used_timeouts": null,
            "remaining_timeouts": null,
            "points": 0
        },
        "away": {
            "id": "bb425691-8689-4adc-a9ef-323dc6d8c6c1",
            "image": "https://avatar.api.quarter4.io/basketball/avatar/bb425691-8689-4adc-a9ef-323dc6d8c6c1/256/logo.png",
            "used_timeouts": null,
            "remaining_timeouts": null,
            "points": 0
        }
    },
    "messageType": "play"
}

The content of the prediction attribute will vary by sport.

The clock Message Type#

Supported Leagues: NBA NFL NHL NCAAM

This message type contains information about the current game clock.

Example clock message
{
    "game": {
        "id": "585ad812-108f-49ba-a3f8-b632f6f435f3"
    },
    "event": {
        "id": "585ad812-108f-49ba-a3f8-b632f6f435f3"
    },
    "clocks": {
        "game": "1157",
        "running": true,
        "shot": "21.00"
    },
    "period": {
        "sequence": 1,
        "number": 1,
        "type": "REG"
    },
    "messageType": "clock"
}

The prediction Message Type#

Supported Leagues: NBA

This message type contains end-of-game team and player predictions, along with the current in-game stats. The messages are sent periodically throughout the game at natural breaks (commercial, timeouts, fouls, etc.). The predicted values will update throughout the game based on the players performance in the game.

Example pediction message
{
  "uuid": "292c8823-7f65-4821-bf40-e9bd70227412",
  "players": [
      {
          "fullName": "Caleb Martin",
          "uniform": "16",
          "uuid": "1a4e4949-ec7a-4495-8e4b-b03c04dcd34d",
          "avatarUrl": "https://avatar.api.quarter4.io/basketball/avatar/bb425691-8689-4adc-a9ef-323dc6d8c6c1/256/uniform/16.png",
          "teamName": "Miami Heat",
          "position": "SF",
          "props": {
              "period": 1,
              "position": 0,
              "points": 0,
              "rebounds": 0,
              "assists": 0,
              "blocks": 0,
              "steals": 0,
              "turnovers": 0,
              "freeThrowsMade": 0,
              "freeThrowsAtt": 0,
              "fieldGoalsMade": 0,
              "fieldGoalsAtt": 0,
              "threePointsMade": 0,
              "threePointsAtt": 0,
              "offensiveRebounds": 0,
              "defensiveRebounds": 0,
              "personalFouls": 0
          },
          "predictions": {
              "fieldGoalsMadeTotal": 2.729,
              "fieldGoalsAttTotal": 6.299,
              "freeThrowsMadeTotal": 0.982,
              "freeThrowsAttTotal": 0.863,
              "threePointsMadeTotal": 1.06,
              "threePointsAttTotal": 3.175,
              "offensiveReboundsTotal": 0.895,
              "defensiveReboundsTotal": 2.61,
              "stealsTotal": 0.99,
              "blocksTotal": 0.223,
              "turnoversTotal": 1.106,
              "personalFoulsTotal": 1.883,
              "assistsTotal": 0.651,
              "reboundsTotal": 3.505,
              "pointsTotal": 7.501
          }
      },
    // etc. for each active player in the depth chart...
  ],
  "event": {
      "uuid": "585ad812-108f-49ba-a3f8-b632f6f435f3",
      "period": 1,
      "period_type": "quarter",
      "period_sequence": 1,
      "clock": "12:00",
      "props": {
          "overUnder": 223.514,
          "homeWinLoss": 0.521314,
          "awayWinLoss": 0.478686,
          "homeSpread": -1,
          "awaySpread": 1,
          "winLossSpreadConsistency": 1,
          "updatedAt": "2023-01-31T23:43:54",
          "probFirstHalfWinHome": 0.483333,
          "probFirstHalfWinAway": 0.516667,
          "probFirstTo10PointsHome": 0.340476,
          "probFirstTo10PointsAway": 0.659524,
          "probFirstTo20PointsHome": 0.514286,
          "probFirstTo20PointsAway": 0.485714,
          "probFirstTo30PointsHome": 0.511905,
          "probFirstTo30PointsAway": 0.488095,
          "probFirstToEarn3ptHome": 0.545238,
          "probFirstToEarn3ptAway": 0.454762,
          "probFirstToScoreHome": 0.547619,
          "probFirstToScoreAway": 0.452381,
          "overtimeProbability": 0.0357143,
          "predictAsOvertime": 0,
          "homePoints": 0,
          "awayPoints": 0,
          "period": 1,
          "totalScore": 223.514
      }
  },
  "teams": {
      "home": {
          "location": "Cleveland",
          "nickName": "Cavaliers",
          "abbreviation": "cle",
          "uuid": "bb980b2c-88df-442b-b2d2-97e73c301c1f"
      },
      "away": {
          "location": "Miami",
          "nickName": "Heat",
          "abbreviation": "mia",
          "uuid": "bb425691-8689-4adc-a9ef-323dc6d8c6c1"
      }
  },
  "messageType": "prediction"
}

The status Message Type#

This message type contains the current game status (when it changes). For example when a game switches from inprogress to halftime.

Example status message
{
    "uuid": "585ad812-108f-49ba-a3f8-b632f6f435f3",
    "status": "inprogress",
    "messageType": "status"
}

Message Type: injury#

This message type indicates an in-game injury has ocurred and the player wil not be returning to the game. New predictions after this will take the injury into account.

Example injury message
{
    "game": "5b21507c-3297-4a55-998b-3532807078f0",
    "player": {
        "fullName": "Mo Bamba",
        "uniform": "11",
        "uuid": "a0db8dd2-afdd-4f29-8e19-ebeccb670071",
        "primaryPosition": "C",
        "avatarUrl": "...url..."
    },
    "team": {
        "location": "Orlando",
        "nickName": "Magic",
        "abbreviation": "orl",
        "uuid": "d4c5d92a-43dd-40f9-96cc-c23f68733736",
        "avatarUrl": "...url..."
    },
    "messageType": "injury"
}

Message Type: swing#

This message type indicates an in-game prediction swing from one team to another. For example if Team A was expected to win but the homeWinLoss and awayWinLoss predictions now indicates Team B will win.

This message is provided for convenience will be preceded by a prediction message with the same event and team information.

Example swing message
{
  "event": {
      "uuid": "585ad812-108f-49ba-a3f8-b632f6f435f3",
      "period": 1,
      "period_type": "quarter",
      "period_sequence": 1,
      "clock": "12:00",
      "props": {
          "overUnder": 223.514,
          "homeWinLoss": 0.521314,
          "awayWinLoss": 0.478686,
          "homeSpread": -1,
          "awaySpread": 1,
          "winLossSpreadConsistency": 1,
          "updatedAt": "2023-01-31T23:43:54",
          "probFirstHalfWinHome": 0.483333,
          "probFirstHalfWinAway": 0.516667,
          "probFirstTo10PointsHome": 0.340476,
          "probFirstTo10PointsAway": 0.659524,
          "probFirstTo20PointsHome": 0.514286,
          "probFirstTo20PointsAway": 0.485714,
          "probFirstTo30PointsHome": 0.511905,
          "probFirstTo30PointsAway": 0.488095,
          "probFirstToEarn3ptHome": 0.545238,
          "probFirstToEarn3ptAway": 0.454762,
          "probFirstToScoreHome": 0.547619,
          "probFirstToScoreAway": 0.452381,
          "overtimeProbability": 0.0357143,
          "predictAsOvertime": 0,
          "homePoints": 0,
          "awayPoints": 0,
          "period": 1,
          "totalScore": 223.514
      }
  },
  "teams": {
      "home": {
          "location": "Cleveland",
          "nickName": "Cavaliers",
          "abbreviation": "cle",
          "uuid": "bb980b2c-88df-442b-b2d2-97e73c301c1f"
      },
      "away": {
          "location": "Miami",
          "nickName": "Heat",
          "abbreviation": "mia",
          "uuid": "bb425691-8689-4adc-a9ef-323dc6d8c6c1"
      }
  },
  "messageType": "swing"
}