Skip to content
CreateDiscordBot
Menu

How to host a tournament on Discord: bots, check-ins and map vetoes

How to host a tournament on Discord: server setup, team roles, check-in windows, match rooms, map vetoes, score reporting and which tournament bot to pick.

By Adam Peleback. Updated . 13 min read.

On this page
  1. Pick a Discord tournament bot or a bracket site
  2. Set up the server before registration opens
  3. Registration: collect Discord IDs, not usernames
  4. Check-in windows that don't stall round one
  5. Match rooms: private threads or private channels
  6. Map and side vetoes
  7. Result reporting and disputes
  8. Streams and announcements
  9. Hosting a tournament on Discord: staff and a match-day runbook
  10. What to automate and what to keep manual
  11. If you build the bot yourself
  12. When a custom tournament bot is worth it

To host a tournament on Discord, keep the bracket in one tool and use your server for everything around it. The bracket lives on a bracket site such as start.gg, Challonge or Battlefy, or in a Discord tournament bot such as Tourney Bot or BattleBot. Discord handles the rest: a role for every team, a check-in window, a private room per match, the map veto, result reporting and a dispute process your staff can follow.

Build the server before registration opens, collect Discord user IDs at sign-up, and decide who rules on disputes before the first match. Below is each piece in order, with the Discord limits that bite at scale, what bots and bracket sites cost as of September 2026, and two tested code samples. I have been a tournament admin since 2017 and have built Discord tooling for leagues of up to 154 teams.

Pick a Discord tournament bot or a bracket site

Decide first which system owns the bracket. Prices are from each product's own page, checked in September 2026.

Option Price Good at Limits worth knowing
BattleBot Free Brackets inside Discord, a private thread per match, both players confirm results Single elimination or first-to-X only; 4, 8, 16 or 32 players
Tourney Bot Free for 3 tournaments a month and 8 participants; $1.99 to $15.99 a month for 32 to 512 Many bracket types, check-in, match threads, proof screenshots Swiss, round robin and team events need a paid tier
Team Up Free; Pro $1.99 or Unlimited $4.99 a month; White Label $19.99 a month Elo ladders, matchmaking queues, tournaments seeded by rating Players sign up one by one, and seeding comes from Elo
start.gg Fee only on paid online registrations: 6%, added to what the player pays Event check-in, player reporting with verify timers, a moderator dashboard, VALORANT veto tasks, LoL tournament codes Can require a Discord connection at sign-up; anything inside your server still needs staff or a bot
Battlefy Free for organizers and players Match pages for scheduling and disputes, match check-in with an auto-drop timer Discord account linking only on some tournaments
Challonge Free up to 256 participants; Premier $6.99 a month billed yearly Quick brackets and embeds API capped at 500 requests a month on the free plan, 2,000 on Premier

Some rules of thumb:

  • One evening, one game, 1v1, up to 32 players: a Discord bot such as BattleBot or Tourney Bot keeps everyone in the server.
  • Team events or online check-in: start.gg or Battlefy for the bracket, Discord for communication, and ideally a bot connecting the two. For paid entry, start.gg and Challonge both take payments through Stripe.
  • Weekly pickups or a ladder: Team Up's queues and Elo.
  • Multi-week leagues with divisions: a bracket site or sheet as the source of truth, plus a bot for rosters, team channels and match rooms.

If you want a Challonge Discord bot, check the API quota first. At 500 requests a month, a bot that checks a bracket once a minute uses the free allowance in about eight hours. Anything beyond that needs a paid API plan, which Challonge manages separately from Premier.

Set up the server before registration opens

This layout works for most online events:

  • Info (read-only): rules, announcements, schedule.
  • Event: check-in, results, and a help or ticket channel.
  • Teams 1, Teams 2 and so on: a private text and voice channel per team. A category holds at most 50 channels, so that is 25 teams per category.
  • Matches: one text channel whose private threads are the match rooms, or channels created per round.
  • Staff: staff chat, a referee log and a bot log.
  • Broadcast: caster chat and voice.

