Fortnite has shipped seven numbered chapters and dozens of seasons since October 2017, and keeping track of when one ends and the next begins is harder than it should be. Epic doesn’t send a push notification to your phone. Community trackers lag by hours. If you run a Discord server for a squad, a content team, or a competitive roster, the first person to notice “Chapter 7, Season 4: Override” swapped to something new usually just got lucky refreshing a wiki page.

This tutorial builds a small, self-hosted Discord bot that watches Fortnite’s public season data, stores the full season history in a local database, and posts an alert the moment a chapter or season identifier changes. You’ll use Python, a lightweight SQLite database, and Fortnite-API.com as the data source, with Epic’s own Fortnite Data API documented as a fallback path. By the end you’ll have a working bot, a queryable archive of every Fortnite season on record, and a polling job that checks for updates without hammering anyone’s rate limits.

The current live season, for reference, is Chapter 7, Season 4, “Override,” which started around August 19–20, 2026 and is expected to run through the end of October or the first days of November. That single data point is exactly what your bot needs to catch automatically instead of checking manually every morning.

Why build a season tracker instead of using an existing one

Plenty of fan wikis list all Fortnite seasons in order, and tier-list sites let players rank them for fun. None of that solves the actual problem: knowing the instant a new season goes live, inside the app your community already uses. A Discord bot closes that gap. It can post to a channel, answer slash commands like /currentseason or /allseasons, and log every transition to a database you control, so you’re not scraping a webpage that might change its layout next month.

There’s also a data-ownership argument. Third-party trackers can vanish or start rate-limiting free users. When you run the bot yourself, you decide the polling interval, you keep the historical record, and you can extend it later to track battle pass changes, map updates, or shop rotations using the same architecture. The steps below build the core season tracker first, then point to the extensions at the end.

Prerequisites and versions

You don’t need much to follow along, but version mismatches are the most common reason a first attempt at a Discord bot fails silently. Confirm each of these before Step 1:

  • Python 3.11 or 3.12 (discord.py’s async task loop relies on features that break on anything older than 3.10)
  • discord.py 2.4 or newer, installed via pip
  • requests 2.31 or newer for HTTP calls
  • SQLite 3, which ships with Python’s standard library, so no separate install is needed
  • A free Discord Developer account and a server (guild) where you have admin rights to add a bot
  • Optionally, a free API key from Fortnite-API.com if you want higher request headroom than the anonymous tier
  • A machine that can stay online for the polling loop: a $5/month VPS, a Raspberry Pi, or a free-tier host like Railway all work

Epic also publishes a Fortnite Data API directly through the Epic Developer Community. It’s publicly accessible without authentication, though Epic applies rate limiting and doesn’t publish a fixed numeric cap, so treat it as a fallback source rather than your primary feed unless you’ve read the current documentation for the specific endpoint you need.

Fortnite-API.com vs Epic’s official Data API

Before writing code, it helps to understand why this tutorial picks a third-party service over Epic’s own endpoint. Both exist for good reasons, and a production bot might eventually use both, but they solve different problems and it’s worth knowing the trade-off going in.

FeatureFortnite-API.comEpic Fortnite Data API
AuthenticationOptional key via request header, higher limits with a keyNone required for public access
Data shapeClean, purpose-built JSON for news, cosmetics, shop, and map dataBroader creator-tooling schema aimed at game integrations
Rate limitsNot fixed publicly, but generous enough for a 30-minute pollApplied by Epic, no fixed numeric cap published
Best used forSeason, shop, and cosmetic tracking bots like this oneDeeper integrations tied to Epic’s broader developer ecosystem

The practical takeaway is simple. Fortnite-API.com gets you a working season tracker faster because its endpoints are already shaped around the kind of data this project needs. Epic’s own API is worth keeping in your back pocket as a fallback, especially if Fortnite-API.com ever goes offline or changes its terms, since it’s maintained directly by the company that actually ships the seasons. Building your API client as its own module in Step 6, rather than scattering HTTP calls throughout bot.py, is what makes swapping providers later a small change instead of a rewrite.

