New at Cloudbet

Building A Sports Odds Platform Using Cloudbet Market Helper

This article will provide you with a step by step guide on how you can build a simple sports odds platform using Cloudbet Feeds API and the new Cloudbet Market Helper package.

The Cloudbet Feeds API provides you with the Cloudbet sports odds required for betting markets and game lines comparison. However the Cloudbet API is best used in conjunction with the Cloudbet Market Helper to help parse all the markets and lines. For a detailed introduction on how to use the Cloudbet API, you can read this article.

The Cloudbet Market Helper is an SDK for the Cloudbet Feeds API published on NPM. By using the SDK, you can read the betting markets and lines and build sports odds comparison platforms free of charge. This is especially useful for Cloudbet Affiliates to conveniently visualise odds comparisons for major sports events from different operators on site to highlight Cloudbet’s favorable odds offers to their visitors and build an odds comparison.

Overview of building a sports odds platform

The article is split into two main parts. The first part focuses on how to request markets and events data and the second part explains how to display game lines and odds using Cloudbet Market Helper. For the purpose of providing you with step by step instructions in this article, we have chosen React JS to build the UI, however you can use any other javascript framework of your choice. The final product will look like below:

Cloudbet Odds rendered with Market Helper package

Getting started

Let’s bootstrap from React JS using following commands


   npm init react-app my-app

or


   yarn create react-app my-app

To test the bootstrap run yarn start

Afterwards you will be able to access the page in the browser and see the below screen.

Initial Deployment with Create React App

Now we are ready to implement the odds platform on top of this.

Displaying markets and events

Before we start with the retrieval and display of odds, it’s important to understand how our data is structured.

Concepts

  • Competition – tournaments, such as NBA
  • Event – Contains markets about teams competing. Ex: Cleveland Cavaliers V Chicago Bulls.
  • Market – Contains a list of lines and selections to bet on for a particular event. Ex: Winner market.

Specifying sports

In order to retrieve oddsretrieveodds, we need to specify a sport by using a sport key. You can find a list of sport keys here. Let’s use Soccer, Basketball and American Football keys for this app and load them into a select dropdown.


//App.js
import "./styles.css";
import React from "react";
const sports = ["soccer", "basketball", "american-football"];
export default function App() {
  const [apiKey, setApiKey] = React.useState("");
  const [sport, setSport] = React.useState(sports[0]);

  return (
    <div className="App">
             <div>
              <label for="sport">Sport</label>
              <select value={sport} onChange={(e) => setSport(e.target.value)}>
                {sports.map((s) => (
                  <option value={s}>{s}</option>
                ))}
              </select>
            </div>
          </div>
  );
}

Now you can see the sports in your app as follows.

Cloudbet Sport Keys rendered in a dropdown menu

We need to convert them to sport names. Especially when you want to localise your app for a specific language(s), That’s where the market helper comes into play.

Install Market Helper

First you need to install a market helper SDK in your project.


   npm install @cloudbet/market-helper

or


   yarn add @cloudbet/market-helper

Using Market Helper for sport names

You can use the market-helper package to retrieve proper sport names using the sport key and by passing the locale of your choice. For this app, we will be using en. But you can see what locales are available using the Locale type. Also note that the market-helper package will fallback to en if translations are not available. Let’s use the getSportsName method from the market helper and load the proper names to a select dropdown.


//App.js
import "./styles.css";
import React from "react";
import { Locale, getSportsName } from "@cloudbet/market-helper";
const sports = ["soccer", "basketball", "american-football"];
export default function App() {
  const [sport, setSport] = React.useState(sports[0]);

  return (
    <div className="App">
       <div>
        <label for="sport">Sport</label>
        <select value={sport} onChange={(e) => setSport(e.target.value)}>
          {sports.map((s) => (
            <option value={s}>{getSportsName(s, Locale.en)}</option>
          ))}
        </select>
      </div>
    </div>
  );
}

Now you can see sport names instead of sport keys in the drop down.

 

Cloudbet Sport Names rendered in a dropdown menu

Getting a sport from Cloudbet API