Roles

Role Who gets it What it opens
Organizer Head admins Everything, including bot settings
Referee Match admins All team channels and match rooms; can manage messages and move people in voice
Caster Broadcast crew Broadcast channels and results
Captain One or two per team Check-in, veto and score reporting
One role per team Every rostered player That team's private channels
Participant Everyone on a roster Event channels and announcements

Two Discord rules shape this. A bot can only give out roles that sit below its own highest role, so drag the bot's role above every team role. And when a bot writes channel permissions, it can only allow or deny permissions it has itself, so give it those permissions server-wide.

Permissions per team channel

For each team channel, deny View Channel for @everyone, then allow it for the team role and the referee role. Voice channels also allow Connect for the team, and Connect plus Move Members for referees. Everything else, such as Send Messages and Speak, comes from the server-wide permissions your members already have. The permissions calculator builds the bot's invite link with the right permissions.

Discord's caps

Discord allows 250 roles and 500 channels per server, and categories count as channels. With one role and two channels per team, an otherwise empty server runs out of room at roughly 240 teams, and a real server with info, staff and broadcast channels runs out sooner. The 154-team Rivals League setup fits with room to spare. Past about 200 teams, give each team one channel, move match rooms into threads, or split divisions into their own servers.

Registration: collect Discord IDs, not usernames

Ask for the team name, the captain, every player's Discord account, their in-game IDs (Riot ID, Steam profile and so on), region, and subs. Store Discord user IDs rather than usernames, because usernames change and IDs do not. Rivals League runs a sync job that repairs usernames changed since registration.

If the bracket is on start.gg, you can require a Discord connection when players register; start.gg asks for your server ID when you set it up and puts the Discord usernames in the attendee export. Battlefy offers Discord linking on some tournaments, which gives linked players roles in the support server.

If registration happens inside Discord, a /register command that opens a team card works well: the captain gets buttons to add players and request subs, and each opens a short form. That is how the registration bot I co-built for a Swedish online league handled 131 teams and 793 players. Whatever you use, put a roster lock date and a sub policy in the rulebook before sign-ups open.

Check-in windows that don't stall round one

There are two kinds of check-in. Event check-in asks "are you here today?" before the bracket starts. Match check-in asks "are you ready for this match?" and runs per round. How the main tools handle it:

  • start.gg: you set a check-in timer and a buffer, the gap between check-in closing and the event starting. In team events one teammate checks in for the whole team. When you finalize, you choose between removing late entrants and rebalancing pools, disqualifying them in place, or doing nothing. Each round also has a DQ timer that defaults to 15 minutes.
  • Battlefy: match check-in with a timer; a team that misses it is dropped, and neither side can report a score until both have checked in.
  • Tourney Bot: /checkin creates a check-in channel with a Confirm button. Ending check-in removes everyone who did not confirm; cancelling keeps them.

Whatever tool you use:

  1. Use one check-in, not two. If start.gg runs check-in, Discord only reminds people.
  2. Post times as Discord timestamps. <t:1791050400:F> shows 18:00 UTC on 3 October 2026 converted to each reader's own time zone, and the :R style shows a relative time such as "in 20 minutes". The timestamp generator makes these.
  3. Size the window. Long enough for players across time zones to see it, short enough to still replace no-shows. For an online cup I open it 30 to 60 minutes before round one and close it 10 to 15 minutes before.
  4. Ping twice. Once when check-in opens and once shortly before it closes. More than that trains people to mute the server.
  5. Decide the no-show rule in advance. Removing and reseeding is fairer to top seeds; disqualifying in place keeps the published bracket intact.

Match rooms: private threads or private channels

Every match needs a place where both teams and a referee can talk, share lobby details and post screenshots.

