Skip to content

JavaScript Workflow Examples

Live Update (HugeAPI)

javascript
const headers = {
    "x-portal-apikey": "__YOUR_KEY__",
};

const baseUrl = "https://pinnacle-odds-api.hgapi.top";

const sinceItems = {};
const allEvents = {};

async function liveUpdate() {
    for (const sportId of [1]) {  // endpoint /kit/v1/sports
        // is_have_odds = 0 or 1, event_type = prematch, live
        const params = new URLSearchParams({
            sport_id: sportId,
            is_have_odds: true,
            ...(sinceItems[sportId] && { since: sinceItems[sportId] })
        });

        console.log('[REQUEST]', Object.fromEntries(params));

        const response = await fetch(`${baseUrl}/kit/v1/markets?${params}`, {
            headers
        });

        if (!response.ok) {
            throw new Error(`${response.status}: ${await response.text()}`);
        }

        const result = await response.json();
        sinceItems[sportId] = result.last;

        for (const event of result.events) {
            allEvents[event.event_id] = event;

            try {
                console.log(`   ${event.league_name}: ${event.home} — ${event.away}`,
                    event.periods?.num_0?.money_line || '');
            } catch (e) {
                // Skip if data not available
            }
        }

        console.log('Sport:', result.sport_name);
        console.log('Number of changes:', result.events.length);
        console.log(' ');
    }
}

// Run polling loop
(async () => {
    while (true) {
        await liveUpdate();
        await new Promise(resolve => setTimeout(resolve, 3000));
    }
})();

Live Update (RapidAPI)

javascript
const headers = {
    "x-rapidapi-key": "__YOUR_KEY__",
    "x-rapidapi-host": "pinbook-odds.p.rapidapi.com"
};

const baseUrl = "https://pinbook-odds.p.rapidapi.com";

const sinceItems = {};
const allEvents = {};

async function liveUpdate() {
    for (const sportId of [1]) {
        const params = new URLSearchParams({
            sport_id: sportId,
            is_have_odds: true,
            ...(sinceItems[sportId] && { since: sinceItems[sportId] })
        });

        console.log('[REQUEST]', Object.fromEntries(params));

        const response = await fetch(`${baseUrl}/kit/v1/markets?${params}`, {
            headers
        });

        if (!response.ok) {
            throw new Error(`${response.status}: ${await response.text()}`);
        }

        const result = await response.json();
        sinceItems[sportId] = result.last;

        for (const event of result.events) {
            allEvents[event.event_id] = event;
        }
    }
}

// Run polling loop
(async () => {
    while (true) {
        await liveUpdate();
        await new Promise(resolve => setTimeout(resolve, 3000));
    }
})();

Workflow 1: Complete API Client Class

A reusable class for interacting with the PinBook Odds API.

javascript
/**
 * PinBook Odds API Client
 *
 * Supports both HugeAPI and RapidAPI providers.
 */
class PinBookClient {
    /**
     * Initialize the client.
     * @param {string} apiKey - Your API key
     * @param {string} provider - "hugeapi" or "rapidapi"
     */
    constructor(apiKey, provider = "hugeapi") {
        this.provider = provider;

        if (provider === "hugeapi") {
            this.baseUrl = "https://pinnacle-odds-api.hgapi.top";
            this.headers = {
                "x-portal-apikey": apiKey
            };
        } else { // rapidapi
            this.baseUrl = "https://pinbook-odds.p.rapidapi.com";
            this.headers = {
                "x-rapidapi-key": apiKey,
                "x-rapidapi-host": "pinbook-odds.p.rapidapi.com"
            };
        }

        // Store since timestamps for each sport
        this._sinceCache = {};
        this._specialSinceCache = {};
    }

    /** Make an API request. */
    async _request(endpoint, params = {}) {
        const url = new URL(`${this.baseUrl}${endpoint}`);
        Object.keys(params).forEach(key => {
            if (params[key] !== undefined && params[key] !== null) {
                url.searchParams.append(key, params[key]);
            }
        });

        const response = await fetch(url, { headers: this.headers });

        if (!response.ok) {
            throw new Error(`API Error ${response.status}: ${await response.text()}`);
        }

        return response.json();
    }

