New at Cloudbet

Sports Betting API Tutorial With Golang

Updated 28th October 2021

‍In April 2021, Cloudbet released its public sports API due to high demand from our professional audience.

We’ve started with a minimal set based on current industry standards, but plan to extend it for future use cases and consider socket connections based on feedback. The API is split into three parts to retrieve fixture data and prices on current events, account information and trading to back and lay lines. The latter will be available soon, alongside the cashout piece on the website. Odds comparators may use the feed api to poll prices.

Here, @kgravenreuth showcases the API with his language of choice, Go (Golang).

Get started with the free Cloudbet API

You should deposit the equivalent of 10 EUR in a currency of your choice and can navigate to the API key section in your account menu. You can generate and revoke keys here in case you leak it, but be careful as there is a penalty for rotating keys too often.

After an API Key is generated and available to you, Play EUR test funds will be available. In case you face any issues with these steps, kindly reach out to our glorious customer service team to enable test funds on your account. You can also raise questions on our dedicated Cloudbet API discussion forum where I’ll look into your issues personally.

Please see our swagger API docs that we will use in the following https://www.cloudbet.com/api/.

Get Sports Odds & Data – The Feed API

Relevant if you want to build an odds comparison site or a trading bot.

The Cloudbet Feed API can be used to get real-time sports odds which you can use in applications such as odds comparison sites or as the data ingestion pipeline for Trading Bots. The odds from the Feed API can be used when placing bets with the Trading API subsequently.

Overview

In general you would follow these steps to get the latest odds:

  1. Get details about upcoming events for your desired sports. The Fixtures endpoint can be used to get a list of upcoming events and competitions for a specific sport on a specific date.
  2. Obtain odds for all events of an upcoming competition that you found from the fixtures endpoint by accessing the competitions endpoint. This allows you to batch ingest odds for multiple events if desired. There is also an event endpoint if you want to get odds for a specific event.

We published the protobuf spec from our API on our Cloudbet GitHub page.

Sports Fixtures

Information about listed events is what we refer to as “fixtures”. We break these down into sports, competitions and events that can be queried from the feed API.


import "github.com/Cloudbet/docs/go/cloudbet"

const eventURL = "https://sports-api.cloudbet.com/pub/v2/odds/events/%v"

// …
req, err := http.NewRequest("GET", fmt.Sprintf(eventURL, 123456), nil)
if err != nil {
	return err
}

req.Header.Set("accept", "application/json")
req.Header.Set("X-API-Key", "eyJ...") // <-- use your API key
req.Header.Set("Content-Type", "application/json")

client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
	return err
}

var event cloudbet.Event

body, err := ioutil.ReadAll(resp.Body)
if err != nil {
	return err
}
defer resp.Body.Close()

err = protojson.UnmarshalOptions{DiscardUnknown: true, AllowPartial: true}.Unmarshal(body, &event)
if err != nil {
return err
}

Sports Odds

Events have a list of markets, markets are broken down by event periods e.g. FT (full-time), OT (over time) etc. Inside a market there are one or more lines. A 1×2 market has a single line while a handicap market may have multiple lines with different handicap values. We refer to markets by their market key, for example soccer.asian_handicap. The market exists on the full time result, e.g. period=ft in the market parameters and is identified by its outcome, e.g. home. Some bookmakers refer by a “line id” to the individual selection that bets are placed on. We refer to it as the market URL. The market URL is composed of <marketkey>/<outcome>?<params>. You can find an example below of listing the available market URLs (or line IDs) with their respective limit, price and probability.


var markets []*bookmaker.Market
// traverse markets, e.g. soccer asian handicap
for marketKey, market := range event.GetMarkets() {
	// traverse submarkets, e.g. soccer asian handicap ->
	for submarketKey, submarket := range market.GetSubmarkets() {
		// traverse selections, e.g. home team +0.5
		for _, selection := range submarket.GetSelections() {
			lineID := strings.TrimRight(fmt.Sprintf("%s/%s?%s", marketKey, selection.GetOutcome(), selection.GetParams()), "?")
			fmt.Println("line id:     %v", lineID)
			fmt.Println("max bet:     %v", selection.MaxStake)
			fmt.Println("odds:        %v", selection.Price)
			fmt.Println("probability: %v", selection.Probability)
		}
	}
}