Private thread Private channel
Channel budget Threads have their own active-thread limit, separate from the 500 channels Uses one of your 500 channels
Who can see it Members who were added, plus anyone with Manage Threads Whichever roles you allow
Voice No Add a voice channel next to it
Afterwards Archive the thread Delete it or move it to an archive category

Threads suit big brackets, and both BattleBot and Tourney Bot use them. Private channels suit leagues where each match also needs voice for referees or observers.

In the room, the bot or an admin should post both captains as mentions, the format, the map pool, the start time as a timestamp, lobby details, and buttons to start the veto, report the score and call a referee. Once results are final, archive rooms instead of deleting them; the history is your evidence in a later dispute.

Map and side vetoes

A veto decides which maps are played and who starts on which side. Teams take turns removing maps (bans) and choosing maps (picks), and the opponent of whoever picked a map chooses the starting side on it. The last map standing is the decider.

The Counter-Strike Major rulebook Valve publishes on GitHub is a good default for CS2:

Step Best of 1 Best of 3
0 Higher seed chooses to be team A or B Higher seed chooses to be team A or B
1 A removes 2 maps A removes 1 map
2 B removes 3 maps B removes 1 map
3 A removes 1 map A picks map 1, B chooses side
4 B chooses side on the last map B picks map 2, A chooses side
5 B removes 1 map, then A removes 1 map
6 The remaining map is map 3, B chooses side

Both orders assume a seven-map pool. For VALORANT, start.gg has veto tasks built into its VALORANT ruleset; if you run the veto in Discord, write the order into your rulebook, and keep the map pool in the bot's settings rather than its code, because pools change between seasons.

A veto bot has to enforce a few things a spreadsheet can't: only the right captain's button works on each turn, the order follows the format automatically, the decider is picked without anyone clicking, and a summary goes to the match room and the results channel for casters. Also write down what happens when a captain stops responding, such as a random ban after five minutes. You can run a best-of-1, 3 or 5 veto in the browser with the map veto tool; the CS2 and VALORANT veto bot I co-built for an esports tournament organizer runs the same flow with buttons in a private channel.

Result reporting and disputes

The pattern that works: one side reports, the other confirms, and the bracket only moves when both agree. BattleBot works this way, and start.gg auto-verifies a reported score when the opponent's verify timer runs out. Tourney Bot accepts the first report and gives either player a Dispute button that opens a channel with the admins.

  • Proof. Ask for a scoreboard screenshot of every map, posted in the match room. Tourney Bot has a proof system for this; its docs say submitted images are used to train Joyn's own AI model, which is worth telling your players.
  • Automatic results where the game allows it. For League of Legends events on start.gg, players join lobbies with tournament codes and use Fetch Data to pull the results. FACEIT sends a match_status_finished webhook when a match ends, which a bot can turn into a result post.
  • Disputes. A "call a referee" button should open a private ticket with both captains and the referee team. The referee rules, cites the rulebook section, and logs the decision in the referee log. On start.gg, moderator requests sit at the top of the match dashboard. If you want to build the ticket side yourself, the ticket bot tutorial covers it.

Streams and announcements

Go-live alerts are cheap to run. Twitch's EventSub has a stream.online event, or a bot can poll the Twitch API. Rivals League checks every minute, posts into the right game's channel, and waits six hours before posting the same channel again so nobody gets spammed. Post schedules with timestamps, and send results to one channel your casters can follow.

Hosting a tournament on Discord: staff and a match-day runbook

Give every admin one job. start.gg's admin levels are a useful model: Administrators control everything, Managers add attendees and report scores, Bracket Managers report scores and edit phases and seeding but cannot add attendees, and Reporters only work in brackets and the match dashboard. Mirror those as Discord roles so the bot and the bracket agree on who can do what, and name one person as the bracket owner for the day.

A runbook that fits on one screen:

  1. Day before: team roles and channels created, bot permissions tested with a test team, rulebook pinned.
  2. 60 minutes before: check-in opens; one ping.
  3. 15 minutes before: check-in closes and is finalized; bracket published; match rooms open.
  4. During: referees watch match rooms and moderator requests; only the bracket owner edits the bracket.
  5. After: results posted, rooms archived, disputes logged, standings updated.