    /** Check Pinnacle server betting status. */
    async checkBettingStatus() {
        return this._request("/kit/v1/betting-status");
    }

    /** Get list of all available sports. */
    async getSports() {
        return this._request("/kit/v1/sports");
    }

    /** Get period definitions for a sport. */
    async getPeriods(sportId) {
        return this._request("/kit/v1/meta-periods", { sport_id: sportId });
    }

    /** Get leagues for a sport. */
    async getLeagues(sportId) {
        return this._request("/kit/v1/leagues", { sport_id: sportId });
    }

    /**
     * Get markets/odds for a sport.
     * @param {number} sportId - Sport ID (required)
     * @param {number} since - UTC timestamp for delta updates
     * @param {string} leagueIds - Comma-separated league IDs
     * @param {string} eventIds - Comma-separated event IDs
     * @param {string} eventType - "prematch" or "live"
     * @param {boolean} isHaveOdds - Filter events with odds
     */
    async getMarkets(sportId, options = {}) {
        const params = { sport_id: sportId };

        if (options.since !== undefined) params.since = options.since;
        if (options.leagueIds) params.league_ids = options.leagueIds;
        if (options.eventIds) params.event_ids = options.eventIds;
        if (options.eventType) params.event_type = options.eventType;
        if (options.isHaveOdds !== undefined) params.is_have_odds = String(options.isHaveOdds).toLowerCase();

        const result = await this._request("/kit/v1/markets", params);

        // Update since cache
        if (result.last !== undefined) {
            this._sinceCache[sportId] = result.last;
        }

        return result;
    }

    /** Get special markets for a sport. */
    async getSpecialMarkets(sportId, options = {}) {
        const params = { sport_id: sportId };

        if (options.since !== undefined) params.since = options.since;
        if (options.eventType) params.event_type = options.eventType;
        if (options.isHaveLines !== undefined) params.is_have_lines = String(options.isHaveLines).toLowerCase();

        const result = await this._request("/kit/v1/special-markets", params);

        if (result.last !== undefined) {
            this._specialSinceCache[sportId] = result.last;
        }

        return result;
    }

    /** Get detailed event info with historical odds. */
    async getEventDetails(eventId) {
        return this._request("/kit/v1/details", { event_id: eventId });
    }

    /**
     * Get archived/settled events.
     */
    async getArchive(sportId, options = {}) {
        const params = {
            sport_id: sportId,
            page_num: options.pageNum || 1
        };

        if (options.leagueIds) params.league_ids = options.leagueIds;
        if (options.startAfter) params.start_after = options.startAfter;
        if (options.startBefore) params.start_before = options.startBefore;

        return this._request("/kit/v1/archive", params);
    }

    /** Get cached since timestamp for a sport. */
    getSince(sportId) {
        return this._sinceCache[sportId];
    }

    /** Get cached special since timestamp for a sport. */
    getSpecialSince(sportId) {
        return this._specialSinceCache[sportId];
    }
}


// Usage Example
(async () => {
    const client = new PinBookClient("YOUR_API_KEY", "hugeapi");

    // Get all sports
    const sports = await client.getSports();
    console.log(`Available sports: ${sports.length}`);

    // Get initial snapshot for Soccer (sport_id=1)
    const markets = await client.getMarkets(1, { isHaveOdds: true });
    console.log(`Initial events: ${markets.events?.length || 0}`);

    // Get delta updates using cached since
    await new Promise(resolve => setTimeout(resolve, 5000));
    const updates = await client.getMarkets(1, {
        since: client.getSince(1),
        isHaveOdds: true
    });
    console.log(`Updated events: ${updates.events?.length || 0}`);
})();

Workflow 2: Real-time Odds Monitor

Continuously monitor odds changes for specific leagues.

javascript
/**
 * Real-time odds monitoring for specific sports/leagues.
 * Tracks odds changes and notifies on significant movements.
 */
class OddsMonitor {
    constructor(apiKey, baseUrl) {
        this.baseUrl = baseUrl;
        this.headers = {
            "x-portal-apikey": apiKey
        };

        // Storage
        this.sinceCache = {};
        this.eventsStore = {};
        this.oddsHistory = {};
    }

