Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.fieldfunded.com/llms.txt

Use this file to discover all available pages before exploring further.

Build a Sports Betting Bot with Node.js

Build a Node.js bot that monitors live odds across 30+ sports, detects value opportunities based on your criteria, and automatically settles bets after matches end. This is a complete tutorial with working code you can run today.

What the Bot Does

Prerequisites

Step 1: API Client

// bot/api.js
const BASE_URL = "https://api.fieldfunded.com/v1";
const API_KEY = process.env.FIELDFUNDED_API_KEY;

async function apiGet(path, params = {}) {
  const url = new URL(`${BASE_URL}${path}`);
  Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));

  const res = await fetch(url, {
    headers: { "X-API-Key": API_KEY },
  });

  if (!res.ok) {
    if (res.status === 429) {
      const retry = res.headers.get("Retry-After") || 1;
      console.log(`Rate limited. Retrying in ${retry}s...`);
      await new Promise((r) => setTimeout(r, retry * 1000));
      return apiGet(path, params);
    }
    throw new Error(`API ${res.status}: ${await res.text()}`);
  }
  return res.json();
}

async function apiPost(path, body) {
  const res = await fetch(`${BASE_URL}${path}`, {
    method: "POST",
    headers: {
      "X-API-Key": API_KEY,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(body),
  });
  if (!res.ok) throw new Error(`API ${res.status}: ${await res.text()}`);
  return res.json();
}

module.exports = { apiGet, apiPost };

Step 2: Odds Monitor

Track odds movements and detect significant changes:
// bot/monitor.js
const { apiGet } = require("./api");

const oddsHistory = new Map(); // key -> [{ odds, timestamp }]

async function scanEvents(sport = "soccer") {
  const data = await apiGet("/events", { sport });
  const alerts = [];

  for (const event of data.events.slice(0, 10)) {
    const odds = await apiGet(`/events/${event.id}/odds`);

    for (const market of odds.markets) {
      if (!market.name.toLowerCase().includes("winner")) continue;

      for (const sel of market.selections) {
        const key = `${event.id}_${market.id}_${sel.id}`;
        const history = oddsHistory.get(key) || [];
        const current = sel.odds;

        // Record history
        history.push({ odds: current, timestamp: Date.now() });
        if (history.length > 100) history.shift(); // Keep last 100
        oddsHistory.set(key, history);

        // Detect drift
        if (history.length >= 2) {
          const previous = history[history.length - 2].odds;
          const change = ((current - previous) / previous) * 100;

          if (Math.abs(change) >= 5) {
            alerts.push({
              event: `${event.home_team} vs ${event.away_team}`,
              market: market.name,
              selection: sel.name,
              previous,
              current,
              change: change.toFixed(1),
              direction: change > 0 ? "DRIFTED" : "SHORTENED",
            });
          }
        }
      }
    }

    // Brief pause between events to respect rate limits
    await new Promise((r) => setTimeout(r, 200));
  }

  return alerts;
}

module.exports = { scanEvents };

Step 3: Settlement Checker

After matches end, check if bets won:
// bot/settler.js
const { apiPost } = require("./api");

// In-memory bet tracker (use a database in production)
const activeBets = [];

function placeBet(bet) {
  activeBets.push({
    ...bet,
    status: "pending",
    placedAt: new Date().toISOString(),
  });
  console.log(
    `[BET] ${bet.selection} @ ${bet.odds} — $${bet.stake} on ${bet.event}`
  );
}

async function settleBets() {
  const pending = activeBets.filter((b) => b.status === "pending");
  if (pending.length === 0) return;

  console.log(`\n[SETTLE] Checking ${pending.length} pending bets...`);

  for (const bet of pending) {
    try {
      const result = await apiPost("/bets/check", {
        event_id: bet.eventId,
        market: bet.market,
        selection: bet.selection,
        stake: bet.stake,
        odds: bet.odds,
        market_id: bet.marketId,
        selection_id: bet.selectionId,
      });

      if (result.status !== "pending") {
        bet.status = result.status;
        bet.payout = result.payout || 0;
        const emoji = result.status === "won" ? "✅" : "❌";
        console.log(
          `${emoji} ${bet.selection}: ${result.status} — ` +
            `Payout: $${bet.payout}`
        );
      }
    } catch (err) {
      // Event not finished yet — try again later
    }
  }
}

module.exports = { placeBet, settleBets, activeBets };

Step 4: Main Loop

// bot/index.js
const { scanEvents } = require("./monitor");
const { settleBets } = require("./settler");

const POLL_INTERVAL = 30_000; // 30 seconds
const SPORTS = ["soccer", "basketball", "tennis"];

async function run() {
  console.log("=== Sports Odds Bot Started ===\n");

  setInterval(async () => {
    for (const sport of SPORTS) {
      try {
        const alerts = await scanEvents(sport);

        for (const alert of alerts) {
          console.log(
            `[${alert.direction}] ${alert.event} — ` +
              `${alert.selection}: ${alert.previous}${alert.current} ` +
              `(${alert.change}%)`
          );
        }
      } catch (err) {
        console.error(`Error scanning ${sport}:`, err.message);
      }
    }
  }, POLL_INTERVAL);

  // Check settlements every 5 minutes
  setInterval(async () => {
    try {
      await settleBets();
    } catch (err) {
      console.error("Settlement error:", err.message);
    }
  }, 300_000);
}

run();

Step 5: Run It

export FIELDFUNDED_API_KEY="your_key_here"
node bot/index.js
Output:
=== Sports Odds Bot Started ===

[DRIFTED] Arsenal vs Chelsea — Arsenal: 1.85 → 2.00 (+8.1%)
[SHORTENED] Lakers vs Celtics — Celtics: 1.95 → 1.80 (-7.7%)
✅ Liverpool: won — Payout: $105.00

Rate Limit Math

ConfigurationRequests/cycleCycles/dayDaily totalMonthlyPlan
3 sports × 10 events, 30s poll332,88095,0402,851,200Ultra ($99.99)
1 sport × 5 events, 60s poll61,4408,640259,200Starter ($29)
1 sport × 3 events, 5-min poll42881,15234,560Free
For a hobby bot monitoring a few matches, the free tier is enough. Scale up to Starter or Pro when you need more sports or faster polling.

Production Improvements

Before running this bot 24/7, consider:
  • Store bets in SQLite or PostgreSQL instead of memory
  • Add Discord/Telegram notifications for alerts
  • Implement exponential backoff on API errors
  • Add a web dashboard to view bot status
  • Log all odds snapshots for historical analysis

Get Your Free API Key

Start building in 5 minutes — 10,000 free requests/month

See Pricing

All plans compared side by side