Let’s create a getSport function to fetch sports data using the Cloudbet API.


function getSport(sportKey) {
  return fetch(`https://sports-api.cloudbet.com/pub/v2/odds/sports/${sportKey}`, {
    headers: {
      "X-Api-Key": "[CLOUDBET_FEEDS_API_KEY]",
      "cache-control": "max-age=600"
    }
  });
}

Now we simply need to obtain an API key as below. There are two types of API Keys: Cloudbet Affiliate API Key and Cloudbet Trading API Key.

Generating Cloudbet Trading API Key 

API key for Affiliates

You need an API Key to consume the Cloudbet Feed API and retrieve odds. For that you can use the Affiliate API. It is primarily meant for consuming Cloudbet odds without the ability to place bets programmatically. The retrieved odds may be cached for some time if you opt to use an Affiliate API Key. You can obtain it by signing in as a Cloudbet affiliate and visiting your Affiliates API Token page after signing in.

API key for Trading

Alternatively you can use Cloudbet Trading API Key by logging into your Cloudbet account or by signing up. You can then find your API key in My Account > API section.

This Cloudbet Trading API Key can be used to fetch sports and competitions data and display them using the market-helper package. In addition, you can use the Cloudbet Trading API Key to place bets programmatically. For placing bets, there are requirements such as minimum balance. You can read more about it here.

You can use this to get the sport data from api by specifying the Affiliate API Key in the X-API-Key request header for the sport endpoint . Since it’s an asynchronous call, we have to introduce a loading state as well, to be shown until the data is fetched. Let’s modify the App.js as follows.


//App.js
import "./styles.css";
import React from "react";
import { Locale, getSportsName } from "@cloudbet/market-helper";
function getSport(sport, apiKey) {
  return fetch(`https://sports-api.cloudbet.com/pub/v2/odds/sports/${sport}`, {
    headers: {
      "X-Api-Key": "[CLOUDBET_FEEDS_API_KEY]",
      "cache-control": "max-age=600"
    }
  });
}
const sports = ["soccer", "basketball", "american-football"];
export default function App() {
  const [sport, setSport] = React.useState(sports[0]);
  const [loading, setLoading] = React.useState(false);
  React.useEffect(() => {
    if (!sport) {
      return;
    }
    setLoading(true);
    getSport(sport)
      .then((response) => {
        setLoading(false);
        return response.json();
      })
  }, [sport]);

  return (
    <div className="App">
      <div>
        <label for="sport">Sport</label>
        <select value={sport} onChange={(e) => setSport(e.target.value)}>
          {sports.map((s) => (
            <option value={s}>{getSportsName(s, Locale.ko)}</option>
          ))}
        </select>
      </div>
      {loading && <Loading />}
    </div>
  );
}


function Loading() {
  return <div className="loading">Loading...</div>;
}

Loading competitions from sports response

Each sport has multiple competitions. In order to display odds, we first need to display the competition. Let’s use the competitions in response and display them in a list.


//App.js
import "./styles.css";
import React from "react";
import { Locale, getSportsName } from "@cloudbet/market-helper";
function getSport(sport) {
  return fetch(`https://sports-api.cloudbet.com/pub/v2/odds/sports/${sport}`, {
    headers: {
      "X-Api-Key": "[CLOUDBET_FEEDS_API_KEY]",
      "cache-control": "max-age=600"
    }
  });
}
const sports = ["soccer", "basketball", "american-football"];
export default function App() {
  const [sport, setSport] = React.useState(sports[0]);
  const [loading, setLoading] = React.useState(false);
  const [competitions, setCompetitions] = React.useState([]);
  React.useEffect(() => {
    if (!sport) {
      return;
    }
    setLoading(true);
    getSport(sport)
      .then((response) => {
        setLoading(false);
        return response.json();
      })
      .then((body) => {
        setCompetitions(body.categories.flatMap((c) => c.competitions));
      });
  }, [sport]);

  return (
    <div className="App">
      <div>
        <label for="sport">Sport</label>
        <select value={sport} onChange={(e) => setSport(e.target.value)}>
          {sports.map((s) => (
            <option value={s}>{getSportsName(s, Locale.ko)}</option>
          ))}
        </select>
      </div>
      {loading ? <Loading /> : competitions.map((c) => <div>{c.name}</div>)}
    </div>
  );
}