What to automate and what to keep manual

Task Automate? Why
Team roles and channels Yes Hundreds of clicks, and one wrong permission leaks a team's channel
Check-in The button, yes; the no-show decision, no Staff know who messaged to say they are two minutes late
Match rooms Yes Creating 64 rooms by hand before round one is slow and easy to get wrong
Map veto Yes Turn order is easy to get wrong by hand, and captains notice
Score reporting Yes, with confirmation Disputes still go to a human
Rulings and penalties No A bot should log decisions, not make them
Seeding With review Ratings help, but someone should read the result
Prize payouts No Too much can go wrong to leave to a script

If you build the bot yourself

Two pieces cover most of the setup work: creating team roles and channels from a roster, and knowing which matches need a room. Both samples below were tested on Node 24.15.0, the Discord script with discord.js 14.27.0. The --env-file flag needs Node 20.6 or newer.

Create team roles and channels from a roster

This script reads teams.json, checks Discord's caps, and creates a role, a private text channel and a private voice channel for every team, then gives the role to each player. With --dry-run it only reads your server and prints what it would do. Run it again after late sign-ups and it only creates what is missing. The bot needs these server permissions, because it grants them in channel overwrites: Manage Roles, Manage Channels, View Channels, Connect, Move Members and Manage Messages.

teams.json
[
  { "name": "Northside", "players": ["111111111111111111", "222222222222222222"] },
  { "name": "Valkyrie Esports", "players": ["333333333333333333"] }
]
.env
DISCORD_TOKEN=your-bot-token
GUILD_ID=your-server-id
STAFF_ROLE_ID=your-referee-role-id
provision-teams.js
// provision-teams.js
// Gives every team in teams.json a role, a private text channel and a private
// voice channel, then gives that role to each player. Safe to re-run: anything
// that already exists is skipped. Always run with --dry-run first.
//
//   node --env-file=.env provision-teams.js teams.json --dry-run
//
// .env needs DISCORD_TOKEN, GUILD_ID and STAFF_ROLE_ID.
// teams.json: [{ "name": "Northside", "players": ["<Discord user ID>", ...] }]

const fs = require('node:fs');
const {
  Client, Events, GatewayIntentBits, ChannelType, OverwriteType, PermissionFlagsBits: P,
} = require('discord.js');

const MAX_ROLES = 250; // Discord's role cap per server
const MAX_CHANNELS = 500; // channel cap per server, categories included
const TEAMS_PER_CATEGORY = 25; // 2 channels per team, 50 channels per category

const slug = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');

function overwrites(guild, teamRoleId, staffRoleId, botId, voice) {
  return [
    { id: guild.roles.everyone.id, type: OverwriteType.Role, deny: [P.ViewChannel] },
    { id: teamRoleId, type: OverwriteType.Role, allow: voice ? [P.ViewChannel, P.Connect] : [P.ViewChannel] },
    {
      id: staffRoleId,
      type: OverwriteType.Role,
      allow: voice ? [P.ViewChannel, P.Connect, P.MoveMembers] : [P.ViewChannel, P.ManageMessages],
    },
    { id: botId, type: OverwriteType.Member, allow: [P.ViewChannel, P.ManageChannels] },
  ];
}

