Skip to content

Build a Discord Price Check Bot

Discord is where the TCG community lives. Thousands of servers are dedicated to trading, collecting, and discussing card games. A price check bot that responds instantly to commands like /price Charizard ex is one of the most popular features a community server can offer. TCG API makes it straightforward to build one that covers every game.

What Your Bot Can Do

A price check bot powered by TCG API can handle a wide range of commands. Here are the most popular ones that TCG Discord communities use daily:

/price Charizard ex

Look up a card by name and get its market price, low price, and 7-day trend. The bot searches across all games automatically.

/price Black Lotus --game mtg

Filter by game when a card name exists in multiple games. Useful for generic names that appear in several TCGs.

/trending pokemon

Show the biggest price movers for a game. Members can see which cards are spiking or crashing right now.

/set "Surging Sparks"

Display summary stats for a set: total value, most expensive cards, and how the set's value has changed recently.

Why TCG API Is the Right Choice

Building a Discord price bot without a proper API means scraping marketplace websites, dealing with rate limits and CAPTCHAs, parsing inconsistent HTML, and handling each game differently. With TCG API, all of that goes away.

  • Single endpoint for all games. Your bot doesn't need separate code paths for Pokemon vs. Magic vs. Yu-Gi-Oh!. The same /search endpoint works across all 89+ games.
  • Fast response times. TCG API responses typically come back in under 200ms. Your bot can reply before Discord's "thinking" indicator even appears.
  • No scraping fragility. Web scrapers break whenever a website changes its layout. An API with versioned endpoints and structured JSON responses is stable and predictable.
  • Price change data included. Every price response includes 24-hour, 7-day, and 30-day percentage changes. Your bot can show trend arrows and context, not just a raw dollar amount.

Full Bot Example

Here's a complete Discord bot using Discord.js v14 and the TCG API. This bot handles slash commands for price lookups and trending cards. It formats responses as rich embeds with price data, trends, and direct links.

JavaScript — index.js
const { Client, GatewayIntentBits, EmbedBuilder } = require("discord.js");

const DISCORD_TOKEN = process.env.DISCORD_TOKEN;
const API_BASE = "https://api.tcgapi.dev/v1";
const API_KEY = process.env.TCG_API_KEY;
const headers = { "X-API-Key": API_KEY };

const client = new Client({ intents: [GatewayIntentBits.Guilds] });

// Helper: fetch card price from TCG API
async function lookupCard(query, game) {
  const params = new URLSearchParams({ q: query, limit: "1" });
  if (game) params.set("game", game);

  const searchRes = await fetch(`${API_BASE}/search?${params}`, { headers });
  const { results } = await searchRes.json();

  if (!results || results.length === 0) return null;

  const card = results[0];
  const priceRes = await fetch(`${API_BASE}/cards/${card.id}/prices`, { headers });
  const prices = await priceRes.json();

  return { ...card, prices };
}

// Helper: format price with trend arrow
function formatTrend(changePercent) {
  if (changePercent == null) return "";
  const arrow = changePercent > 0 ? "\u2B06" : changePercent < 0 ? "\u2B07" : "\u27A1";
  return `${arrow} ${changePercent > 0 ? "+" : ""}${changePercent.toFixed(1)}%`;
}