    async _request(endpoint, params) {
        const url = new URL(`${this.baseUrl}${endpoint}`);
        Object.keys(params).forEach(key => {
            if (params[key] !== undefined && params[key] !== null) {
                url.searchParams.append(key, params[key]);
            }
        });

        const response = await fetch(url, { headers: this.headers });
        if (!response.ok) {
            throw new Error(`HTTP ${response.status}: ${await response.text()}`);
        }
        return response.json();
    }

    /** Fetch markets with delta updates. */
    async fetchMarkets(sportId, leagueIds = null) {
        const params = {
            sport_id: sportId,
            is_have_odds: true,
            since: this.sinceCache[sportId]
        };

        if (leagueIds) {
            params.league_ids = leagueIds.join(',');
        }

        const result = await this._request("/kit/v1/markets", params);
        this.sinceCache[sportId] = result.last;

        return result;
    }

    /** Track odds changes for an event. */
    trackOddsChanges(event) {
        const changes = [];
        const eventId = event.event_id;

        let currentMl;
        try {
            currentMl = event.periods.num_0.money_line;
        } catch (e) {
            return changes;
        }

        if (this.eventsStore[eventId]) {
            const oldEvent = this.eventsStore[eventId];
            try {
                const oldMl = oldEvent.periods.num_0.money_line;

                for (const key of ["home", "draw", "away"]) {
                    if (currentMl[key] !== undefined && oldMl[key] !== undefined) {
                        const oldVal = oldMl[key];
                        const newVal = currentMl[key];
                        if (oldVal !== newVal) {
                            changes.push({
                                field: `money_line.${key}`,
                                old: oldVal,
                                new: newVal,
                                diff: Math.round((newVal - oldVal) * 10000) / 10000
                            });
                        }
                    }
                }
            } catch (e) {
                // Skip
            }
        }

        // Store current state
        this.eventsStore[eventId] = event;

        // Store in history
        if (changes.length > 0) {
            if (!this.oddsHistory[eventId]) {
                this.oddsHistory[eventId] = [];
            }
            this.oddsHistory[eventId].push({
                timestamp: new Date().toISOString(),
                changes
            });
        }

        return changes;
    }

    /**
     * Start monitoring loop.
     * @param {number} sportId - Sport to monitor
     * @param {number[]} leagueIds - Optional list of league IDs to filter
     * @param {number} interval - Polling interval in seconds
     */
    async monitor(sportId, leagueIds = null, interval = 5) {
        console.log(`Starting monitor for sport_id=${sportId}`);
        console.log(`Leagues: ${leagueIds || 'All'}`);
        console.log("-".repeat(50));

        while (true) {
            try {
                const result = await this.fetchMarkets(sportId, leagueIds);
                const events = result.events || [];

                console.log(`\n[${new Date().toISOString()}]`);
                console.log(`Events with changes: ${events.length}`);

                for (const event of events) {
                    const changes = this.trackOddsChanges(event);

                    if (changes.length > 0) {
                        console.log(`\n  Event: ${event.home} vs ${event.away}`);
                        console.log(`  League: ${event.league_name}`);
                        for (const change of changes) {
                            const diffStr = change.diff >= 0 ? `+${change.diff.toFixed(4)}` : change.diff.toFixed(4);
                            console.log(`    ${change.field}: ${change.old} -> ${change.new} (${diffStr})`);
                        }
                    }
                }

                await new Promise(resolve => setTimeout(resolve, interval * 1000));

            } catch (error) {
                console.error(`Request error: ${error.message}`);
                await new Promise(resolve => setTimeout(resolve, interval * 2 * 1000));
            }
        }
    }

    /** Get odds change history for an event. */
    getEventHistory(eventId) {
        return this.oddsHistory[eventId] || [];
    }
}


// Usage
(async () => {
    const monitor = new OddsMonitor(
        "YOUR_API_KEY",
        "https://pinnacle-odds-api.hgapi.top"
    );

    // Monitor Soccer (sport_id=1) for specific leagues
    // Bundesliga: 1842, Premier League: 1980
    await monitor.monitor(1, [1842, 1980], 5);
})();

Workflow 3: Value Bet Finder

Find potential value bets by comparing odds movements.

javascript
/**
 * Find potential value bets by analyzing odds movements.
 */
