There is no separate "bot API" in Haxball. A bot is just code holding a reference to the headless room object — reacting to its events and calling its methods. Once you have seen that, the whole ecosystem stops looking mysterious.
The anatomy of a bot
Almost everything reduces to this shape: something happened → decide → act on the room.
// Auto-admin the first player in, so an empty room is never stuck without one.
room.onPlayerJoin = function (player) {
if (room.getPlayerList().filter(p => p.id !== 0).length === 1) {
room.setPlayerAdmin(player.id, true);
room.sendAnnouncement("You have admin — you were first in.", player.id);
}
};
Stack enough of those and you have auto-admin, welcome messages, anti-spam, AFK handling, team balancing, draft picks, match records and league enforcement. The building blocks are the same every time.
What people actually build
| Behaviour | Built from | Why it exists |
|---|---|---|
| Auto-admin | join/leave events + setPlayerAdmin |
So a room is never left with nobody able to start a game. |
| AFK handling | activity tracking + setPlayerTeam or
kickPlayer |
One idle player in a 4v4 ruins the game for seven others. |
| Anti-spam | chat events + rate tracking | Listed rooms attract flooders. Chat mute beats a kick for a first offence. |
| Team balancing / draft | join events + setPlayerTeam,
chat commands |
Fair sides without an argument every round. |
| Match records and stats | goal/victory events, disc reads | Ladders, head-to-head records, and something to argue about after. |
| League enforcement | setTeamsLock,
setScoreLimit, setTimeLimit, setPassword |
The format holds whether or not the right admin turned up. |
Mistake one: doing work on the tick
The room object offers a per-tick event. It fires 60 times a second, which gives you a budget of roughly 16.7 ms per tick — and that budget is shared with the physics simulation itself.
Put something slow in there and you do not merely slow your bot down. You delay the simulation. Every player in the room feels stutter, and no amount of good hosting or low ping hides it, because the delay is happening on the host before the packets are ever sent.
Never do these in a tick handler: write to a file or database, make an HTTP request, log a line per tick, recompute over the full player list on every tick, or build strings you throw away 60 times a second.
The fix is decimation: let the tick count, and act on a fraction of them. Sampling positions three times a second is plenty for possession stats, and costs you a twentieth of the work:
var n = 0;
room.onGameTick = function () {
if (++n % 20) return; // ~3 Hz instead of 60 Hz
sample(room.getBallPosition());
};
Anything genuinely expensive — writing to storage, calling an API — belongs on a timer or a batched flush, off the tick path entirely. Our own runner treats the raw 60 Hz tick as reserved and hands plugins a decimated one, precisely because this mistake is so easy to make and so hard to diagnose from the inside.
Mistake two: assuming the room remembers
A room is a process. Restart it — a crash, an update, a move to another location — and it is a brand new process with no recollection of anything your script was holding.
Which means, unless you deliberately arranged otherwise:
- Bans are gone. The person you banned last night can walk back in.
- Stats are gone. That ladder you have been building all season, gone.
- Warnings and strikes are gone. Everyone is a first-time offender again.
- In-progress state is gone. Mid-draft, mid-tournament, mid-anything.
If it must survive a restart, it has to live outside the room process — a database, an API, a file on disk that the room reads at startup. "It has worked for weeks" is not evidence it persists; it is evidence nothing has restarted yet.
What this asks of your host
Scripts change what you should want from a host:
- Real logs. When a script misbehaves at 2 am you need to see what it did. A room with no console is a room you cannot debug.
- Headroom that is actually yours. If your room shares a machine with no resource limits, someone else's runaway script becomes your stutter.
- Restarts that restore settings. A room that comes back with the default stadium and no limits has not really come back.
- Somewhere to keep state. If the platform gives you durable bans and stats, you do not have to build persistence yourself — which is most of the work.
Your scripts, our uptime
Custom room scripts and custom stadiums on every plan, with a live console, player admin and bans that survive a restart.
See the plansWhere to go next
- The headless host — the full room object surface your script is written against.
- Why Haxball lags — including the host-side stutter a bad tick handler causes.
- How to host a Haxball room — start here if you are still choosing how to run it.