async function provision(guild, teams, { dryRun, staffRoleId, botId, log = console.log }) {
  // 1. Reject bad input before touching the server.
  if (!staffRoleId || !guild.roles.cache.has(staffRoleId)) throw new Error('STAFF_ROLE_ID is missing or not a role in this server');
  const seen = new Set();
  for (const team of teams) {
    const key = slug(team.name ?? '');
    if (!key || seen.has(key)) throw new Error(`Missing or duplicate team name: "${team.name}"`);
    seen.add(key);
    for (const id of team.players ?? []) {
      if (!/^\d{17,20}$/.test(id)) throw new Error(`${team.name}: "${id}" is not a Discord user ID`);
    }
  }

  // 2. Work out what is missing.
  const channels = () => guild.channels.cache.filter((c) => !c.isThread());
  const find = (type, name) => channels().find((c) => c.type === type && c.name === name);
  const roles = new Map(guild.roles.cache.map((r) => [r.name, r]));
  const plan = [];
  teams.forEach((team, i) => {
    const category = `Teams ${Math.floor(i / TEAMS_PER_CATEGORY) + 1}`;
    if (!roles.has(team.name)) plan.push({ kind: 'role', name: team.name, team });
    if (!find(ChannelType.GuildCategory, category) && !plan.some((s) => s.kind === 'category' && s.name === category)) {
      plan.push({ kind: 'category', name: category });
    }
    if (!find(ChannelType.GuildText, slug(team.name))) plan.push({ kind: 'text', name: slug(team.name), team, category });
    if (!find(ChannelType.GuildVoice, team.name)) plan.push({ kind: 'voice', name: team.name, team, category });
  });

  // 3. Check Discord's caps before creating anything.
  const newRoles = plan.filter((s) => s.kind === 'role').length;
  const newChannels = plan.length - newRoles;
  log(`Roles: ${guild.roles.cache.size} existing + ${newRoles} new (cap ${MAX_ROLES})`);
  log(`Channels: ${channels().size} existing + ${newChannels} new (cap ${MAX_CHANNELS})`);
  if (guild.roles.cache.size + newRoles > MAX_ROLES) throw new Error('This would pass the role cap');
  if (channels().size + newChannels > MAX_CHANNELS) throw new Error('This would pass the channel cap');
  for (const step of plan) log(`${dryRun ? '[dry run] would create' : 'create'} ${step.kind}: ${step.name}`);

  // 4. Create roles, categories and channels, in that order.
  if (!dryRun) {
    const categories = new Map();
    for (const step of plan) {
      if (step.kind === 'role') {
        roles.set(step.name, await guild.roles.create({ name: step.name, mentionable: true, reason: 'Team setup' }));
      } else if (step.kind === 'category') {
        categories.set(step.name, await guild.channels.create({ name: step.name, type: ChannelType.GuildCategory }));
      } else {
        const voice = step.kind === 'voice';
        const parent = categories.get(step.category) ?? find(ChannelType.GuildCategory, step.category);
        await guild.channels.create({
          name: step.name,
          type: voice ? ChannelType.GuildVoice : ChannelType.GuildText,
          parent: parent.id,
          permissionOverwrites: overwrites(guild, roles.get(step.team.name).id, staffRoleId, botId, voice),
          reason: 'Team setup',
        });
      }
    }
  }

  // 5. Give each player their team role. Fetching one member by ID needs no privileged intent.
  for (const team of teams) {
    for (const id of team.players ?? []) {
      const member = await guild.members.fetch(id).catch((err) => {
        if (err.code === 10007 || err.code === 10013) return null; // Unknown Member / Unknown User
        throw err;
      });
      if (!member) {
        log(`Not in the server yet: ${id} (${team.name})`);
      } else if (!dryRun && !member.roles.cache.has(roles.get(team.name).id)) {
        await member.roles.add(roles.get(team.name), 'Team setup');
      }
    }
  }
}

if (require.main === module) {
  const [file, ...flags] = process.argv.slice(2);
  const client = new Client({ intents: [GatewayIntentBits.Guilds] });
  client.once(Events.ClientReady, async (c) => {
    try {
      const guild = await c.guilds.fetch(process.env.GUILD_ID);
      await Promise.all([guild.roles.fetch(), guild.channels.fetch()]);
      await provision(guild, JSON.parse(fs.readFileSync(file, 'utf8')), {
        dryRun: flags.includes('--dry-run'),
        staffRoleId: process.env.STAFF_ROLE_ID,
        botId: c.user.id,
      });
    } catch (err) {
      console.error(err.message);
      process.exitCode = 1;
    } finally {
      await c.destroy();
    }
  });
  client.login(process.env.DISCORD_TOKEN).catch((err) => {
    console.error(err.message);
    process.exitCode = 1;
  });
}

