- Published at
Scraping API-sports for automated betting notifications

Here is my approach to automatic a betting strategy that made me DOZENS of Danish Kroner
- Authors
-
-
- Name
- Joachim Bülow
- Cofounder and CTO at Doubble
-
Table of Contents
Introduction
Recently, I realized that whenever football matches reach 3 goals in the first half, odds for goals in the second half are inflated due to the momentum of the game.
Bookmakers forget that people in the halftime become complacent when ahead, and defensive when behind.
So I needed a way to get notified when these key events occur.
I devised a plan to run a lambda function periodically and email notify me if any matches lived up to my criteria.
Services
For a sports API, I chose API-sports because they have a free tier of up to 100 API calls per day, And a very easy to use and flexible API.
For emails, I used MailerSend, as they have 12000 emails per month for free.
For hosting I chose AWS Lambda
. I schedules this with using AWS EventBridge Schedule
.

The architecture of the automated betting notifier
Now that is what i would call

Free money
Code snippets
API-sports client
package apisports
import (
"encoding/json"
"fmt"
"io"
"net/http"
"github.com/joachimbulow/autobetter/src"
)
// Get a response from the API
func getResponse(url string) (*[]byte, error) {
client := &http.Client{}
req, err := http.NewRequest("GET", url, nil)
if err != nil {
fmt.Println(err)
return nil, err
}
req.Header.Add("x-apisports-key", "MY_API_KEY")
res, err := client.Do(req)
if err != nil {
fmt.Println(err)
return nil, err
}
defer res.Body.Close()
body, err := io.ReadAll(res.Body) // Read the stream
if err != nil {
fmt.Println(err)
return nil, err
}
return &body, nil
}
func GetFixtures() (*src.ApiResponse, error) {
var url = "https://v3.football.api-sports.io/fixtures?live=all"
var body, err = getResponse(url)
if err != nil {
fmt.Println(err)
return nil, err
}
// Parse the response
var parsedResponse src.ApiResponse
err = json.Unmarshal(*body, &parsedResponse)
if err != nil {
fmt.Println(err)
return nil, err
}
return &parsedResponse, nil
}
const overUnderLineBetId = 36
func GetLiveOdds(fixtureId string) (*src.BetResponse, error) {
// Endpoint for fetching all events from live fixtures
var url = fmt.Sprintf("https://v3.football.api-sports.io/odds/live?fixture=%s&bet=%d", fixtureId, overUnderLineBetId)
var body, err = getResponse(url)
if err != nil {
fmt.Println(err)
return nil, err
}
// Parse the response
var parsedResponse src.BetResponse
err = json.Unmarshal(*body, &parsedResponse)
if err != nil {
fmt.Println(err)
return nil, err
}
return &parsedResponse, nil
}
Lambda function
package main
import (
"bytes"
"fmt"
"slices"
"time"
// Local modules
"github.com/aws/aws-lambda-go/lambda"
"github.com/joachimbulow/autobetter/src"
"github.com/joachimbulow/autobetter/src/apisports"
"github.com/joachimbulow/autobetter/src/emails"
)
var accepted_league_ids = []int{
135, // Series A
39, // Premier League
78, // Bundesliga
140, // La Liga
88, // Eredivisie
61, // Ligue 1
}
const halftimeLeewayMinutes = 30
const halfTimeMinutes = 45
const minGoals = 3
func main() {
lambda.Start(handler)
// handler()
}
func handler() {
apiResponse, err := apisports.GetFixtures()
if err != nil {
fmt.Println(err)
return
}
// Filter out the events from the leagues we care about
var relevantFixtures []src.FixtureResponse
for _, fixture := range apiResponse.Response {
if slices.Contains(accepted_league_ids, fixture.League.ID) {
relevantFixtures = append(relevantFixtures, fixture)
}
}
var fixturesWithinRequirements []src.FixtureResponse
for _, fixture := range relevantFixtures {
timeStartedUtc, err := time.Parse(time.RFC3339, fixture.Fixture.Date)
if err != nil {
fmt.Println(err)
return
}
now := time.Now()
beforeHalfTime := timeStartedUtc.Add(time.Minute * (halfTimeMinutes + halftimeLeewayMinutes)).After(now)
anyTeamThreePlusGoals := *fixture.Goals.Away >= minGoals || *fixture.Goals.Home >= minGoals
if beforeHalfTime && anyTeamThreePlusGoals {
fixturesWithinRequirements = append(fixturesWithinRequirements, fixture)
}
}
if len(fixturesWithinRequirements) < 1 {
fmt.Println("No fixtures within requirements")
return
}
var buffer bytes.Buffer
buffer.WriteString(`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>New Cool Bets Available</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f9f9f9;
color: #333;
margin: 0;
padding: 20px;
}
.container {
max-width: 600px;
margin: 0 auto;
background: #fff;
padding: 20px;
border: 1px solid #ddd;
border-radius: 5px;
}
.header {
text-align: center;
font-size: 24px;
margin-bottom: 20px;
}
.fixture {
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
}
.fixture:last-child {
border-bottom: none;
}
</style>
</head>
<body>
<div class="container">
<div class="header">New Cool Bets Available</div>
`)
for _, fixture := range fixturesWithinRequirements {
// Test the odds thingy
odds, _ := apisports.GetLiveOdds(fmt.Sprint(fixture.Fixture.ID))
goalsScored := *fixture.Goals.Away + *fixture.Goals.Home
value := "Under"
handicap := fmt.Sprintf("%g", (float64(goalsScored+1) + 0.5))
var oddsValue string
for _, betValue := range odds.Response[0].Odds[0].Values {
if betValue.Value == value && betValue.Handicap != nil && *betValue.Handicap == handicap {
oddsValue = betValue.Odd
}
}
buffer.WriteString(`
<div class="fixture">
<h2>Match: ` + fixture.Teams.Home.Name + ` vs ` + fixture.Teams.Away.Name + `</h2>
<strong>` + fmt.Sprint(*fixture.Goals.Home) + ` - ` + fmt.Sprint(*fixture.Goals.Away) + `</strong>
<p>Under ` + handicap + ` goals gives odds: ` + oddsValue + `</p>
</div>
`)
}
buffer.WriteString(`
</div>
</body>
</html>
`)
emails.SendEmail("New cool bets available", buffer.String())
}
Email client
package emails
import (
"context"
"fmt"
"time"
"github.com/mailersend/mailersend-go"
)
type EmailRequest struct {
Recipients []string `json:"recipients"`
Subject string `json:"subject"`
Content string `json:"content"`
}
func SendEmail(subject string, content string) {
ms := mailersend.NewMailersend("MY_API_KEY")
ctx := context.Background()
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
text := "There are news from the auto better..."
from := mailersend.From{
Name: "Byllefar Productions",
Email: "automation@joachimbulow.net",
}
recipients := []mailersend.Recipient{
{
Name: "Joachim Bülow",
Email: "joachimbulow@hotmail.dk",
},
{
Name: "Rasmus Topp",
Email: "rasmus_topp@hotmail.com",
},
{
Name: "Dennis Rosenlund",
Email: "Dennis.rosenlund@hotmail.com",
},
}
tags := []string{"foo", "bar"}
message := ms.Email.NewMessage()
message.SetFrom(from)
message.SetSubject(subject)
message.SetHTML(content)
message.SetText(text)
message.SetTags(tags)
for _, recipient := range recipients {
message.SetRecipients([]mailersend.Recipient{recipient})
_, err := ms.Email.Send(ctx, message)
if err != nil {
fmt.Println(err)
return
}
}
fmt.Println("Emails were sent successfully")
}