There is a further parameter known from betting exchanges, the side. By default, all bets on the API are backing the outcome. We actually currently support only backing of outcomes but are thinking of allowing you to lay bets via the API as well. On sportsbooks this is what is commonly known as cashouts. You lay an outcome you previously backed to receive the difference instantly back and close out your position. Just like in stock trading. If you would like to see this feature please request it on our Github discussion board where we will note down the interest.

Create a Sports Betting Bot – The Trading API

The above sections are all you need if you want to consume and reuse Cloudbet data, for example for an odds comparison site. In case you want to trade with Cloudbet for arbitrage bots or automated trading strategies keep reading on.

Keep track of balances with the Account API

The first thing for automated betting is knowing about our funds available. Using the endpoint below, you can find out the list of currencies available.


req, err := http.NewRequest("GET", "https://sports-api.cloudbet.com/pub/v1/account/currencies", nil)
if err != nil {
	return err
}

req.Header.Set("accept", "application/json")
req.Header.Set("X-API-Key", "eyJ...") // <-- use your API key
req.Header.Set("Content-Type", "application/json")

client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
	return err
}

bodyText, err := ioutil.ReadAll(resp.Body)
if err != nil {
	return err
}
fmt.Printf("%sn", bodyText) // {"currencies":["BONUS_EUR","BTC","ETH","PLAY_EUR"]}
}

Which allows you to continue querying the balance for a particular currency you want to bet with. Here we query PLAY_EUR, our test funds currency that currently can be enabled on your account upon request.


client := &http.Client{}
req, err := http.NewRequest("GET", "https://sports-api.cloudbet.com/pub/v1/account/currencies/PLAY_EUR/balance", nil)
if err != nil {
    log.Fatal(err)
}

req.Header.Set("accept", "application/json")
req.Header.Set("X-API-Key", "eyJ...") // <-- use your API key
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
    log.Fatal(err)
}

bodyText, err := ioutil.ReadAll(resp.Body)
if err != nil {
    log.Fatal(err)
}

fmt.Printf("%sn", bodyText) // {"amount":"984.69"}

Test betting automation with Play Funds

From the fixtures and odds queries above, assuming we wanted to place our asian handicap market on a soccer event, we’d compose the payload with the market URL, define the price available and desired stake up to what funds we have or what the market is currently limited to. Note that limits increase towards the start time of the event and are highest just before kick off and during in-play.


client := &http.Client{}
var data = []byte(`{{
  "acceptPriceChange": "BETTER",
  "currency": "PLAY_EUR",
  "eventId": "7190563",
  "marketUrl": "soccer.asian_handicap/home?handicap=0.5",
  "price": "1.65",
  "referenceId": "1891d3a0-0af8-11ec-b536-11ca86b8c5a4",
  "stake": "1.1"
}}`)
req, err := http.NewRequest("POST", "https://sports-api.cloudbet.com/pub/v3/bets/place", data)
if err != nil {
    log.Fatal(err)
}
req.Header.Set("accept", "application/json")
req.Header.Set("X-API-Key", "eyJ...") // <-- use your API key
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
    log.Fatal(err)
}

bodyText, err := ioutil.ReadAll(resp.Body)
if err != nil {
    log.Fatal(err)
}

fmt.Printf("%sn", bodyText) // {"referenceId":"1891d3a0-0af8-11ec-b536-11ca86b8c5a4","price":"1.652","eventId":"7190563","marketUrl": "soccer.asian_handicap/home?handicap=0.5","side":"BACK","currency":"PLAY_EUR","stake":"1.1","status":"ACCEPTED","error":""}