module.exports = { provision, overwrites, slug };
terminal
npm install discord.js@14
node --env-file=.env provision-teams.js teams.json --dry-run
node --env-file=.env provision-teams.js teams.json

A few things this handles on purpose. It runs on the Guilds intent only, because fetching a single member by ID is a plain API call and needs no privileged intent. It names text channels in lowercase with hyphens, the way Discord shows them, so a re-run recognizes them. Players who have not joined the server yet are listed rather than treated as errors; run it again once they join. Keep the team order in teams.json stable between runs, because it decides which category each team lands in. discord.js waits out Discord's rate limits by itself, so a big league takes a while: run it the day after registration closes.

Find the matches that need a room (start.gg)

If start.gg owns the bracket, the bot needs to know which sets are ready to play so it can open rooms and ping captains. start.gg has a public GraphQL API at https://api.start.gg/gql/alpha, authenticated with a token from your developer settings. Tokens expire after one year. The limits are an average of 80 requests per 60 seconds and 1,000 objects per request, nested ones included, so this query asks for 10 sets per page to leave room for 5v5 rosters.

open-sets.graphql
query OpenSets($slug: String!, $page: Int!) {
  event(slug: $slug) {
    sets(page: $page, perPage: 10, sortType: CALL_ORDER, filters: { hideEmpty: true }) {
      pageInfo { totalPages }
      nodes {
        id
        fullRoundText
        winnerId
        slots {
          entrant {
            id
            name
            participants {
              gamerTag
              requiredConnections { type externalId externalUsername }
            }
          }
        }
      }
    }
  }
}
startgg.js
// startgg.js: list the sets in a start.gg event that are ready to play but have no winner yet.
// Needs STARTGG_TOKEN from start.gg's developer settings (tokens expire after one year).
const fs = require('node:fs');
const path = require('node:path');

const QUERY = fs.readFileSync(path.join(__dirname, 'open-sets.graphql'), 'utf8');