One more distinction matters for anyone extending this project past season tracking: Epic also runs separate, authenticated account and game-service APIs used internally by the Epic Games ecosystem, involving OAuth flows and player-specific permissions. Those are not the same thing as the public Data API, and a hobby Discord bot has no legitimate reason to touch them. Stick to documented, public endpoints, and never ask users for their Epic account credentials to make a season tracker work.

Step 1: Scaffold the project

Start with a clean folder and a virtual environment so your bot’s dependencies don’t collide with anything else on your machine. Run this in a terminal:

mkdir fortnite-season-bot
cd fortnite-season-bot
python3 -m venv venv
source venv/bin/activate   # on Windows: venv\Scripts\activate
pip install discord.py requests python-dotenv

Create four files you’ll fill in over the next steps: bot.py for the Discord client, fortnite_api.py for the data client, database.py for the SQLite layer, and .env for secrets. Keeping the API logic separate from the Discord logic matters later, when you want to test season detection without spinning up a live bot connection every time.

Step 2: Register the Discord bot application

Go to the Discord Developer Portal, create a New Application, and give it a name like “Fortnite Season Watch.” Under the Bot tab, click Add Bot, then copy the token that appears. Treat this token like a password: anyone who has it can control your bot. Under Privileged Gateway Intents, you don’t need Server Members or Presence for this project, but you do need Message Content if you plan to support text commands alongside slash commands.

Generate an invite URL from the OAuth2 > URL Generator page. Select the bot and applications.commands scopes, then under bot permissions check Send Messages, Embed Links, and Use Slash Commands. Paste the generated URL into a browser and add the bot to your test server.

Save the token in your .env file, never in the script itself:

DISCORD_TOKEN=your_bot_token_here
FORTNITE_API_KEY=your_fortnite_api_key_here
ALERT_CHANNEL_ID=123456789012345678

Step 3: All Fortnite seasons — the historical reference table

Before writing any detection logic, you need a ground-truth record of every chapter and how many seasons it ran. This table is what your database seed script in Step 5 will be built from, and it’s also useful on its own if a teammate just wants the timeline without touching code.

ChapterSeasons in chapterApproximate date rangeNotable season
Chapter 110 (Seasons 1–9 plus Season X)October 2017 – October 2019Season X, the mid-chapter rebrand before Chapter 2
Chapter 28October 2019 – December 2021Season 1, which introduced the sinking-island storyline
Chapter 34December 2021 – December 2022Season 4, “Last Resort”
Chapter 44December 2022 – December 2023Season 2, “MEGA”
Chapter 55 (including the Remix season)December 2023 – December 2024Remix, a genre-hopping music-themed mini-season
Chapter 64December 2024 – November 2025Season 4, the run-up to Chapter 7
Chapter 74 so far, Season 4 is currentNovember 2025 – presentSeason 4, “Override,” active as of September 28, 2026

Add those rows up and you get 39 numbered Battle Royale seasons across seven chapters, not counting a few short-lived special modes that some fan trackers count separately. That’s the baseline your bot’s /allseasons command will return, and it’s also the number you should expect to see grow by one whenever Chapter 7, Season 4 wraps up around late October or early November 2026.

Step 4: Design the SQLite schema

You need two SQLite tables: one holding the static historical record from Step 3, and one holding the single “last known state” your bot compares against on every poll. Create database.py:

import sqlite3

DB_PATH = "seasons.db"

def init_db():
    conn = sqlite3.connect(DB_PATH)
    cur = conn.cursor()
    cur.execute("""
        CREATE TABLE IF NOT EXISTS seasons (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            chapter INTEGER NOT NULL,
            season INTEGER NOT NULL,
            name TEXT,
            start_date TEXT,
            end_date TEXT
        )
    """)
    cur.execute("""
        CREATE TABLE IF NOT EXISTS last_known_state (
            id INTEGER PRIMARY KEY CHECK (id = 1),
            chapter INTEGER,
            season INTEGER,
            name TEXT,
            checked_at TEXT
        )
    """)
    conn.commit()
    conn.close()