function Loading() {
  return <div className="loading">Loading...</div>;
}

Now the competitions will be listed as follows.

Competitions List for a Sport Rendered with the Cloudbet Market Helper

Displaying competition data

Each competition has multiple events. We can use the following function to fetch competition data from the API. Note the competition endpoint used here.


function getCompetition(competition, apiKey) {
  return fetch(
    `https://sports-api.cloudbet.com/pub/v2/odds/competitions/${competition}`,
    {
      headers: {
        "X-Api-Key": "[CLOUDBET_FEEDS_API_KEY]",
        "cache-control": "max-age=600"
      }
    }
  );
}

In order to display a competition, let’s create a separate Competition.js component and use the above getCompetition API function to load the events into the competition upon clicking on a competition’s name and display a list of Event IDs as follows.


//Competition.js
import "./styles.css";
import React from "react";
function getCompetition(competition) {
  return fetch(
    `https://sports-api.cloudbet.com/pub/v2/odds/competitions/${competition}`,
    {
      headers: {
        "X-Api-Key": "[CLOUDBET_FEEDS_API_KEY]",
        "cache-control": "max-age=600"
      }
    }
  );
}

export default function Competition({ competition, sportKey }) {
  const [events, setEvents] = React.useState([]);
  const [loading, setLoading] = React.useState(false);
  const [expanded, setExpanded] = React.useState(false);
  const loadEvents = (key) => {
    setExpanded((e) => !e);
    if (events.length || loading) {
      return;
    }
    setLoading(true);
    getCompetition(key)
      .then((response) => response.json())
      .then((body) => {
        setEvents(body.events);
        setLoading(false);
      });
  };
  return (
    <div className="competition">
      <div
        className="competition-title"
        onClick={() => loadEvents(competition.key)}
      >
        {competition.name}
      </div>
      {expanded && (
        <div>
          {loading ? (
            <Loading />
          ) : (
            events.map((e) => <div>{e.id}</div>)
          )}
        </div>
      )}
    </div>
  );
}

function Loading() {
  return <div className="loading">Loading...</div>;
}

Now import the Competition and modify the JSX in App.js to display the event under competition as follows.


//App.js
{loading ? <Loading /> : competitions.map((c) => (
        <Competition
          competition={c}
          apiKey={apiKey}
          key={c.key}
          sportKey={sport}
        />
      ))}

Now you can see a list of Event IDs upon clicking into a competition.

Displaying game lines and odds

Event Data from the API will contain the following items.

  • Line – Odds for multiple outcomes of a given market.
  • Outcome – Possible result for a market. Ex: Home Team wins, Away Team wins or draws.
  • Odds – Out of all outcomes, likelihood of a particular outcome to occur in the game.

Each competition has multiple events that consist of lines for different markets. In order to read and display odds for these events, we need to specify the markets as follows using the  MarketType and use getMarket methods from the market-helper package and pass the event to get the market details as follows.

Displaying odds for simple markets

Let’s first start with one simple market from each sport.

A Market has multiple lines that consist of outcomes. Each outcome has variables and back  values. Variables will contain either of the following.

Result – Market outcome to be a given competitor winning, losing or draw.

total – Market outcome to be over or under a total value. Ex. Total goals to be over/under 1

handicap – Asian Handicap value for the market. See handicap betting for more details.


import {
    MarketType,
    getMarket
} from "@cloudbet/market-helper";

const sportMarkets = {
    "soccer": [MarketType.soccer_match_odds],
    "basketball": [MarketType.basketball_1x2],
    "american-football": [MarketType.american_football_quarter_total]
};
function getMarkets(event, sportKey) {
    const [markets] = getMarket(event, sportMarkets[sportKey][0]);
    return markets;
}

