Tutorials

Build a Live Odds App: Architecture & Code

Streaming odds, price updates, line movement alerts with complete API examples.

2025-10-21· 5 min read

Overview

In this tutorial, you'll learn how to build a live odds application using the API 4 Sports platform. We'll cover fetching live odds, pre-match odds, and implementing real-time price updates.

Available Odds Endpoints

API 4 Sports provides two main endpoints for odds data:

  • /football/live-odds — Real-time odds for currently live matches
  • /football/pre-match-odds — Pre-match odds for upcoming matches (max 5 days range)
  • /tennis/live-odds — Live odds for tennis matches
  • /tennis/pre-match-odds — Pre-match odds for tennis (max 5 days range)

Fetching Live Odds (Football)

Get real-time odds for all currently live football matches. You can filter by match_id, country_id, or league_id.

cURL Request

bash
curl -X GET "https://api.api4sports.com/api/football/live-odds?league_id=207" \
  -H "X-Api-Key: your_api_key_here"

JavaScript Implementation

javascript
async function getLiveOdds(options = {}) {
  const params = new URLSearchParams();
  if (options.matchId) params.append('match_id', options.matchId);
  if (options.leagueId) params.append('league_id', options.leagueId);
  if (options.countryId) params.append('country_id', options.countryId);
  
  const url = `https://api.api4sports.com/api/football/live-odds?${params}`;
  const response = await fetch(url, {
    headers: { 'X-Api-Key': process.env.API_KEY }
  });
  
  const data = await response.json();
  return data.result;
}

// Usage - Get all live odds
const allLiveOdds = await getLiveOdds();

// Get odds for Premier League only
const premierLeagueOdds = await getLiveOdds({ leagueId: 207 });

// Get odds for a specific match
const matchOdds = await getLiveOdds({ matchId: 59409 });

Python Implementation

python
import requests

def get_live_odds(match_id=None, league_id=None, country_id=None):
    url = "https://api.api4sports.com/api/football/live-odds"
    headers = {"X-Api-Key": "your_api_key_here"}
    params = {}
    
    if match_id:
        params["match_id"] = match_id
    if league_id:
        params["league_id"] = league_id
    if country_id:
        params["country_id"] = country_id
    
    response = requests.get(url, headers=headers, params=params)
    return response.json().get("result", [])

# Usage
live_odds = get_live_odds(league_id=207)
for match in live_odds:
    print(f"Match {match['match_id']}: {match['live_odds']}")

Live Odds Response

json
{
  "success": 1,
  "result": [
    {
      "match_id": 59409,
      "live_odds": {
        "1X2": {
          "Home": "1.85",
          "Draw": "3.50",
          "Away": "2.10"
        },
        "Over/Under": {
          "Over 2.5": "1.90",
          "Under 2.5": "1.90"
        },
        "Both Teams to Score": {
          "Yes": "1.75",
          "No": "2.05"
        }
      }
    }
  ]
}

Fetching Pre-Match Odds

Get odds for upcoming matches within a date range. Maximum 5 days range is supported.

cURL Request

bash
curl -X GET "https://api.api4sports.com/api/football/pre-match-odds?from=2024-01-15&to=2024-01-20" \
  -H "X-Api-Key: your_api_key_here"

JavaScript Implementation

javascript
async function getPreMatchOdds(from, to, matchId = null) {
  const params = new URLSearchParams({ from, to });
  if (matchId) params.append('match_id', matchId);
  
  const response = await fetch(
    `https://api.api4sports.com/api/football/pre-match-odds?${params}`,
    { headers: { 'X-Api-Key': process.env.API_KEY } }
  );
  
  const data = await response.json();
  return data.result;
}

// Usage
const odds = await getPreMatchOdds('2024-01-15', '2024-01-20');

// Iterate through matches
for (const [matchId, markets] of Object.entries(odds)) {
  console.log(`Match ${matchId}:`);
  for (const [market, selections] of Object.entries(markets)) {
    console.log(`  ${market}:`, selections);
  }
}

Pre-Match Odds Response

json
{
  "success": 1,
  "result": {
    "1625280": {
      "Match Winner": {
        "Home": {
          "bet365": "3.10",
          "Marathon": "3.16",
          "1xBet": "3.21"
        },
        "Draw": {
          "bet365": "3.50",
          "Marathon": "3.54"
        },
        "Away": {
          "bet365": "2.25",
          "Marathon": "2.31"
        }
      },
      "Correct Score": {
        "1:0": { "bet365": "8.00", "Marathon": "6.35" },
        "2:1": { "bet365": "11.00", "Marathon": "8.50" },
        "0:0": { "bet365": "10.00" }
      },
      "Over/Under 2.5": {
        "Over": { "bet365": "1.90" },
        "Under": { "bet365": "1.90" }
      }
    }
  }
}