class ValueBetFinder {
    constructor(apiKey, baseUrl) {
        this.baseUrl = baseUrl;
        this.headers = {
            "x-portal-apikey": apiKey
        };
        this.sinceCache = {};
    }

    async _request(endpoint, params) {
        const url = new URL(`${this.baseUrl}${endpoint}`);
        Object.keys(params).forEach(key => {
            if (params[key] !== undefined && params[key] !== null) {
                url.searchParams.append(key, params[key]);
            }
        });

        const response = await fetch(url, { headers: this.headers });
        if (!response.ok) {
            throw new Error(`HTTP ${response.status}: ${await response.text()}`);
        }
        return response.json();
    }

    /** Get all events with odds for a sport. */
    async getEventsWithOdds(sportId, eventType = "prematch") {
        const params = {
            sport_id: sportId,
            is_have_odds: true,
            event_type: eventType
        };

        const result = await this._request("/kit/v1/markets", params);
        this.sinceCache[sportId] = result.last;

        return result.events || [];
    }

    /** Convert decimal odds to implied probability. */
    calculateImpliedProbability(odds) {
        return odds > 0 ? 1 / odds : 0;
    }

    /** Calculate bookmaker margin from money line odds. */
    calculateMargin(moneyLine) {
        const totalProb = Object.values(moneyLine)
            .reduce((sum, odds) => sum + this.calculateImpliedProbability(odds), 0);
        return totalProb - 1; // Margin as percentage over 100%
    }

    /**
     * Find events with low bookmaker margin (good value).
     * @param {number} sportId - Sport ID
     * @param {number} maxMargin - Maximum acceptable margin (e.g., 0.02 = 2%)
     * @param {string} eventType - "prematch" or "live"
     */
    async findLowMarginEvents(sportId, maxMargin = 0.04, eventType = "prematch") {
        const events = await this.getEventsWithOdds(sportId, eventType);
        const lowMarginEvents = [];

        for (const event of events) {
            try {
                const moneyLine = event.periods?.num_0?.money_line;
                if (!moneyLine) continue;

                const margin = this.calculateMargin(moneyLine);

                if (margin <= maxMargin) {
                    const impliedProbs = {};
                    for (const [key, value] of Object.entries(moneyLine)) {
                        impliedProbs[key] = Math.round(this.calculateImpliedProbability(value) * 10000) / 100;
                    }

                    lowMarginEvents.push({
                        eventId: event.event_id,
                        home: event.home,
                        away: event.away,
                        league: event.league_name,
                        starts: event.starts,
                        moneyLine,
                        margin: Math.round(margin * 10000) / 100,
                        impliedProbs
                    });
                }
            } catch (e) {
                continue;
            }
        }

        return lowMarginEvents;
    }

    /**
     * Find events with significantly dropping odds.
     */
    async findDroppingOdds(sportId, threshold = 0.05, eventType = "prematch") {
        // First get current snapshot
        const events = await this.getEventsWithOdds(sportId, eventType);
        const droppingOdds = [];

        for (const event of events) {
            const eventId = event.event_id;

            // Get historical data
            try {
                const details = await this._request("/kit/v1/details", { event_id: eventId });
                const eventsDetails = details.events || [];

                if (eventsDetails.length > 0) {
                    const eventDetail = eventsDetails[0];
                    const periods = eventDetail.periods || [];

                    for (const period of periods) {
                        const history = period.history || {};
                        const moneyLineHistory = history.home || [];

                        if (moneyLineHistory.length >= 2) {
                            // history format: [timestamp, price, max_bet]
                            const current = moneyLineHistory[moneyLineHistory.length - 1];
                            const previous = moneyLineHistory[0];

                            if (current.length >= 2 && previous.length >= 2) {
                                const currentPrice = current[1];
                                const previousPrice = previous[1];

                                // Price drop means odds increased (more payout)
                                const drop = (currentPrice - previousPrice) / previousPrice;

                                if (drop >= threshold) {
                                    droppingOdds.push({
                                        eventId,
                                        home: event.home,
                                        away: event.away,
                                        league: event.league_name,
                                        previousPrice,
                                        currentPrice,
                                        dropPercent: Math.round(drop * 10000) / 100
                                    });
                                }
                            }
                        }
                    }
                }
            } catch (e) {
                continue;
            }
        }

        return droppingOdds;
    }