def get_connection():
    return sqlite3.connect(DB_PATH)

The CHECK (id = 1) constraint on last_known_state is a simple trick to keep that table to exactly one row, which you’ll update in place with INSERT OR REPLACE rather than accumulating rows you’d otherwise have to query with an ORDER BY every time.

Step 5: Seed the historical season data

Write a one-time seed script that loads the Chapter/Season pairs from Step 3 into the seasons table. You only need to run this once, but keep the script around, since it doubles as documentation for anyone else who touches the project.

from database import get_connection, init_db

SEED_DATA = [
    (1, 1, None, "2017-10-26", "2017-12-13"),
    (1, 2, None, "2017-12-14", "2018-02-21"),
    (2, 1, None, "2019-10-15", "2020-02-19"),
    (3, 1, None, "2021-12-05", "2022-03-19"),
    (4, 1, None, "2022-12-04", "2023-03-10"),
    (5, 1, None, "2023-12-02", "2024-03-08"),
    (6, 1, None, "2024-12-01", "2025-02-21"),
    (7, 1, "Pacific Break", "2025-11-29", "2026-03-16"),
    (7, 2, None, "2026-03-17", "2026-06-05"),
    (7, 3, "Runners", "2026-06-06", "2026-08-20"),
    (7, 4, "Override", "2026-08-20", None),
]

def seed():
    init_db()
    conn = get_connection()
    cur = conn.cursor()
    cur.executemany(
        "INSERT INTO seasons (chapter, season, name, start_date, end_date) VALUES (?, ?, ?, ?, ?)",
        SEED_DATA,
    )
    conn.commit()
    conn.close()
    print(f"Seeded {len(SEED_DATA)} seasons.")

if __name__ == "__main__":
    seed()

This trimmed list covers the start of each chapter plus the full Chapter 7 run, which is what actually matters for change detection. Fill in the remaining rows from the Step 3 table if you want a complete archive for the /allseasons command. The detection logic in Step 7 only cares about the most recent row.

Step 6: Build the Fortnite API client

Fortnite-API.com is the practical choice for this project: it’s a third-party service that wraps Epic’s game data into clean JSON endpoints, and it accepts an API key through a request header for higher-throughput access. Write fortnite_api.py to fetch the current season/chapter data with basic error handling built in from the start, since a season-tracking bot that crashes on a timeout is worse than useless:

import os
import requests

BASE_URL = "https://fortnite-api.com/v2"
API_KEY = os.getenv("FORTNITE_API_KEY", "")

def get_current_season_info():
    headers = {"Authorization": API_KEY} if API_KEY else {}
    try:
        resp = requests.get(f"{BASE_URL}/news/br", headers=headers, timeout=10)
        resp.raise_for_status()
    except requests.exceptions.RequestException as exc:
        print(f"Fortnite API request failed: {exc}")
        return None

    data = resp.json().get("data", {})
    return {
        "hash": data.get("hash"),
        "last_modified": data.get("date"),
    }

Notice this endpoint returns a content hash and a last-modified timestamp rather than a clean “chapter 7, season 4” field on every provider. That’s intentional: public game-data APIs change their schemas more often than official documentation gets updated, so building your detection logic around a hash comparison, with the chapter/season number layered on top from your own seed data, is more resilient than trusting a single field name to stay stable for a year.

Step 7: Write the change-detection logic

This is the core of the bot. On every poll, compare the freshly fetched data against what’s stored in last_known_state. If the hash or the chapter/season pair has changed, fire an alert and update the record. Add this function to database.py:

import datetime

def check_for_change(new_hash, chapter=None, season=None, name=None):
    conn = get_connection()
    cur = conn.cursor()
    cur.execute("SELECT chapter, season, name FROM last_known_state WHERE id = 1")
    row = cur.fetchone()

    changed = row is None or row[0] != chapter or row[1] != season

    cur.execute("""
        INSERT OR REPLACE INTO last_known_state (id, chapter, season, name, checked_at)
        VALUES (1, ?, ?, ?, ?)
    """, (chapter, season, name, datetime.datetime.utcnow().isoformat()))
    conn.commit()
    conn.close()
    return changed