// Handle /price command
async function handlePrice(interaction) {
  const query = interaction.options.getString("card");
  const game = interaction.options.getString("game");

  await interaction.deferReply();

  const result = await lookupCard(query, game);

  if (!result) {
    await interaction.editReply(`No results found for "${query}".`);
    return;
  }

  const { prices } = result;
  const embed = new EmbedBuilder()
    .setTitle(result.name)
    .setDescription(`**${result.set_name}** · ${result.game_name}`)
    .setColor(0x6366f1)
    .addFields(
      {
        name: "Market Price",
        value: prices.market_price
          ? `$${prices.market_price.toFixed(2)}`
          : "N/A",
        inline: true,
      },
      {
        name: "Low Price",
        value: prices.low_price
          ? `$${prices.low_price.toFixed(2)}`
          : "N/A",
        inline: true,
      },
      {
        name: "Foil Market",
        value: prices.foil_market_price
          ? `$${prices.foil_market_price.toFixed(2)}`
          : "N/A",
        inline: true,
      },
      {
        name: "24h",
        value: formatTrend(prices.change_24h) || "—",
        inline: true,
      },
      {
        name: "7d",
        value: formatTrend(prices.change_7d) || "—",
        inline: true,
      },
      {
        name: "30d",
        value: formatTrend(prices.change_30d) || "—",
        inline: true,
      }
    )
    .setFooter({ text: "Data from TCG API · tcgapi.dev" })
    .setTimestamp();

  if (result.image_url) embed.setThumbnail(result.image_url);

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

// Handle /trending command
async function handleTrending(interaction) {
  const game = interaction.options.getString("game") || "pokemon";

  await interaction.deferReply();

  const res = await fetch(
    `${API_BASE}/prices/top-movers?game=${game}&period=24h&limit=5`,
    { headers }
  );
  const { gainers, losers } = await res.json();

  const formatList = (cards) =>
    cards
      .map(
        (c, i) =>
          `${i + 1}. **${c.name}** — $${c.market_price.toFixed(2)} (${
            c.change_pct > 0 ? "+" : ""
          }${c.change_pct.toFixed(1)}%)`
      )
      .join("\n");

  const embed = new EmbedBuilder()
    .setTitle(`Trending Cards — ${game.charAt(0).toUpperCase() + game.slice(1)}`)
    .setColor(0x6366f1)
    .addFields(
      { name: "\u2B06 Top Gainers (24h)", value: formatList(gainers) || "None" },
      { name: "\u2B07 Top Losers (24h)", value: formatList(losers) || "None" }
    )
    .setFooter({ text: "Data from TCG API · tcgapi.dev" })
    .setTimestamp();

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

// Route slash commands
client.on("interactionCreate", async (interaction) => {
  if (!interaction.isChatInputCommand()) return;

  try {
    if (interaction.commandName === "price") await handlePrice(interaction);
    if (interaction.commandName === "trending") await handleTrending(interaction);
  } catch (err) {
    console.error(err);
    const reply = { content: "Something went wrong. Try again in a moment.", ephemeral: true };
    interaction.deferred
      ? await interaction.editReply(reply)
      : await interaction.reply(reply);
  }
});

client.login(DISCORD_TOKEN);

Register Slash Commands

Before the bot can respond to slash commands, you need to register them with Discord. Run this script once to set up the commands.

JavaScript — register-commands.js
const { REST, Routes, SlashCommandBuilder } = require("discord.js");

const commands = [
  new SlashCommandBuilder()
    .setName("price")
    .setDescription("Look up a card's current market price")
    .addStringOption(opt =>
      opt.setName("card").setDescription("Card name to search for").setRequired(true)
    )
    .addStringOption(opt =>
      opt.setName("game").setDescription("Filter by game (e.g. pokemon, magic, yugioh)")
    ),

  new SlashCommandBuilder()
    .setName("trending")
    .setDescription("Show top price movers for a game")
    .addStringOption(opt =>
      opt.setName("game").setDescription("Game to check (default: pokemon)")
    ),
];

const rest = new REST().setToken(process.env.DISCORD_TOKEN);

(async () => {
  await rest.put(
    Routes.applicationCommands(process.env.DISCORD_APP_ID),
    { body: commands.map(c => c.toJSON()) }
  );
  console.log("Slash commands registered.");
})();

What Users See

Here's what a typical interaction looks like in Discord. The bot responds with a clean embed that shows all the information a player or collector needs at a glance.

user123 used /price card: Charizard ex
Charizard ex
Scarlet & Violet -- Obsidian Flames · Pokemon
Market Price
$28.47
Low Price
$22.15
Foil Market
$45.80
24h
+2.3%
7d
+8.1%
30d
-4.2%
Data from TCG API · tcgapi.dev

Key API Endpoints

Tips for Production Bots

  • Cache popular lookups. Cards like Charizard get searched hundreds of times a day. Cache results for 5-10 minutes to reduce API calls and speed up responses.
  • Use deferred replies. Always call interaction.deferReply() before making API calls. This gives you 15 minutes to respond instead of the default 3-second window.
  • Handle rate limits gracefully. If you hit your daily limit, show a friendly message instead of an error. Upgrade your plan if your bot serves a large community.
  • Add autocomplete. Discord supports autocomplete for slash command options. Use the /games endpoint to populate game name suggestions dynamically.

Ready to build your Discord bot?

Free tier includes 100 requests per day. No credit card required.