NOTE: your bet might get rejected for reasons, with recommended new stake and price on the rejection payload. Please update your payload with a new referenceId and try again with updated fields.

Be aware that you shouldn’t make abusive API requests towards the betting endpoint. If your rejection rate is too high, e.g. (> 80 rejections for your last 100 bets) you may get flagged for review. For an uninterrupted betting experience do make sure to query the betting endpoint sensibly and when you have confidence that the bet is placable. There is a line fetch type endpoint around to grab current odds and limits if need be.

Query the Bet History for Settlements

After a bet is submitted it may take a few seconds in pre match to get accepted. You can poll the bet status endpoint with the request UUID to check the state. During in-play betting usually will take up to 10 seconds, but during danger situations, e.g. a free kick, in soccer we might hold your bet for up to 5 minutes. This is to protect both the bookmaker and the player. In case of a major event the bet will get rejected, otherwise it should get accepted.


client := &http.Client{}
req, err := http.NewRequest("GET", "https://sports-api.cloudbet.com/pub/v3/bets/1891d3a0-0af8-11ec-b536-11ca86b8c5a4/status", nil)
if err != nil {
    log.Fatal(err)
}
req.Header.Set("accept", "application/json")
req.Header.Set("X-API-Key", "eyJ...") // <-- use your API key
resp, err := client.Do(req)
if err != nil {
    log.Fatal(err)
}
bodyText, err := ioutil.ReadAll(resp.Body)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("%sn", bodyText) // {"referenceId":"1891d3a0-0af8-11ec-b536-11ca86b8c5a4","price":"1.652","eventId":"7190563","marketUrl":"soccer.asian_handicap/home?handicap=0.5","side":"BACK","currency":"PLAY_EUR","stake":"1.1","status":"ACCEPTED","returnAmount":"0.0","sportsKey":"soccer","competitionId":"85","categoryKey":"international_clubs","error":""}

Similarly to what you can see on the web UI, we also provide a way to query a chronological bet history of all your accepted bets. For this see the sample query below:


client := &http.Client{}
req, err := http.NewRequest("GET", "https://sports-api.cloudbet.com/pub/v3/bets/history?limit=5&offset=0", nil)
if err != nil {
    log.Fatal(err)
}

req.Header.Set("accept", "application/json")
req.Header.Set("X-API-Key", "eyJ...") // <-- use your API key
resp, err := client.Do(req)
if err != nil {
    log.Fatal(err)
}

bodyText, err := ioutil.ReadAll(resp.Body)
if err != nil {
    log.Fatal(err)
}

fmt.Printf("%sn", bodyText) // {"bets":[{"referenceId":"1891d3a0-0af8-11ec-b536-11ca86b8c5a4","price":"1.652","eventId":"7190563","marketUrl":"soccer.asian_handicap/home? handicap=0.5","side":"BACK","currency":"PLAY_EUR","stake":"1.1","status":"ACCEPTED", "returnAmount":"0","eventName":"Sevilla FC V FC Salzburg", "sportsKey":"soccer","competitionId":"85","categoryKey":"international_clubs","error":""},{"referenceId":"300f1f90-a182-11eb-ae26-db06bcd7e230", "price":"1.377","eventId":"5943646","marketUrl":"soccer.match_odds/home","side": "BACK","currency":"PLAY_EUR","stake":"1","status":"LOSS","returnAmount":"-1","eventName":"UE Santa Coloma A V CE Carroi","sportsKey":"soccer","competitionId":"488","categoryKey":"andorra","error":""}],"totalBets":"2"}

‍Additional Resources

These are some additional resources for you to explore the Cloudbet API further.

Feedback & Q/A

We encourage everyone to use Github Discussions for any technical questions and feature requests: https://github.com/Cloudbet/docs/discussions.

If you want to show off your skills, bet shares include an API tag.

Share this post

Share on Facebook Share on X