async function openSets(eventSlug) {
  if (!process.env.STARTGG_TOKEN) throw new Error('STARTGG_TOKEN is not set');
  const open = [];
  for (let page = 1, totalPages = 1; page <= totalPages; page++) {
    const res = await fetch('https://api.start.gg/gql/alpha', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${process.env.STARTGG_TOKEN}`,
      },
      body: JSON.stringify({ query: QUERY, variables: { slug: eventSlug, page } }),
    });
    const body = await res.json();
    // Rate-limit and complexity errors arrive as { success: false, message }, not as GraphQL errors.
    if (body.errors || !body.data) {
      throw new Error(body.errors?.map((e) => e.message).join('; ') ?? body.message ?? `HTTP ${res.status}`);
    }
    if (!body.data.event) throw new Error(`No start.gg event found for "${eventSlug}"`);
    const { sets } = body.data.event;
    totalPages = sets.pageInfo.totalPages;
    open.push(...sets.nodes.filter((set) => !set.winnerId && set.slots.every((s) => s.entrant)));
  }
  return open;
}

module.exports = { openSets };

Call it with an event slug such as tournament/your-cup/event/valorant-5v5. hideEmpty skips sets still waiting for earlier results, and the filter drops sets that already have a winner or an empty slot. requiredConnections is where the players' linked Discord accounts appear, including the Discord user ID in externalId, but start.gg only returns it to tournament admins, so use a token from an admin account. I validated the query against start.gg's published schema and tested the script against recorded responses on 27 September 2026. When a match is confirmed in Discord, the reportBracketSet mutation sends the winner back to the bracket.

When a custom tournament bot is worth it

For a one-off cup, a free bot plus the structure above is enough. A custom bot starts to pay for itself when you run a league every season with 100 or more teams, keep rosters and stats across seasons, connect Discord to your own site or to start.gg, FACEIT or Riot data, or want one bot instead of four. That is the work I do: see custom esports and tournament bots for what it covers and costs, or send me your league's details for a fixed quote.

Questions

Can you host a tournament on Discord for free?

Yes. As of September 2026, BattleBot is free for brackets of 4 to 32 players, Tourney Bot's free tier covers 3 tournaments a month with 8 participants, Team Up has a free plan, Battlefy is free for organizers, and Challonge's Standard plan is $0 for up to 256 participants.

Is there an official Challonge Discord bot?

Challonge's pricing and feature pages list no Discord bot as of September 2026. Community-built bots use Challonge's API, which now allows 500 requests a month on the free plan and 2,000 with Premier, so a bot that checks a bracket every minute needs a paid API plan.

What is a Discord scrim bot?

A bot that sets up practice matches: it posts a scrim or custom-game slot, lets players join a side, and records the score. ScrimBot, an open-source bot for VALORANT custom games, works this way but its README says it is no longer actively maintained. For pickup games with ratings, Team Up's matchmaking queues are the closer fit.

Can a bot disqualify teams that miss check-in?

Yes, but keep a human on the final call. Tourney Bot's End option removes everyone who did not check in, Battlefy drops a team when its match check-in timer runs out, and start.gg lets an admin remove late entrants and rebalance pools when check-in is finalized.

Sources

Prices and features were checked on September 27, 2026.

  1. Discord Support, Account caps, server caps and more (250 roles, 500 channels including categories)
  2. Discord, Opcodes and status codes (250 roles, 500 channels)
  3. Discord, Channel resource (50 channels per category, private threads)
  4. Discord, Threads (threads do not count toward the channel limit)
  5. Discord, Permissions (hierarchy and overwrites)
  6. Discord, Guild resource (Create Guild Channel, Get and List Guild Members)
  7. Discord, Message formatting and timestamp styles
  8. start.gg Help Center, Online event check-in
  9. start.gg Help Center, Online round settings (DQ and verify timers)
  10. start.gg Help Center, Reporting match results
  11. start.gg Help Center, Online match moderation
  12. start.gg Help Center, Account connections in registration
  13. start.gg Help Center, Admin permissions
  14. start.gg Help Center, Platform fee and processing fee
  15. start.gg Help Center, League of Legends tournament codes
  16. start.gg Help Center, VALORANT ruleset
  17. start.gg Developer Portal, Sending requests (API endpoint)
  18. start.gg Developer Portal, Rate limits
  19. start.gg Developer Portal, Authentication
  20. start.gg Developer Portal, Report set mutation
  21. start.gg schema reference, Participant (requiredConnections is admin-only)
  22. start.gg schema reference, SetFilters (hideEmpty)
  23. Challonge, Pricing
  24. Challonge, Integration and API features by plan
  25. Challonge API v2.1, Getting started
  26. Battlefy, Organize (pricing)
  27. Battlefy Help Center, Setting up match check-in
  28. Battlefy Help Center, Linking Discord accounts
  29. Tourney Bot, Premium tiers
  30. Tourney Bot, Hosting a tournament (match threads, score reporting, disputes)
  31. Tourney Bot, Check-in process
  32. Tourney Bot, Match proof system
  33. BattleBot, Free Discord tournament bot
  34. Team Up, Pricing
  35. Team Up, Discord tournament bot
  36. Valve, Counter-Strike Major supplemental rulebook (map pick-ban)
  37. FACEIT for Developers, Webhooks
  38. Twitch, EventSub subscription types
  39. ScrimBot on GitHub
  40. Node.js, --env-file flag

Tell me what your server needs

Send a short brief. You get a reply within one business day with questions or a price range, and a fixed quote before any work starts.