    /** Analyze spread markets for value opportunities. */
    async analyzeSpreads(sportId, eventType = "prematch") {
        const events = await this.getEventsWithOdds(sportId, eventType);
        const spreadAnalysis = [];

        for (const event of events) {
            try {
                const spreads = event.periods?.num_0?.spreads;
                if (!spreads) continue;

                for (const [hdpStr, spreadData] of Object.entries(spreads)) {
                    const handicap = spreadData.hdp;
                    const homeOdds = spreadData.home;
                    const awayOdds = spreadData.away;
                    const maxBet = spreadData.max;

                    // Calculate fair odds (without margin)
                    const homeProb = this.calculateImpliedProbability(homeOdds);
                    const awayProb = this.calculateImpliedProbability(awayOdds);
                    const totalProb = homeProb + awayProb;

                    spreadAnalysis.push({
                        eventId: event.event_id,
                        home: event.home,
                        away: event.away,
                        league: event.league_name,
                        handicap,
                        homeOdds,
                        awayOdds,
                        maxBet,
                        margin: Math.round((totalProb - 1) * 10000) / 100
                    });
                }
            } catch (e) {
                continue;
            }
        }

        return spreadAnalysis;
    }
}


// Usage Example
(async () => {
    const finder = new ValueBetFinder(
        "YOUR_API_KEY",
        "https://pinnacle-odds-api.hgapi.top"
    );

    // Find low margin events (Soccer)
    console.log("Finding low margin events...");
    const lowMargin = await finder.findLowMarginEvents(1, 0.04);

    console.log(`\nFound ${lowMargin.length} events with margin <= 4%:`);
    for (const event of lowMargin.slice(0, 10)) {
        console.log(`\n  ${event.home} vs ${event.away}`);
        console.log(`  League: ${event.league}`);
        console.log(`  Margin: ${event.margin}%`);
        console.log(`  Money Line: ${JSON.stringify(event.moneyLine)}`);
    }

    // Analyze spreads
    console.log("\n" + "=".repeat(50));
    console.log("Analyzing spreads...");
    let spreads = await finder.analyzeSpreads(1);

    // Sort by margin (lowest first)
    spreads.sort((a, b) => a.margin - b.margin);

    console.log("\nTop 5 lowest margin spreads:");
    for (const spread of spreads.slice(0, 5)) {
        console.log(`\n  ${spread.home} vs ${spread.away}`);
        console.log(`  Handicap: ${spread.handicap}`);
        console.log(`  Home: ${spread.homeOdds} | Away: ${spread.awayOdds}`);
        console.log(`  Margin: ${spread.margin}% | Max: $${spread.maxBet}`);
    }
})();

Workflow 4: Event Scheduler & Reminder

Track upcoming events and get reminders.

javascript
/**
 * Track upcoming events and provide scheduling functionality.
 */
class EventScheduler {
    constructor(apiKey, baseUrl) {
        this.baseUrl = baseUrl;
        this.headers = {
            "x-portal-apikey": apiKey
        };
        this.monitoredEvents = {};
    }

    async _request(endpoint, params) {
        const url = new URL(`${this.baseUrl}${endpoint}`);
        Object.keys(params).forEach(key => {
            if (params[key] !== undefined && params[key] !== null) {
                url.searchParams.append(key, params[key]);
            }
        });

        const response = await fetch(url, { headers: this.headers });
        if (!response.ok) {
            throw new Error(`HTTP ${response.status}: ${await response.text()}`);
        }
        return response.json();
    }

    /**
     * Get events starting within specified hours.
     * @param {number} sportId - Sport ID
     * @param {number} hoursAhead - How many hours ahead to look
     * @param {number[]} leagueIds - Optional list of league IDs to filter
     */
    async getUpcomingEvents(sportId, hoursAhead = 24, leagueIds = null) {
        const params = {
            sport_id: sportId,
            is_have_odds: true,
            event_type: "prematch"
        };

        if (leagueIds) {
            params.league_ids = leagueIds.join(',');
        }

        const result = await this._request("/kit/v1/markets", params);
        const events = result.events || [];

        const now = new Date();
        const cutoff = new Date(now.getTime() + hoursAhead * 60 * 60 * 1000);

        const upcoming = [];
        for (const event of events) {
            try {
                const starts = new Date(event.starts);

                if (now <= starts && starts <= cutoff) {
                    event.timeUntil = starts - now;
                    event.startsLocal = starts;
                    upcoming.push(event);
                }
            } catch (e) {
                continue;
            }
        }

        // Sort by start time
        upcoming.sort((a, b) => a.startsLocal - b.startsLocal);

        return upcoming;
    }

