> ## 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 Discord Sports Odds Bot with Node.js

> Build a Discord bot with /odds, /scores, and /track slash commands. Rich embeds with team logos. Full Node.js tutorial using FieldFunded SDK.

# Build a Discord Odds Bot Your Community Will Actually Use

A bot that serves live sports odds directly in your Discord server. Users type `/odds Lakers` and get real-time lines with team logos.

## What You'll Use

| SDK Method       | Endpoint                   | Purpose                       |
| ---------------- | -------------------------- | ----------------------------- |
| `search()`       | `GET /v1/search`           | Find events by team name      |
| `getEventOdds()` | `GET /v1/events/{id}/odds` | Get odds for a specific event |
| `getScores()`    | `GET /v1/scores`           | Get live scores               |
| `getLive()`      | `GET /v1/live`             | List all live events          |

## Prerequisites

* Node.js 18+
* A Discord bot token (from [Discord Developer Portal](https://discord.com/developers))
* A free FieldFunded API key

```bash theme={null}
npm init -y
npm install discord.js @fieldfunded/sdk
```

## Step 1: Initialize Both SDKs

```typescript theme={null}
import { Client, GatewayIntentBits, EmbedBuilder, SlashCommandBuilder, REST, Routes } from 'discord.js';
import { FieldFundedSDK } from '@fieldfunded/sdk';

const discord = new Client({ intents: [GatewayIntentBits.Guilds] });
const ff = new FieldFundedSDK({
  apiKey: process.env.FIELDFUNDED_API_KEY!,
  baseUrl: 'https://api.fieldfunded.com/v1',
});
```

## Step 2: Register Slash Commands

```typescript theme={null}
const commands = [
  new SlashCommandBuilder()
    .setName('odds')
    .setDescription('Get live odds for a team')
    .addStringOption(opt =>
      opt.setName('team').setDescription('Team name').setRequired(true)
    ),
  new SlashCommandBuilder()
    .setName('scores')
    .setDescription('Show all live scores')
    .addStringOption(opt =>
      opt.setName('sport').setDescription('Filter by sport (e.g. soccer)')
    ),
];

// Register commands on bot startup
const rest = new REST().setToken(process.env.DISCORD_TOKEN!);
await rest.put(
  Routes.applicationCommands(process.env.DISCORD_CLIENT_ID!),
  { body: commands.map(c => c.toJSON()) }
);
```

## Step 3: Handle `/odds` Command

Uses `search()` to find the team, then `getEventOdds()` for market data:

```typescript theme={null}
discord.on('interactionCreate', async (interaction) => {
  if (!interaction.isChatInputCommand()) return;

  if (interaction.commandName === 'odds') {
    await interaction.deferReply();
    const team = interaction.options.getString('team', true);

    // Search for the team using the SDK
    const results = await ff.search(team, { limit: 3 });

    if (!results.events || results.events.length === 0) {
      await interaction.editReply(`No upcoming events found for "${team}"`);
      return;
    }

    const event = results.events[0];

    // Get odds for main markets
    const odds = await ff.getEventOdds(event.id);

    const embed = new EmbedBuilder()
      .setTitle(`${event.home_team} vs ${event.away_team}`)
      .setDescription(`${event.league} • ${event.sport}`)
      .setColor(0x53FC18)
      .addFields(
        { name: 'Status', value: event.status, inline: true },
        { name: 'Start', value: new Date(event.start_time).toLocaleString(), inline: true },
      );

    // Add main market odds
    if (odds.markets && odds.markets.length > 0) {
      const mainMarket = odds.markets[0]; // Usually 1x2 or Winner
      const oddsStr = mainMarket.selections
        .map((s: any) => `**${s.name}**: ${s.odds}`)
        .join(' | ');
      embed.addFields({ name: mainMarket.name, value: oddsStr });
    }

    await interaction.editReply({ embeds: [embed] });
  }
});
```

## Step 4: Handle `/scores` Command

Uses `getScores()` — the lightweight endpoint optimized for frequent polling:

```typescript theme={null}
if (interaction.commandName === 'scores') {
  await interaction.deferReply();
  const sport = interaction.options.getString('sport') || undefined;

  const data = await ff.getScores({ sport });

  if (!data.scores || data.scores.length === 0) {
    await interaction.editReply('No live games right now.');
    return;
  }

  const embed = new EmbedBuilder()
    .setTitle('🔴 Live Scores')
    .setColor(0xFF0000);

  // Show up to 10 games
  for (const game of data.scores.slice(0, 10)) {
    embed.addFields({
      name: `${game.home_team} vs ${game.away_team}`,
      value: `**${game.score.home} - ${game.score.away}** • ${game.league}`,
    });
  }

  await interaction.editReply({ embeds: [embed] });
}
```

## Step 5: Deploy

```typescript theme={null}
discord.login(process.env.DISCORD_TOKEN!);
```

Deploy to [Railway](https://railway.app) (free tier) or any Node.js host. Set environment variables: `DISCORD_TOKEN`, `DISCORD_CLIENT_ID`, `FIELDFUNDED_API_KEY`.

## Rate Limit Awareness

The free tier gives you 10,000 req/month. With a Discord bot:

* Each `/odds` command = 2 requests (search + odds)
* Each `/scores` command = 1 request
* 10,000 / 2 = **\~5,000 /odds commands per month** on the free tier

That's more than enough for a community server.

<CardGroup cols={2}>
  <Card title="Search API Reference" icon="magnifying-glass" href="/api-reference/events/search-events">
    See search endpoint docs →
  </Card>

  <Card title="Live Scores Reference" icon="futbol" href="/api-reference/live/get-scores">
    See scores endpoint docs →
  </Card>
</CardGroup>

***

## Related Guides

* [Build a Telegram Bot](/guides/build-telegram-bot) — same concept, different platform (includes inline search)
* [Build a Twitch Overlay](/guides/build-twitch-overlay) — live esports odds on stream
* [Build a Settlement Engine](/guides/build-settlement-engine) — resolve bets automatically

<Card title="Get Your Free API Key" icon="key" href="/quickstart">
  Start building in 5 minutes — 10,000 free requests/month
</Card>