Tennis Live Odds

Similar endpoints exist for tennis matches. Filter by match_id or tournament_id.

Tennis Live Odds Request

bash
curl -X GET "https://api.api4sports.com/api/tennis/live-odds?tournament_id=11277" \
  -H "X-Api-Key: your_api_key_here"

Tennis Live Odds Response

json
{
  "success": 1,
  "result": [
    {
      "match_id": 12345,
      "live_odds": {
        "Match Winner": {
          "1": { "bet365": "1.85", "Marathon": "1.90" },
          "2": { "bet365": "2.10" }
        },
        "Set Winner": {
          "1": { "bet365": "1.70" },
          "2": { "bet365": "2.20" }
        }
      }
    }
  ]
}

Real-Time Odds Polling

For live odds, implement polling with short intervals (2-5 seconds) to capture price movements.

Odds Polling Class

javascript
class OddsPoller {
  constructor(apiKey, sport = 'football') {
    this.apiKey = apiKey;
    this.sport = sport;
    this.intervalId = null;
    this.lastOdds = new Map();
  }
  
  async fetchLiveOdds(leagueId = null) {
    const params = leagueId ? `?league_id=${leagueId}` : '';
    const response = await fetch(
      `https://api.api4sports.com/api/${this.sport}/live-odds${params}`,
      { headers: { 'X-Api-Key': this.apiKey } }
    );
    return (await response.json()).result;
  }
  
  detectChanges(newOdds) {
    const changes = [];
    
    for (const match of newOdds) {
      const lastMatch = this.lastOdds.get(match.match_id);
      if (!lastMatch) {
        changes.push({ type: 'new', match });
        continue;
      }
      
      // Compare odds and detect movements
      const oddsChanges = this.compareOdds(lastMatch.live_odds, match.live_odds);
      if (oddsChanges.length > 0) {
        changes.push({ type: 'update', match, changes: oddsChanges });
      }
    }
    
    // Update cache
    newOdds.forEach(m => this.lastOdds.set(m.match_id, m));
    return changes;
  }
  
  compareOdds(oldOdds, newOdds) {
    const changes = [];
    for (const [market, selections] of Object.entries(newOdds)) {
      for (const [selection, price] of Object.entries(selections)) {
        const oldPrice = oldOdds?.[market]?.[selection];
        if (oldPrice && oldPrice !== price) {
          changes.push({
            market,
            selection,
            oldPrice: parseFloat(oldPrice),
            newPrice: parseFloat(price),
            direction: parseFloat(price) > parseFloat(oldPrice) ? 'up' : 'down'
          });
        }
      }
    }
    return changes;
  }
  
  start(callback, intervalMs = 3000, leagueId = null) {
    const poll = async () => {
      const odds = await this.fetchLiveOdds(leagueId);
      const changes = this.detectChanges(odds);
      callback(odds, changes);
    };
    
    poll();
    this.intervalId = setInterval(poll, intervalMs);
  }
  
  stop() {
    if (this.intervalId) clearInterval(this.intervalId);
  }
}

// Usage
const poller = new OddsPoller(process.env.API_KEY);
poller.start((odds, changes) => {
  if (changes.length > 0) {
    console.log('Price movements detected:', changes);
  }
}, 3000, 207); // Poll Premier League every 3 seconds

UI/UX Patterns for Odds Display

  • Highlight price increases with green/up arrow
  • Highlight price decreases with red/down arrow
  • Show last update timestamp and connection status
  • Animate price changes with subtle transitions
  • Display bookmaker comparison in a grid layout
  • Show line movement history for each selection

Architecture Best Practices

  • Use short polling intervals (2-5s) for live odds
  • Cache pre-match odds with longer TTL (1-5 minutes)
  • Implement backpressure when API rate limits are approached
  • Batch UI updates to reduce re-renders
  • Use edge regions close to your users for lower latency

Error Handling

javascript
async function fetchOddsWithRetry(url, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch(url, {
        headers: { 'X-Api-Key': process.env.API_KEY }
      });
      
      if (response.status === 429) {
        // Rate limited - wait and retry
        const waitTime = Math.pow(2, attempt) * 1000;
        console.log(`Rate limited, waiting ${waitTime}ms...`);
        await new Promise(r => setTimeout(r, waitTime));
        continue;
      }
      
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }
      
      return await response.json();
    } catch (error) {
      if (attempt === maxRetries) throw error;
      await new Promise(r => setTimeout(r, 1000 * attempt));
    }
  }
}

Next Steps

Combine live odds with match data from the Live Scores API to build a complete betting companion app. See our Best Practices guide for production deployment tips.