    /** Get all events starting today. */
    async getTodaysEvents(sportId, leagueIds = null) {
        return this.getUpcomingEvents(sportId, 24, leagueIds);
    }

    /** Get events starting in the next hour. */
    async getEventsNextHour(sportId) {
        return this.getUpcomingEvents(sportId, 1);
    }

    /** Add an event to the watchlist. */
    addToWatchlist(eventId, reminderMinutes = 30, notes = "") {
        this.monitoredEvents[eventId] = {
            reminderMinutes,
            notes,
            addedAt: new Date().toISOString()
        };
    }

    /** Check if any monitored events need reminders. */
    async checkReminders() {
        const reminders = [];
        const now = new Date();

        for (const [eventId, config] of Object.entries(this.monitoredEvents)) {
            try {
                // Get event details
                const details = await this._request("/kit/v1/details", { event_id: eventId });
                const events = details.events || [];

                if (events.length > 0) {
                    const event = events[0];
                    const starts = new Date(event.starts);

                    const timeUntil = starts - now;
                    const reminderMs = config.reminderMinutes * 60 * 1000;

                    if (timeUntil <= reminderMs && timeUntil > 0) {
                        reminders.push({
                            eventId,
                            home: event.home,
                            away: event.away,
                            league: event.league_name,
                            starts: event.starts,
                            minutesUntil: Math.floor(timeUntil / 60000),
                            notes: config.notes
                        });
                    }
                }
            } catch (e) {
                continue;
            }
        }

        return reminders;
    }

    /** Print a formatted daily schedule. */
    async printDailySchedule(sportId, leagueIds = null) {
        const events = await this.getTodaysEvents(sportId, leagueIds);

        console.log("\n" + "=".repeat(60));
        console.log(`DAILY SCHEDULE - ${new Date().toISOString().split('T')[0]}`);
        console.log("=".repeat(60));

        if (events.length === 0) {
            console.log("No events scheduled for today.");
            return;
        }

        let currentHour = null;
        for (const event of events) {
            const hour = event.startsLocal.toTimeString().slice(0, 2) + ":00";

            if (hour !== currentHour) {
                currentHour = hour;
                console.log(`\n[${hour}]`);
            }

            const timeStr = event.startsLocal.toTimeString().slice(0, 5);
            const timeUntil = this._formatDuration(event.timeUntil);

            console.log(`  ${timeStr} | ${event.home} vs ${event.away}`);
            console.log(`         | League: ${event.league_name}`);
            console.log(`         | In: ${timeUntil}`);

            // Show money line if available
            try {
                const ml = event.periods.num_0.money_line;
                console.log(`         | Odds: H:${ml.home} D:${ml.draw} A:${ml.away}`);
            } catch (e) {
                // Skip
            }
        }

        console.log("\n" + "=".repeat(60));
        console.log(`Total events: ${events.length}`);
    }

    _formatDuration(ms) {
        const hours = Math.floor(ms / 3600000);
        const minutes = Math.floor((ms % 3600000) / 60000);
        return `${hours}h ${minutes}m`;
    }

    /** Run the scheduler loop. */
    async runScheduler(sportId, checkInterval = 60) {
        console.log("Starting event scheduler...");
        console.log("Press Ctrl+C to stop.\n");

        const intervalId = setInterval(async () => {
            try {
                // Check reminders
                const reminders = await this.checkReminders();

                for (const reminder of reminders) {
                    console.log("\n" + "!".repeat(40));
                    console.log(`REMINDER: Event starting in ${reminder.minutesUntil} minutes!`);
                    console.log(`  ${reminder.home} vs ${reminder.away}`);
                    console.log(`  League: ${reminder.league}`);
                    if (reminder.notes) {
                        console.log(`  Notes: ${reminder.notes}`);
                    }
                    console.log("!".repeat(40) + "\n");
                }

                // Show events in next hour
                const nextHour = await this.getEventsNextHour(sportId);

                if (nextHour.length > 0) {
                    const time = new Date().toTimeString().slice(0, 8);
                    console.log(`[${time}] Events in next hour: ${nextHour.length}`);
                }
            } catch (e) {
                console.error("Scheduler error:", e.message);
            }
        }, checkInterval * 1000);

        // Handle graceful shutdown
        process.on('SIGINT', () => {
            clearInterval(intervalId);
            console.log("\nScheduler stopped.");
            process.exit(0);
        });
    }
}


