Skip to content

In-Game Play-By-Play Predictions#

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.

The Realtime APIs allow you to follow live play-by-play 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.

Note

Only events with full coverage will have live play-by-play predictions available.

Quickstart#

To connect to the Realtime feed using socket.io, you'll need:

For example, using JavaScript directly in a web browser you would do the following to make a new connection and join the event feed:

const sport = 'american-football'
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);
});

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

socket.connect();

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);
});

You then listen for the incoming message events and process the message accordingly:

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

Message Format#

The message item will have the following format with the prediction payload content varying by sport:

{
  "message": "J.Hurts pass complete to PHI 28. Catch made by Z.Ertz at PHI 28. Gain of 6 yards. Tackled by PIT at PHI 34. Eagles pass ended with Eagles 2nd down, 14:33 remaining in 1st quarter. Expect a Miles Sanders rush for 9.77 yards for a firstdown!",
  "prediction": {
    "play": "rush",
    "description": "Expect a Miles Sanders rush for 9.77 yards for a firstdown!",
    "predicted_prob_rush": 0.59864699840546,
    "predicted_prob_pass": 0.40130370855331,
    "predicted_prob_punt": 7.6541675753106E-7,
    "predicted_prob_kickoff": 3.8038670027163E-5,
    "predicted_prob_field_goal": 9.9582648545038E-6,
    "predicted_prob_extra_point": 6.6446453672597E-8,
    "predicted_prob_conversion": 4.8781402028908E-7,
    "predicted_prob_incomplete": 0.0,
    "predicted_prob_interception": 0.0,
    "predicted_prob_fumble": 3.2986552942274E-13,
    "predicted_prob_sack": 0.0087019801139832,
    "predicted_prob_touchdown": 0.0,
    "predicted_prob_firstdown": 1.0,
    "yards": 9.7655839920044
  },
  "play": {
    "description": "J.Hurts pass complete to PHI 28. Catch made by Z.Ertz at PHI 28. Gain of 6 yards. Tackled by PIT at PHI 34."
  },
  "game": {
    "id": "4519deb0-fcf2-40f7-b1ec-9c2eb078e248",
    "quarter": 1,
    "clock": "14:45",
    "home": {
      "id": "ff952860-0638-4c8b-9db5-39ac11152f8b",
      "image": "https://s3.ca-central-1.amazonaws.com/quarter4.io-public-assets-ca/images/team/logo/ff952860-0638-4c8b-9db5-39ac11152f8b.png",
      "used_timeouts": 0,
      "remaining_timeouts": 3,
      "points": 0
    },
    "away": {
      "id": "4b678417-7135-456c-992b-f3dd64609894",
      "image": "https://s3.ca-central-1.amazonaws.com/quarter4.io-public-assets-ca/images/team/logo/4b678417-7135-456c-992b-f3dd64609894.png",
      "used_timeouts": 0,
      "remaining_timeouts": 3,
      "points": 0
    }
  }
}

Note

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

Full Example#

Here's a is a fully featured example you can copy and paste into an HTML file and run locally in your browser:

<html>
  <head>
    <link
      rel="stylesheet"
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css"
    />
    <link
      rel="stylesheet"
      href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.15.4/css/fontawesome.min.css"
    />
    <script
      src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/js/bootstrap.min.js"
      crossorigin="anonymous"
    ></script>
    <script
      src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/js/bootstrap.bundle.min.js"
      crossorigin="anonymous"
    ></script>
    <script
      src="https://cdn.socket.io/4.1.2/socket.io.min.js"
      integrity="sha384-toS6mmwu70G0fw54EGlWWeA4z3dyJ+dlXBtSURSKN4vyRFOcxd3Bzjj/AoOwY+Rg"
      crossorigin="anonymous"
    ></script>
    <script
      src="https://code.jquery.com/jquery-3.6.0.min.js"
      integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4="
      crossorigin="anonymous"
    ></script>

    <style>
      img {
        max-height: 24px;
      }
    </style>
  </head>

  <body>
    <div class="container">
      <header class="row">
        <h1>Socket Connection Test Client</h1>
      </header>
      <div class="row">
        <form>
          <div class="mb-3">
            <label for="server" class="form-label">Socket Server</label>
            <select id="server" class="form-control">
              <option>https://api.quarter4.io</option>
            </select>
          </div>
          <div class="mb-3">
            <label for="sport" class="form-label">Sport</label>
            <select id="sport" class="form-control">
              <option value="american-football">American Football</option>
            </select>
          </div>
          <div class="mb-3">
            <label for="game_id" class="form-label"
              >Q4 Game UUID (or all)</label
            >
            <input type="text" id="game_id" class="form-control" value="all" />
          </div>
          <div class="mb-3">
            <label for="api_key" class="form-label">API Key</label>
            <input type="text" id="api_key" class="form-control" value="" />
          </div>
          <div class="mb-3">
            <input type="submit" class="btn btn-primary" />
          </div>
        </form>
      </div>
      <div class="row">
        <ul id="list" class="list-group"></ul>
      </div>
    </div>

    <footer class="text-center text-lg-start bg-light text-muted">
      <div
        class="text-center p-4"
        style="background-color: rgba(0, 0, 0, 0.05)"
      >
        <p>This source provided for example purposes only.</p>
        &copy; 2021 Copyright:
        <a class="text-reset fw-bold" href="https://developer.quarter4.io/"
          >developer.quarter4.io</a
        >
      </div>
    </footer>

    <script>
      var formatted = (item) => {
        return (
          '<li class="list-group-item">' +
          '<img src="' +
          item.game.away.image +
          '">@' +
          '<img src="' +
          item.game.home.image +
          '"> ' +
          item.message +
          "</li>"
        );
      };

      var socket;
      const list = $("#list");

      $("form").submit((e) => {
        e.preventDefault();

        if (socket) {
          socket.disconnect();
        }

        const server = $("#server").val();
        const sport = $("#sport").val();
        const gameId = $("#game_id").val();
        const apiKey = $("#api_key").val();

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

        socket.on("connect_error", (err) => {
          console.log(`connect_error due to ${err.message}`);
          alert(`connect_error due to ${err.message}`);
        });

        socket.on("connect", () => {
          console.log("Connect", socket.connected, socket.id);

          // Join the "room" for the game
          socket.emit(
            // action
            "join",
            // Game UUID
            //'37bc47ee-dd7a-4229-9fc3-b6e7a2d6076c',
            gameId,
            // Acknowledgement
            (history) => {
              // History will be an array of items in the order they were
              // sent to the original stream. Each item is the same as the
              // item in `message`
              console.log(history);
              $.each(history, (index, item) => {
                list.prepend(formatted(item));
              });
            }
          );
        });

        socket.on("disconnect", () => {
          console.log("Disconnect", socket.connected, socket.id);
          list.empty();
        });

        socket.io.on("reconnect_attempt", () => {
          console.log("reconnecting...");
        });

        socket.io.on("reconnect", () => {
          console.log("reconnect");
        });

        socket.on("message", (item) => {
          console.log("message", item, item.message);
          // Add the item.message to the UI
          list.prepend(formatted(item));
        });

        socket.onAny((eventName, ...args) => {
          // catchall for all messages to debug
          console.log("catchall", eventName, args);
        });

        console.log("Attempting to connect to " + server + "...");
        socket.connect();
      });
    </script>
  </body>
</html>