Compare the chapter and season number together, not the display name alone. Names can be missing, localized differently across providers, or renamed by Epic mid-cycle for marketing reasons. The numeric pair is the identifier least likely to change without an actual new season shipping.

Step 8: Build the Discord bot and slash commands

Now wire everything into a discord.py client. This version supports two slash commands, /currentseason and /allseasons, plus the background polling loop covered in Step 9. Create bot.py:

import os
import discord
from discord import app_commands
from dotenv import load_dotenv
from database import init_db, get_connection

load_dotenv()
TOKEN = os.getenv("DISCORD_TOKEN")
ALERT_CHANNEL_ID = int(os.getenv("ALERT_CHANNEL_ID", "0"))

intents = discord.Intents.default()
client = discord.Client(intents=intents)
tree = app_commands.CommandTree(client)

@tree.command(name="currentseason", description="Show the latest known Fortnite chapter and season")
async def current_season(interaction: discord.Interaction):
    conn = get_connection()
    cur = conn.cursor()
    cur.execute("SELECT chapter, season, name FROM seasons ORDER BY id DESC LIMIT 1")
    row = cur.fetchone()
    conn.close()
    if row:
        chapter, season, name = row
        label = f"Chapter {chapter}, Season {season}" + (f' - "{name}"' if name else "")
        await interaction.response.send_message(f"Current season on record: **{label}**")
    else:
        await interaction.response.send_message("No season data found yet.")

@tree.command(name="allseasons", description="List every Fortnite chapter and season on record")
async def all_seasons(interaction: discord.Interaction):
    conn = get_connection()
    cur = conn.cursor()
    cur.execute("SELECT chapter, season, name, start_date FROM seasons ORDER BY id ASC")
    rows = cur.fetchall()
    conn.close()
    lines = [f"Ch.{c} S.{s} - {n or 'Unnamed'} (started {d})" for c, s, n, d in rows]
    text = "\n".join(lines[-15:])
    await interaction.response.send_message(f"Most recent seasons:\n```\n{text}\n```")

@client.event
async def on_ready():
    init_db()
    await tree.sync()
    print(f"Logged in as {client.user}")

client.run(TOKEN)

Run python bot.py after saving. Slash commands can take up to an hour to appear globally the first time, so test in a single server first by syncing commands to a specific guild ID during development instead of syncing globally.

Step 9: Add the background polling task

discord.py ships a tasks extension built for exactly this: a loop that runs on an interval alongside the bot’s normal event handling, without blocking it. Add this above client.run(TOKEN) in bot.py:

from discord.ext import tasks
from fortnite_api import get_current_season_info
from database import check_for_change

@tasks.loop(minutes=30)
async def poll_season():
    info = get_current_season_info()
    if info is None:
        return

    conn = get_connection()
    cur = conn.cursor()
    cur.execute("SELECT chapter, season, name FROM seasons ORDER BY id DESC LIMIT 1")
    chapter, season, name = cur.fetchone()
    conn.close()

    changed = check_for_change(info["hash"], chapter, season, name)
    if changed:
        channel = client.get_channel(ALERT_CHANNEL_ID)
        if channel:
            await channel.send(
                f"New Fortnite season data detected: Chapter {chapter}, Season {season}"
                + (f' - "{name}"' if name else "")
            )

@client.event
async def on_ready():
    init_db()
    await tree.sync()
    if not poll_season.is_running():
        poll_season.start()
    print(f"Logged in as {client.user}")

Thirty minutes is a reasonable default polling interval. It’s frequent enough to catch a season change within half an hour of it going live, and it stays well inside any reasonable rate-limit window for a free-tier API key. Do not drop this below five minutes. There’s no benefit to your users, and it’s the fastest way to get an API key throttled or blocked.

Step 10: Format alerts with a rich embed