Now let’s adopt the above code and create a new Event.js component to load and display the odds for these markets. We will be using a back price for each outcome. Back price is the value for the outcome that you’re backing. Ex. You’re backing a particular team to have a number of goals above given value at the end of first half by betting on it. Market type for that would be soccer_total_goals_period_first_half.


//Event.js
import "./styles.css";
import React from "react";
import {
  MarketType,
  getMarket,
} from "@cloudbet/market-helper";
const sportMarkets = {
  soccer: [MarketType.soccer_match_odds],
  basketball: [MarketType.basketball_1x2],
  tennis: [MarketType.tennis_winner]
};

export default function Event({ event, sportKey }) {
  const eventMarkets = React.useMemo(() => {
    const [markets, err] = getMarket(event, sportMarkets[sportKey][0]);
    if (err) {
      return [];
    }
    return markets;
  }, [event, sportKey]);
  if (!eventMarkets || !eventMarkets.length) {
    return null;
  }
  return (
    <div>
      <div className="event-title">{event.name}</div>
      {eventMarkets.map((m) => {
        const line = m.lines[0];
        if (!line) {
          return null;
        }
        return (
          <div className="selections">
            {line.map((outcome) => (
              <div className="selection">
                {outcome.name} <br />
                <div className="price">{outcome.back.price}</div>
              </div>
            ))}
          </div>
        );
      })}
    </div>
  );
}

Finally we can use the event component in the previously created Competition component and display the odds by importing the Event component and modifying the JSX as below.


//Competition.js
{expanded && (
        <div>
          {loading ? (
            <Loading />
          ) : (
            events.map((e) => (
              <Event event={e} key={e.id} sportKey={sportKey} />
            ))
          )}
        </div>
      )}

Displaying game line for game final result

Let’s select Soccer from the dropdown and you can see the line for the game result as follows for each event.

Here, the getMarket function from the market-helper package will populate the outcome name and provide you the odds value so you can get the line and display it.

Displaying line for over/under

Let’s select American Football from the dropdown and you can see the lines for the game result populated into markets returned by getMarket function as follows.

Displaying Handicap lines

Now let’s improve our odds platform by adding more lines. For that, I am going to use the Soccer Asian Handicap market. Let’s assign it to the soccer market config as follows.


//Event.js
const sportMarkets = {
  'soccer': [MarketType.soccer_match_odds, MarketType.soccer_asian_handicap],
  'basketball': [MarketType.basketball_1x2],
  'american-football': [MarketType.american_football_quarter_total]
};

Modify the React Memo to load all the lines of all the markets of selected sport.


//Event.js
const eventMarkets = React.useMemo(() => {
    let markets = [];
    sportMarkets[sportKey].forEach(market => {
      const [current, err] = getMarket(event, market);
      if(!err){
        markets = markets.concat(current);
      }
    })
    return markets;
  }, [event, sportKey]);

Now if you select soccer from the dropdown, you will see both final result and handicap game lines displayed together as follows.

After we specify  the handicap market, getMarket populate the handicap value of each outcome into market.name and provide lines based on different handicap values. First entry in the lines array is the primary line. Alternative lines are sorted based on Handicap value in asc order. But this does not apply for ICE Hockey or Baseball where the run line 1.5 is the primary line.

Now let’s use additional data populated in the market from getMarket and display market names above respective lines by modifying JSX in the Event component.


//Event.js
return (
    <div>
      <div className="event-title">{event.name}</div>
      {eventMarkets.map((market) => {
        if (!market.lines.length) {
          return null;
        }
        const lines =  market.lines.map(line => (
          <div className="selections">
            {line.map((outcome) => (
              <div className="selection">
                {outcome.name} <br />
                {outcome.back.price}
              </div>
            ))}
          </div>
        ));
        return (
          <>
            {market.name}<br/>
            {lines}
          </>
        )
      })}
    </div>
  );

Now you will have lines categorised by market name as below..

Displaying outrights

We can further modify this to display outright markets. Let’s add american football outrights market to market config.