// Usage Example
(async () => {
    const scheduler = new EventScheduler(
        "YOUR_API_KEY",
        "https://pinnacle-odds-api.hgapi.top"
    );

    // Print today's schedule
    await scheduler.printDailySchedule(1);

    // Add events to watchlist
    const upcoming = await scheduler.getEventsNextHour(1);
    for (const event of upcoming.slice(0, 3)) {
        scheduler.addToWatchlist(
            event.event_id,
            15,
            `Important match: ${event.home} vs ${event.away}`
        );
    }

    // Run scheduler
    await scheduler.runScheduler(1, 60);
})();

Workflow 5: Multi-Sport Dashboard

Build a dashboard showing data across multiple sports.

javascript
/**
 * Dashboard for monitoring multiple sports simultaneously.
 */
class MultiSportDashboard {
    // Common sport IDs
    static SPORTS = {
        1: "Soccer",
        2: "Tennis",
        3: "Basketball",
        4: "Ice Hockey",
        5: "American Football",
        6: "Baseball",
        7: "MMA",
        8: "Handball",
        9: "Volleyball",
        10: "Cricket"
    };

    constructor(apiKey, baseUrl) {
        this.baseUrl = baseUrl;
        this.headers = {
            "x-portal-apikey": apiKey
        };
        this.sinceCache = {};
    }

    async _request(endpoint, params = null) {
        const url = new URL(`${this.baseUrl}${endpoint}`);
        if (params) {
            Object.keys(params).forEach(key => {
                if (params[key] !== undefined && params[key] !== null) {
                    url.searchParams.append(key, params[key]);
                }
            });
        }

        const response = await fetch(url, { headers: this.headers });
        if (!response.ok) {
            throw new Error(`HTTP ${response.status}: ${await response.text()}`);
        }
        return response.json();
    }

    /** Get list of all available sports. */
    async getAllSports() {
        return this._request("/kit/v1/sports");
    }

    /** Get summary statistics for a sport. */
    async getSportSummary(sportId) {
        const params = {
            sport_id: sportId,
            is_have_odds: true
        };

        const result = await this._request("/kit/v1/markets", params);
        this.sinceCache[sportId] = result.last;

        const events = result.events || [];

        const summary = {
            sportId,
            sportName: result.sport_name || "Unknown",
            totalEvents: events.length,
            prematchEvents: 0,
            liveEvents: 0,
            openMarkets: 0,
            leagues: new Set(),
            nextEvent: null
        };

        const now = new Date();

        for (const event of events) {
            // Count by type
            if (event.event_type === "prematch") {
                summary.prematchEvents++;
            } else if (event.event_type === "live") {
                summary.liveEvents++;
            }

            // Count open markets
            if (event.is_have_open_markets) {
                summary.openMarkets++;
            }

            // Track leagues
            if (event.league_name) {
                summary.leagues.add(event.league_name);
            }

            // Find next event
            try {
                const starts = new Date(event.starts);

                if (starts > now) {
                    if (!summary.nextEvent || starts < summary.nextEvent.starts) {
                        summary.nextEvent = {
                            home: event.home,
                            away: event.away,
                            league: event.league_name,
                            starts
                        };
                    }
                }
            } catch (e) {
                // Skip
            }
        }

        summary.leagues = summary.leagues.size;

        return summary;
    }

    /** Get summary for all active sports. */
    async getAllSportsSummary() {
        const summaries = [];

        for (const sportId of Object.keys(MultiSportDashboard.SPORTS)) {
            try {
                const summary = await this.getSportSummary(parseInt(sportId));
                if (summary.totalEvents > 0) {
                    summaries.push(summary);
                }
                await new Promise(resolve => setTimeout(resolve, 300)); // Rate limiting
            } catch (e) {
                continue;
            }
        }

        return summaries;
    }