A plain text message works, but a Discord embed reads better in a busy server and lets you add a timestamp, a color, and structured fields. Replace the alert send call in Step 9 with this:

embed = discord.Embed(
    title="New Fortnite Season Detected",
    description=f'Chapter {chapter}, Season {season}' + (f' - "{name}"' if name else ""),
    color=discord.Color.blue(),
)
embed.add_field(name="Detected at", value=info["last_modified"] or "unknown", inline=False)
embed.set_footer(text="Fortnite Season Watch Bot")
await channel.send(embed=embed)

Embeds also give you room to grow the alert later, for example adding a field for the new battle pass name once you extend the API client to pull that data too.

Step 11: Choose where to run it

A Discord bot with a background polling loop needs to stay running continuously, which rules out running it from your laptop unless you never close the lid. Here’s how the common hosting options compare for a small, single-purpose bot like this one:

Hosting optionTypical costSetup effortBest for
Raspberry Pi at homeOne-time hardware cost, no monthly feeModerate (OS setup, systemd service)Hobbyists who already own a Pi
Basic VPS (DigitalOcean, Linode, Hetzner)Roughly $4–7/monthModerate (SSH, systemd or Docker)Anyone wanting full control and uptime
Railway or Render free/hobby tierFree tier available, paid plans scale upLow (git push to deploy)Fast setup without server administration
Docker on any of the aboveSame as underlying hostLow once the Dockerfile existsReproducible deploys across environments

If you go the VPS or Raspberry Pi route, wrap the bot in a systemd service so it restarts automatically after a crash or a reboot:

[Unit]
Description=Fortnite Season Discord Bot
After=network.target

[Service]
WorkingDirectory=/home/youruser/fortnite-season-bot
ExecStart=/home/youruser/fortnite-season-bot/venv/bin/python bot.py
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

Save that as /etc/systemd/system/fortnite-bot.service, then run sudo systemctl enable --now fortnite-bot. The Restart=always line is what saves you from a bot that silently dies at 3 a.m. and stays dead until you notice.

Step 12: Test the full flow end to end

Before trusting the bot to run unattended, force a test alert by temporarily deleting the row in last_known_state so the next poll registers a “change”:

python3 -c "from database import get_connection; conn = get_connection(); conn.execute('DELETE FROM last_known_state'); conn.commit()"

Restart the bot and watch your alert channel. You should see output like this in your terminal within a few seconds of startup:

Logged in as FortniteSeasonWatch#4821
Seeded 11 seasons.
[poll_season] Change detected: Chapter 7, Season 4 - "Override"
[poll_season] Alert sent to channel 123456789012345678

And the embed itself should land in the channel looking roughly like this: a blue-accented card titled “New Fortnite Season Detected,” with the chapter and season line as the description and a detected-at timestamp field underneath. If both of those show up, the pipeline from API fetch to database write to Discord message is working end to end.

Securing your bot token and API keys

A season tracker is low-risk compared to a bot with moderation powers, but a leaked Discord token is still a real problem. Anyone holding it can log in as your bot, post messages in every server it’s joined, and read whatever channels it has access to. Treat the token from Step 2 with the same care you’d give a database password, not as a throwaway config value.

Three habits cover most of the risk. First, keep secrets in .env and add that filename to .gitignore before your first commit, not after. Second, scope the bot’s permissions narrowly when you generate the invite link in Step 2: Send Messages, Embed Links, and Use Slash Commands are enough for this project, and there’s no reason to grant Administrator or Manage Server just because the checkbox is there. Third, if a token ever does leak, for example by accidentally pasting it into a public gist, regenerate it immediately from the Developer Portal rather than hoping nobody notices. The old token stops working the instant you do.

The same discipline applies to the Fortnite-API.com key. It’s lower stakes since it only grants read access to public game data, but a leaked key tied to your account could get rate-limited or revoked by someone else’s misuse, leaving your bot silently broken until you notice and rotate it. Loading both secrets through os.getenv rather than hardcoding them, as shown in Steps 2 and 6, means rotating either one is a one-line change to .env and a bot restart, nothing more.