//Event.js
const sportMarkets = {
  'soccer': [MarketType.soccer_match_odds, MarketType.soccer_asian_handicap],
  'basketball': [MarketType.basketball_1x2],
  'american-football': [MarketType.american_football_quarter_total, MarketType.american_football_outright]
};

Now the market-helper package will populate outright markets for the competition with other markets and display them as follows after quarter total lines.

Displaying game lines in one view

For some markets, instead of separating the lines, we can rearrange the outcomes to show the lines in one view. In order to do that, let’s first modify the basketball market config as follows.


//Event.js
const sportMarkets = {
  'soccer': [MarketType.soccer_match_odds, MarketType.soccer_asian_handicap],
  'basketball': [MarketType.basketball_1x2, MarketType.basketball_handicap, MarketType.basketball_totals, MarketType.basketball_moneyline],
  'american-football': [MarketType.american_football_quarter_total, MarketType.american_football_outright]
};

Now you will see these additional markets displayed as follows.

Let’s create a new component to arrange this view as follows to show game lines in one view. First, separate the three markets out as game line markets.


const sportMarketsGameLine = {
  'basketball': [MarketType.basketball_handicap, MarketType.basketball_totals, MarketType.basketball_moneyline]
};

Create a new React Memo to store the odds data needed to render in a new view. We consider the first outcome (array element) in the line as the home team and second one as the away team.


const [ headers, home, away ] = React.useMemo(() => {
    const headers = ['Competitor'];
    const home = [homeName];
    const away = [awayName];
    if(sportMarketsGameLine[sportKey]){
      sportMarketsGameLine[sportKey].forEach(market => {
        const [current, err] = getMarket(event, market);
        if(!err && current[0]){
          headers.push(current[0].name);
          const { variables: { handicap: handicapHome, total: totalHome }, back: { price: priceHome } } = current[0].lines[0][0];
          const { variables: { handicap: handicapAway, total: totalAway }, back: { price: priceAway } } = current[0].lines[0][0];
          home.push({ name: handicapHome || totalHome, price: priceHome });
          away.push({ name: handicapAway || totalAway, price: priceAway });
        }
      })
    }
    return [ headers, home, away ];
  }, [event, sportKey, homeName, awayName]);

Now let’s combine these and create a GameLines component as follows.


//GameLines.js
import "./styles.css";
import React from "react";
import {
  MarketType,
  getMarket,
} from "@cloudbet/market-helper";

const sportMarketsGameLine = {
  'basketball': [MarketType.basketball_handicap, MarketType.basketball_totals, MarketType.basketball_moneyline]
};
export default function GameLines({ event, sportKey }) {
  const homeName = event.home.name;
  const awayName = event.away.name;

  const [ headers, home, away ] = React.useMemo(() => {
     //...Memo logic from above goes here
     return [ headers, home, away ];
  }, [event, sportKey, homeName, awayName]);

  if (!headers.length || !home.length || !away.length) {
    return null;
  }
  return (
    <div>
      {<div className="game-lines-title">{'Game Lines'}</div>}
      {<div className="selections">
          {headers.map((header) => (
            <div className="header-text">
              {header}
            </div>
          ))}
      </div>}
      {<div className="selections">
          {home.map((value, index) => (
            index === 0 ? (
              <div className="selection">
                {value}
              </div>
            ) : (
              <div className="selection">
                {value.name} <br />
                <div className="price">{value.price}</div>
              </div>
            )
          ))}
      </div>}
      {<div className="selections">
          {away.map((value, index) => (
            index === 0 ? (
              <div className="selection">
                {value}
              </div>
            ) : (
              <div className="selection">
                {value.name} <br />
                <div className="price">{value.price}</div>
              </div>
            )
          ))}
      </div>}
    </div>
  );
}

Modify the JSX in the Event component by adding the GameLine component at the bottom.


//Event.js
return (
    <div>
      {/* Remaining jsx*/}
      <GameLines event={event} sportKey={sportKey}/>
    </div>

Now let’s select Basketball and see how the Game Lines view  is rendered.

Available markets and sports

For a full list of markets and sports, please see this Github gist.

Share this post

Share on Facebook Share on X