    /** Print a formatted dashboard. */
    async printDashboard() {
        console.log("\n" + "=".repeat(70));
        console.log(`PINBOOK ODDS DASHBOARD - ${new Date().toISOString()}`);
        console.log("=".repeat(70));

        const summaries = await this.getAllSportsSummary();

        // Header
        console.log(`\n${"Sport".padEnd(20)} ${"Events".padStart(8)} ${"Prematch".padStart(10)} ${"Live".padStart(6)} ${"Leagues".padStart(10)}`);
        console.log("-".repeat(70));

        let totalEvents = 0;
        let totalPrematch = 0;
        let totalLive = 0;

        for (const summary of summaries) {
            console.log(`${summary.sportName.padEnd(20)} ` +
                `${String(summary.totalEvents).padStart(8)} ` +
                `${String(summary.prematchEvents).padStart(10)} ` +
                `${String(summary.liveEvents).padStart(6)} ` +
                `${String(summary.leagues).padStart(10)}`);

            totalEvents += summary.totalEvents;
            totalPrematch += summary.prematchEvents;
            totalLive += summary.liveEvents;
        }

        console.log("-".repeat(70));
        console.log(`${"TOTAL".padEnd(20)} ${String(totalEvents).padStart(8)} ${String(totalPrematch).padStart(10)} ${String(totalLive).padStart(6)}`);

        // Next events
        console.log("\n" + "-".repeat(70));
        console.log("UPCOMING EVENTS:");
        console.log("-".repeat(70));

        const upcoming = [];
        for (const summary of summaries) {
            if (summary.nextEvent) {
                upcoming.push({
                    sport: summary.sportName,
                    ...summary.nextEvent
                });
            }
        }

        upcoming.sort((a, b) => a.starts - b.starts);

        for (const event of upcoming.slice(0, 5)) {
            const timeUntil = event.starts - new Date();
            const minutes = Math.floor(timeUntil / 60000);

            console.log(`\n  [${event.sport}] ${event.home} vs ${event.away}`);
            console.log(`    League: ${event.league}`);
            console.log(`    Starts in: ${minutes} minutes`);
        }

        console.log("\n" + "=".repeat(70));
    }

    /** Run live updating dashboard. */
    async runLiveDashboard(refreshInterval = 60) {
        const intervalId = setInterval(async () => {
            try {
                // Clear screen (optional - uncomment if desired)
                // console.clear();

                await this.printDashboard();

                console.log(`\nRefreshing in ${refreshInterval} seconds... (Ctrl+C to stop)`);
            } catch (e) {
                console.error("Dashboard error:", e.message);
            }
        }, refreshInterval * 1000);

        // Initial display
        await this.printDashboard();

        // Handle graceful shutdown
        process.on('SIGINT', () => {
            clearInterval(intervalId);
            console.log("\n\nDashboard stopped.");
            process.exit(0);
        });
    }

    /** Get all currently live events across sports. */
    async getLiveEvents() {
        const liveEvents = [];

        for (const sportId of Object.keys(MultiSportDashboard.SPORTS)) {
            try {
                const params = {
                    sport_id: sportId,
                    event_type: "live",
                    is_have_odds: true
                };

                const result = await this._request("/kit/v1/markets", params);
                const events = result.events || [];

                for (const event of events) {
                    liveEvents.push({
                        sport: result.sport_name,
                        home: event.home,
                        away: event.away,
                        league: event.league_name,
                        periods: event.periods || {}
                    });
                }

                await new Promise(resolve => setTimeout(resolve, 300));
            } catch (e) {
                continue;
            }
        }

        return liveEvents;
    }
}


// Usage Example
(async () => {
    const dashboard = new MultiSportDashboard(
        "YOUR_API_KEY",
        "https://pinnacle-odds-api.hgapi.top"
    );

    // Print one-time dashboard
    await dashboard.printDashboard();

    // Or run live dashboard
    // await dashboard.runLiveDashboard(60);
})();

We’re dedicated to providing the best API products