Bot vs. webhook: which approach fits your server

Everything above assumes you want a full bot with slash commands, but that’s not the only option. Discord webhooks let you post messages to a channel from any script, with no bot application, no gateway connection, and no on_ready event to manage. Which approach fits depends on how much you want out of the project beyond the alert itself.

When a webhook is enough

If all you want is a message dropped into a channel when the season changes, a webhook trims most of the setup from Step 2. Create one from a channel’s Integrations settings, copy the URL, and post to it with a single requests.post() call carrying a JSON payload. There’s no token to protect beyond the webhook URL itself, no gateway intents to configure, and no persistent connection to keep alive between polls, since the webhook only needs to exist for the moment you send a message. A cron job or a scheduled cloud function can run the whole check without a long-lived process at all.

When you need a full bot

The moment you want /currentseason or /allseasons as commands members can run on demand, you need a real bot application with a persistent connection, which is what Steps 2 through 9 build. The same is true if you plan to support multiple servers from one deployment, react to messages, or add moderation-adjacent features later. A webhook can only push. It can’t listen. For this tutorial’s scope, the full bot is worth the extra setup because the slash commands turn a passive alert feed into something members can actually query, but if you’re prototyping quickly or only need the alert, start with a webhook and upgrade later. The database and detection logic from Steps 4 and 7 work unchanged either way.

Common pitfalls to avoid

  • Polling too aggressively. Checking every 60 seconds doesn’t get you faster alerts in any meaningful way, since Epic doesn’t ship seasons at second-level precision, and it’s the fastest way to get an API key throttled.
  • Comparing only the season name string. Names get localized, renamed for marketing, or left blank by some API responses. Always key change detection off the numeric chapter/season pair first.
  • Hardcoding the bot token in the script. If you ever push this to a public GitHub repo, that token is now public too. Use environment variables and a .gitignore entry for .env from day one.
  • Skipping error handling on the API call. Third-party APIs go down or time out. A bot that crashes on the first failed request instead of logging and retrying will need a manual restart every time the upstream service hiccups.
  • Syncing slash commands globally during development. Global command sync can take up to an hour to propagate. Sync to a single test guild while iterating, then switch to global sync once the commands are final.

Troubleshooting

SymptomLikely causeFix
Bot shows offline in DiscordInvalid or expired token, or the process isn’t runningRegenerate the token in the Developer Portal and confirm the .env file loads correctly
Slash commands don’t appearCommands haven’t propagated yetWait up to an hour for global sync, or sync to a specific guild ID for instant testing
discord.py import errors on startupPython version below 3.10, or a stale virtual environmentRecreate the venv and reinstall with pip inside it
Fortnite API requests return 403 or 401Missing or invalid Authorization headerConfirm the API key is present in the .env file and loaded via os.getenv
Polling loop appears to freezeAn unhandled exception inside the loop body silently stops the taskWrap the loop body in try/except and log exceptions instead of letting them propagate
Alert channel never receives a messageWrong channel ID, or the bot lacks Send Messages permission thereRight-click the channel in Discord with Developer Mode on to copy the correct ID, and check role permissions
SQLite “database is locked” errorMultiple processes writing to seasons.db at onceRun a single bot instance per database file, or switch to a proper server-based database for multi-process setups
Bot restarts in a crash loop under systemdAn exception on startup, often a missing environment variableRun journalctl -u fortnite-bot -f to read the actual traceback

Advanced tips for extending the bot

Once the core tracker is stable, a few extensions make it genuinely useful for a busy community server rather than a novelty. First, add a confidence model instead of a single binary “changed” flag: treat a chapter/season identifier change from a trusted API as high confidence and post immediately, but treat a battle pass ID change alone as medium confidence and wait for a second signal (like a map update or an official news post) before alerting. This mirrors how community trackers avoid false alarms from leaks and rumors.

