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 Chrome Extension That Shows Odds on Sports Articles

Reading a match preview on ESPN or BBC Sport? This extension detects team names and injects real-time odds directly into the page.

What You’ll Use

SDK MethodEndpointPurpose
search()GET /v1/searchMatch team names found in articles
getEventOdds()GET /v1/events/{id}/oddsGet odds for matched events

How It Works

User reads ESPN article about "Barcelona vs Real Madrid"

Content script detects team names

Background script calls search("Barcelona")

Gets odds for the matched event

Injects tooltip next to team names showing odds

Step 1: Manifest V3

{
  "manifest_version": 3,
  "name": "Sports Odds Overlay",
  "version": "1.0",
  "description": "See live odds while reading sports articles",
  "permissions": ["storage"],
  "host_permissions": [
    "https://api.fieldfunded.com/*"
  ],
  "content_scripts": [
    {
      "matches": [
        "*://*.espn.com/*",
        "*://*.bbc.com/sport/*",
        "*://*.skysports.com/*"
      ],
      "js": ["content.js"],
      "css": ["overlay.css"]
    }
  ],
  "background": {
    "service_worker": "background.js"
  },
  "options_page": "options.html"
}

Step 2: Background Script (API Calls)

// background.js — handles all API communication
const API_KEY = ''; // Set via options page
const BASE_URL = 'https://api.fieldfunded.com/v1';

// Cache to avoid repeated API calls
const cache = new Map<string, any>();

chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
  if (msg.type === 'SEARCH_TEAM') {
    const cached = cache.get(msg.team);
    if (cached) {
      sendResponse(cached);
      return true;
    }

    fetch(`${BASE_URL}/search?q=${encodeURIComponent(msg.team)}&limit=1`, {
      headers: { 'X-API-Key': API_KEY },
    })
      .then(r => r.json())
      .then(async (data) => {
        if (data.events && data.events.length > 0) {
          const event = data.events[0];

          // Fetch odds
          const oddsRes = await fetch(`${BASE_URL}/events/${event.id}/odds`, {
            headers: { 'X-API-Key': API_KEY },
          });
          const odds = await oddsRes.json();

          const result = { event, odds };
          cache.set(msg.team, result);

          // Cache expires after 5 minutes
          setTimeout(() => cache.delete(msg.team), 5 * 60 * 1000);

          sendResponse(result);
        } else {
          sendResponse(null);
        }
      });

    return true; // Async response
  }
});

Step 3: Content Script (Team Detection + Tooltip)

// content.js — detects team names and injects tooltips

// Common team names to look for
const TEAMS = [
  'Barcelona', 'Real Madrid', 'Manchester United', 'Manchester City',
  'Liverpool', 'Arsenal', 'Chelsea', 'Juventus', 'Bayern Munich',
  'PSG', 'AC Milan', 'Inter Milan', 'Atletico Madrid', 'Dortmund',
  'Lakers', 'Warriors', 'Celtics', 'Patriots', 'Chiefs',
  // Add more or load dynamically from getSports()/getLeagues()
];

function scanForTeams() {
  const textNodes: Node[] = [];
  const walker = document.createTreeWalker(
    document.body,
    NodeFilter.SHOW_TEXT,
    null
  );

  while (walker.nextNode()) {
    textNodes.push(walker.currentNode);
  }

  const processed = new Set<string>();

  for (const node of textNodes) {
    const text = node.textContent || '';
    for (const team of TEAMS) {
      if (text.includes(team) && !processed.has(team)) {
        processed.add(team);

        // Ask background script for odds
        chrome.runtime.sendMessage(
          { type: 'SEARCH_TEAM', team },
          (result) => {
            if (result && result.odds?.markets?.length > 0) {
              injectTooltip(node, team, result);
            }
          }
        );
      }
    }
  }
}

function injectTooltip(textNode: Node, team: string, data: any) {
  const parent = textNode.parentElement;
  if (!parent) return;

  const html = parent.innerHTML;
  const market = data.odds.markets[0];
  const oddsStr = market.selections
    .map((s: any) => `${s.name}: ${s.odds}`)
    .join(' • ');

  const tooltip = `<span class="ff-odds-trigger">${team}<span class="ff-odds-tooltip">
    <strong>${data.event.home_team} vs ${data.event.away_team}</strong><br>
    ${market.name}: ${oddsStr}
  </span></span>`;

  parent.innerHTML = html.replace(team, tooltip);
}

// Run on page load
scanForTeams();

Step 4: Tooltip CSS

/* overlay.css */
.ff-odds-trigger {
  position: relative;
  border-bottom: 2px dotted #53FC18;
  cursor: pointer;
}

.ff-odds-tooltip {
  display: none;
  position: absolute;
  bottom: 100%;
  left: 50%;
  transform: translateX(-50%);
  background: #1a1a2e;
  color: #ffffff;
  padding: 8px 12px;
  border-radius: 6px;
  font-size: 13px;
  white-space: nowrap;
  z-index: 10000;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
  border: 1px solid #53FC18;
}

.ff-odds-trigger:hover .ff-odds-tooltip {
  display: block;
}

Rate Limit Math

  • Each unique team name found = 2 requests (search + odds)
  • Aggressive caching (5 min) means repeat visits cost 0
  • A typical article mentions 2-4 teams = 4-8 requests per page
  • Free tier handles ~1,000 article reads per month

Search API Reference

See search endpoint docs →

Get Your Free API Key

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