Skip to content
MMA Calendar
MMA Fight Data API · Code guide

How do I build a UFC fight-night Discord bot?

Poll the live UFC card from the MMA Fight Data API and post each new result to a Discord channel webhook, or answer a slash command with discord.js. The Python block posts every result with the winner, method, round and time, checking once a minute while a card is live and every 15 minutes otherwise. On Pro, the fight.result webhook can push results to your bot instead.

Free plan: 1,000 requests a month, no card. Paid plans from $19/mo.

Endpoints used

Endpoints used
Method & pathWhat it returnsPlan
GET /v1/events?org=ufc&include=headlineMMA schedule + results API: full fight cards, venues, broadcasts, and a card-change diff log for late reshuffles.All plans
GET /v1/events/{slug}One event with its full fight card (both corners, results when completed), venue and event-level broadcasts.All plans
POST /v1/webhook-endpointsSigned webhooks for fight results, card changes, event completion and consensus odds moves · no polling needed.Pro and up

The code

curl shows the call behind /nextufc. The Python block posts results through a Discord channel webhook, with no bot account needed; the JavaScript block is a discord.js bot with a /nextufc slash command.

Every block runs as shown with your API key in the UFCAL_API_KEY environment variable. Python uses pip install ufcalendar and JavaScript or TypeScript uses npm install @ufcalendar/sdk; any HTTP client works too.

curl
curl "https://api.ufcalendar.com/v1/events?org=ufc&limit=1&include=headline" \
  -H "Authorization: Bearer $UFCAL_API_KEY"
Python
# pip install ufcalendar requests
import os
import time

import requests
from ufcalendar import FightAPI

api = FightAPI()  # reads UFCAL_API_KEY from the environment
DISCORD_WEBHOOK = os.environ["DISCORD_WEBHOOK_URL"]  # Channel settings -> Integrations -> Webhooks
watching, posted = set(), set()


def post_new_results():
    watching.update(ev["slug"] for ev in api.events("ufc", status="live"))
    for slug in list(watching):
        event = api.event(slug)
        for f in event["card"]:
            r = f["result"]
            if not r or f["id"] in posted:
                continue
            posted.add(f["id"])
            names = {c["id"]: c["name"] for c in (f["fighter_a"], f["fighter_b"])}
            winner = names.get(r["winner_fighter_id"], "No winner")
            msg = (f"**{f['fighter_a']['name']} vs {f['fighter_b']['name']}**\n"
                   f"{winner} - {r['method']}, round {r['round']} at {r['time']}")
            requests.post(DISCORD_WEBHOOK, json={"content": msg}, timeout=10).raise_for_status()
        if event["status"] != "live":  # one last pass after the card ends
            watching.discard(slug)


while True:
    post_new_results()
    time.sleep(60 if watching else 15 * 60)  # fast on fight night, slow otherwise
JavaScript
// npm install discord.js @ufcalendar/sdk
import { Client, Events, GatewayIntentBits, REST, Routes, SlashCommandBuilder } from "discord.js";
import { FightAPI } from "@ufcalendar/sdk";

const api = new FightAPI(); // reads UFCAL_API_KEY from the environment
const { DISCORD_TOKEN, DISCORD_APP_ID } = process.env;

// Register /nextufc once (global commands can take a few minutes to appear)
const command = new SlashCommandBuilder().setName("nextufc").setDescription("The next UFC card and its main event");
await new REST().setToken(DISCORD_TOKEN).put(Routes.applicationCommands(DISCORD_APP_ID), { body: [command.toJSON()] });

const client = new Client({ intents: [GatewayIntentBits.Guilds] });
client.on(Events.InteractionCreate, async (interaction) => {
  if (!interaction.isChatInputCommand() || interaction.commandName !== "nextufc") return;
  for await (const ev of api.events({ org: "ufc", limit: 1, include: ["headline"] })) {
    const h = ev.headline;
    const unix = Math.floor(new Date(ev.main_card_at ?? ev.starts_at).getTime() / 1000);
    // <t:...:F> renders in each reader's own time zone
    await interaction.reply(`**${ev.title}**\n${h ? `${h.fighter_a.name} vs ${h.fighter_b.name}\n` : ""}Main card <t:${unix}:F> (<t:${unix}:R>)`);
    return;
  }
  await interaction.reply("No UFC card is scheduled right now.");
});
await client.login(DISCORD_TOKEN);

Sample response

A real response from the live API, trimmed for length. … marks removed fields.

GET /v1/events?org=ufc&limit=1&include=headline
{
  "data": [
    {
      "id": 2805,
      "slug": "ufc-fight-night-2026-10-31",
      "title": "UFC Fight Night: Moicano vs Nolan",
      "short_title": "Fight Night",
      "numbering": "Fight Night",
      "org": "ufc",
      "sport": "mma",
      "starts_at": "2026-10-31T21:00:00.000Z",
      "main_card_at": "2026-11-01T00:00:00.000Z",
      "prelims_at": "2026-10-31T21:00:00.000Z",
      "early_prelims_at": null,
      "tz": "America/Los_Angeles",
      "status": "announced",
      "is_ppv": false,
      "is_title_card": false,
      "headline": {
        "fight_id": 93077,
        "fighter_a": { "id": 589, "slug": "renato-moicano", "name": "Renato Moicano" },
        "fighter_b": { "id": 92, "slug": "tom-nolan", "name": "Tom Nolan" },
        "weight_class": "Lightweight",
        "is_title": false,
        "winner_fighter_id": null,
        "method": null,
        "round": null,
        "time": null
      }
    }
  ],
  "meta": {
    "pagination": { "next_cursor": "eyJpZCI6MjgwNX0", "has_more": true, "limit": 1 }
  }
}

Notes and pitfalls

  • Discord's timestamp markup shows the start time in each member's own time zone, so send the Unix time rather than a formatted date.
  • Budget: a poll every 15 minutes is about 3,000 requests a month, and a six-hour card polled every minute adds about 720 (a listing call and a card call each minute). That fits the Hobby plan; the free plan suits the slash command on its own.
  • Keep the posted bout ids in a file or database so a restart does not repost the card, and fetch each card once more after it flips to completed so the main event is not missed.
  • The sample registers /nextufc on every start to stay short. In production, register it once at deploy time, and expect a global command to take a few minutes to appear.

Related pages

More code guides

All code guides →

UFCalendar is an independent publication, not affiliated with UFC/Zuffa/TKO, PFL, OKTAGON MMA, BKFC, RIZIN FF or any promotion. Organization names are used to describe the data. Terms of service · Refund Policy

How do I build a UFC fight-night Discord bot? | UFCalendar