Second, cache static data aggressively. Season history from Step 3 almost never changes retroactively, so there’s no reason to refetch it on every poll. Only the “current state” check needs to hit the network on a schedule. Third, if you want to support multiple Discord servers from one bot instance, add a guild_id column to a settings table so each server can register its own alert channel, rather than hardcoding a single channel ID as this tutorial does for simplicity.

Finally, consider exponential backoff on the HTTP client. If a request fails, don’t just skip that poll cycle silently forever. Retry with a short delay, then a longer one, and log a warning after several consecutive failures so you notice a real outage instead of assuming everything is fine.

Timestamps deserve a mention too. The checked_at field written in Step 7 uses UTC, which is the right default for a bot that might serve members across time zones. If you display dates back to users, format them with Discord’s own timestamp markup rather than hardcoding a format string, since Discord automatically renders those in each viewer’s local time zone without any extra code on your end. It’s a small detail, but it avoids the confusing experience of a bot announcing a season change at a time that doesn’t match what a member sees on their own clock.

The complete project, assembled

Here’s the full requirements.txt to pin the versions used throughout this tutorial, so your setup matches what’s been tested:

discord.py>=2.4,<3.0
requests>=2.31,<3.0
python-dotenv>=1.0,<2.0

Your finished project folder should look like this:

fortnite-season-bot/
├── venv/
├── .env
├── requirements.txt
├── database.py
├── fortnite_api.py
├── seed.py
├── bot.py
└── seasons.db          (created automatically on first run)

Run python seed.py once to populate the database, then start the bot with python bot.py. From there, deploy it using the systemd service from Step 11, or containerize it with a short Dockerfile that copies the project, installs requirements.txt, and runs bot.py as its entrypoint.

Why this matters heading into the next Fortnite season

Chapter 7, Season 4 is expected to wrap up around the end of October or the first days of November 2026, which puts the next transition well within range while this bot is still fresh. That's a useful stress test: your polling loop, alert formatting, and database update logic all get exercised by a real event rather than a manual trigger. If the alert fires cleanly when the real season changes, you know the architecture holds up, and you can extend it with the same confidence toward tracking battle passes, map changes, or item shop rotations using the patterns already built here.

Frequently asked questions

How many Fortnite seasons have there been in total?
Counting every numbered season across all seven chapters, Fortnite has run 39 numbered Battle Royale seasons since October 2017, not counting a handful of short special modes that some community trackers list separately.

What is the current Fortnite season as of late September 2026?
Chapter 7, Season 4, subtitled "Override," which began around August 19–20, 2026 and is expected to run through the end of October or the first days of November 2026.

Do I need Epic's official API, or is Fortnite-API.com enough?
Fortnite-API.com covers everything this tutorial needs and is simpler to integrate. Epic's own Fortnite Data API is public and doesn't require authentication, but it's better used as a fallback or for deeper game-data needs, since it doesn't publish a fixed rate limit and its schema is aimed at broader creator tooling.

How often should my bot check for a new season?
Every 15 to 60 minutes is the practical range. Anything faster wastes API calls without any real benefit, since season transitions aren't a second-level event.

Can I run this bot for free?
Yes. Discord bot hosting has no cost from Discord's side, and free tiers on platforms like Railway or Render can run a small polling bot like this one without hitting their limits, though you should check current free-tier terms before deploying, since they change over time.

What happens if the Fortnite API changes its response format?
Your bot's alert stops firing correctly until you update the parsing logic in fortnite_api.py. This is why the tutorial recommends wrapping API calls in error handling and logging failures clearly, so you notice a schema change quickly instead of silently missing every future season alert.

Can this bot also track the item shop or battle pass instead of just seasons?
Yes, using the same architecture. Add a new table for shop or battle pass state, fetch the relevant Fortnite-API.com endpoint, and run a second comparison function alongside check_for_change. The polling loop and alert-embed pattern from Steps 9 and 10 work unchanged.

Is it against Epic's or Discord's rules to build something like this?
No. This project only reads publicly available season and game-state data through documented APIs and posts it to a Discord server you control. It doesn't touch player accounts, automate gameplay, or bypass any authentication. Always check the current terms of service for whichever API provider you use before deploying at scale.