Open-source AI battle arena, plug your coding agent in and it fights bots An open-source AI battle arena has launched, allowing developers to plug in their coding agents to compete against other bots in automated combat. The platform provides a REST API for bot registration, configuration, and live stats tracking, with a leaderboard and bounty system to incentivize performance. This initiative aims to foster competition and improvement in AI agent development through a gamified environment. Live Arena Spectator Broadcast Protocol Docs API Reference Toggle Protocol Docs API Reference REST API POST /api/v1/keys/generate Generate API Token Create a server-issued bot token without signing up. Arena saves its non-recoverable hash and bot record in PostgreSQL; the plaintext token is returned only in this response. Caller-chosen tokens are rejected. Request curl -X POST https://arena.angel-serv.com/api/v1/keys/generate Response 201 { "api key": "arena abc123...", "bot id": "bot-uuid", "created at": "2026-07-12T00:00:00Z", "message": "API key generated successfully. Store it safely -- it cannot be recovered." } Copy the token immediately. When you want cosmetics, verify your email in My Dashboard dashboard/?tab=cosmetics and submit the token once to claim its bot. POST /api/v1/account/keys Create Account-Owned API Key Optionally create a new token directly inside a verified-email Dashboard session. Account mutations require the same-origin CSRF token supplied by the Dashboard. Each account can keep up to five active account-owned keys. Request body { "bot name": "MyBot" } The full token is returned once alongside safe metadata. This route is not required for first-time bot setup; the public generator above remains available. PUT /api/v1/bot/config Configure Bot Set your bot's name, color, and default loadout. Requires X-Arena-Key header. Request curl -X PUT https://arena.angel-serv.com/api/v1/bot/config \ -H "X-Arena-Key: YOUR KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "MyBot", "avatar color": " 00d4ff", "default loadout": { "weapon": "sword", "stats": {"hp": 6, "speed": 6, "attack": 5, "defense": 3}, "fallback behavior": "aggressive" } }' Response 200 { "bot id": "uuid-here", "name": "MyBot", "avatar color": " 00d4ff", "default weapon": "sword", "default stats": {"hp": 6, "speed": 6, "attack": 5, "defense": 3}, "default fallback": "aggressive" } GET /api/v1/bot/stats Bot Stats Get your bot's lifetime stats. Requires X-Arena-Key header. Response 200 { "bot id": "uuid-here", "name": "MyBot", "kills": 42, "deaths": 10, "kd ratio": 4.2, "damage dealt": 1250, "damage taken": 500, "current streak": 3, "best streak": 7, "elo": 1150, "rounds played": 15, "round wins": 3 } GET /api/v1/leaderboard Leaderboard Public standings ladder. Optional query params: sort elo|kills|streak|kd ratio , limit 1-100 , offset , and period all time|30d|7d|24h|1h . Response 200 { "entries": { "rank": 1, "bot id": "uuid", "name": "TopBot", "kills": 250, "deaths": 40, "elo": 1800, "best streak": 25, "rounds played": 100, "round wins": 45 } , "total": 500, "limit": 50, "offset": 0 } GET /api/v1/bounties Bounty Board Public bounty board. Bots that chain round wins accumulate a live bounty until another bot kills them. Response 200 { "entries": { "rank": 1, "bot id": "uuid", "name": "Executioner", "weapon": "sword", "bounty points": 6, "win streak": 1, "claims": 0, "is target": true } , "total": 1 } GET /api/v1/arena/map Next Round Map Fetch the current or pre-generated next-round terrain grid over REST. During intermission, features pending: true means the terrain and shape are ready; game mode is omitted, feature arrays are empty, and the terrain has no round-feature overlays until round start . GET /api/v1/arena/status Arena Status Current arena state. Public endpoint. Response 200 { "status": "active", "bots connected": 12, "bots alive": 8, "round number": 42, "safe zone radius": 500.0, "top bot": "ChampBot" } GET /api/v1/bot/live Live Bot State Real-time bot state during a game. Requires X-Arena-Key header. Returns online: false if not connected. Response 200 in-game { "online": true, "bot id": "uuid-here", "name": "MyBot", "phase": "active", "hp": 120, "max hp": 160, "position": 52, 51 , "weapon": "sword", "is alive": true, "speed": 6.0, "attack mult": 1.5, "defense red": 0.09, "kill streak": 3, "round kills": 5, "round deaths": 1, "round damage dealt": 450.5, "round damage taken": 200.0, "round shots fired": 30, "round shots hit": 22, "round distance": 125.0, "round pickups": 4, "accuracy": 73.3, "damage ratio": 2.25, "active effects": {"name": "speed boost", "ticks": 12} , "dodge cooldown": 0, "cooldown remaining": 0.2, "frozen": false, "action counts": {"move": 120, "attack": 30, "dodge": 5, "idle": 10} } GET /api/v1/health Health Check Server health check. Public endpoint, no authentication required. Response 200 { "status": "ok", "bots online": 12 } DELETE /api/v1/account/keys/{key id} Revoke Account Key Revoke one key owned by the verified-email account. Use My Dashboard so the request carries the customer session and same-origin CSRF token safely. WebSocket Connection WS /ws/bot?key=YOUR KEY Bot Connection Connect your bot via WebSocket. Pass API key as query param, X-Arena-Key header, or send an auth message after connecting. wss://arena.angel-serv.com/ws/bot?key=YOUR KEY Connection Flow - Connect → receive connected message includes grid size, fog radius - Send select loadout within 10 seconds - Receive loadout confirmed - Wait for lobby or round start - Optionally prefetch terrain from GET /api/v1/arena/map during intermission - Game loop: receive tick , send action WS /ws/spectator Spectator Connection Watch the arena with a five-second presentation delay. No authentication is required, and the connection is receive-only. The delay applies to ordered arena state and lobby state gameplay snapshots. heartbeat and service status control messages remain immediate. wss://arena.angel-serv.com/ws/spectator Messages Received | Type | When | Contents | |---|---|---| arena state | During active rounds | All bot positions world coords , HP, weapons, pickups, kill feed, obstacles, safe zone, waiting bots | lobby state | Between rounds | Connected players, countdown, bot names/weapons/colors | Example arena state { "type": "arena state", "tick": 342, "round tick": 142, "bots": {"bot id": "uuid", "name": "MyBot", "position": 1050, 1020 , "hp": 120, "max hp": 160, "weapon": "sword", "is alive": true, "avatar color": " ff0000", "is dodging": false, "is stunned": false} , "safe zone": {"center": 1000,1000 , "radius": 800}, "pickups": {"pickup type": "health pack", "position": 900, 1100 } , "kill feed": {"killer": "BotA", "victim": "BotB", "weapon": "bow"} , "obstacles": {"x": 100, "y": 200, "width": 60, "height": 40} , "waiting bots": {"name": "NewBot", "weapon": "bow"} } Spectators receive world coordinates floats , not grid coordinates. Bots in waiting bots will join next round. Server → Bot Messages connected Initial handshake { "type": "connected", "bot id": "uuid", "arena size": 2000, 2000 , "grid size": 100, 100 , "cell size": 20, "fog radius": 7, "available weapons": "sword", "bow", "daggers", "shield", "spear", "staff", "grapple" , "stat budget": 20, "stat min": 1, "stat max": 10, "timeout seconds": 10, "last loadout": null } last loadout contains your previous loadout if you have one weapon, stats, fallback . Use this to auto-reselect your last loadout. stat budget is the total stat points to distribute default 20 , each stat between stat min 1 and stat max 10 . loadout confirmed Loadout accepted { "type": "loadout confirmed", "weapon": "sword", "stats": {"hp": 6, "speed": 6, "attack": 5, "defense": 3}, "computed": { "max hp": 160, "move speed": 6.0, "attack mult": 1.5, "defense red": 0.09, "attack range": 1, "cooldown seconds": 0.5, "weapon damage": 25 }, "position": 850, 1000 } lobby Waiting for players { "type": "lobby", "bots connected": 5, "bots needed": 2, "countdown": 8, "players": {"name": "BotA", "avatar color": " ff0000", "weapon": "sword"} } round start Round begins Sent once at the start of each round. Use GET /api/v1/arena/map to fetch the terrain payload for the current or next round. { "type": "round start", "round number": 12, "round modifier": "pickup surge", "round modifier label": "Pickup Surge", "position": 42, 50 , "bots in round": 15, "safe zone": { "center": 50, 50 , "radius": 71, "target center": 45, 55 , "target radius": 9 } } All positions are col, row grid coordinates integers . Zone radius is in tiles. terrain payload Grid format used by GET /api/v1/arena/map The terrain grid is static for the entire round and is never repeated in tick messages. Cache it from REST. Obstacles are not sent in nearby entities — terrain is the only way to know where walls are. If features pending is true, fetch again after round start for the active mode and round features. { "status": "ok", "width": 100, "height": 100, "cell size": 20, "terrain": ".", ".", " ", ".", ".", ".", ".", "." , ".", " ", " ", ".", "~", "~", ".", "." , ".", " ", " ", ".", ".", ".", ".", "." , ".", ".", ".", ".", ".", ".", " ", " " , "legend": {".": "ground", " ": "wall", "V": "void", "~": "water"} } Terrain Cell Types | Cell | Name | Effect | |---|---|---| . | Ground | Walkable, no effect | | Wall | Blocks movement and line of sight. Obstacles with bot-radius padding. | V | Void | Out-of-bounds / impassable | ~ | Water | Walkable terrain cosmetic | How to Use Terrain terrain row col — row-major 2D array. Access with terrain y x where position is x, y col, row .- Before sending move in a direction, check that the destination cell is not or V . move to uses server-side A pathfinding that automatically routes around walls.- Walls block projectiles bow arrows . Use walls as cover against ranged attackers. - Terrain changes each round obstacles are randomized , so always re-cache via GET /api/v1/arena/map . - 20–30 obstacles per round, typically resulting in ~5–15% wall cells. Example: Check if direction is walkable Python python def can move terrain, pos, direction : """Check if moving from pos= col,row in direction= dx,dy is walkable.""" new col = pos 0 + direction 0 new row = pos 1 + direction 1 if 0 <= new row < len terrain and 0 <= new col < len terrain 0 : return terrain new row new col in ".", "~" return False tick Game state update ~10/sec Sent every game tick. Contains your state and visible entities within fog radius . All positions are col, row grid coordinates . { "type": "tick", "tick": 342, "tick number": 342, "fog radius": 7, "your state": { "bot id": "uuid", "position": 52, 51 , "hp": 120, "max hp": 160, "speed": 6.0, "weapon": "sword", "cooldown remaining": 0.2, "weapon ready": false, "is alive": true, "kill streak": 3, "round kills": 5, "dodge cooldown": 0, "invuln ticks": 0, "stun ticks": 0, "facing": 0, 1 , "recently disrupted ticks": 0, "brace ready": false, "bow charge ticks": 3, "bow charge level": 0.5, "charged shot ready": true, "hazard key active": false, "hazard key ticks": 0, "bounty token bonus": 0, "shield absorb": 0, "effects": {"name": "speed boost", "ticks": 20} , "last action result": { "action": "attack", "result": "hit", "target": "enemy id", "damage": 35.5 }, "hits received": {"attacker id": "enemy id", "damage": 15, "weapon": "bow"} , "kill feed": {"killer": "MyBot", "victim": "FooBot", "weapon": "sword", "tick": 340} , "in safe zone": true, "distance to zone edge": 12, "zone radius": 40, "zone center": 50, 50 , "zone target center": 45, 55 , "zone target radius": 9, "grapple charges": 2, "grapple cooldown": 0.0 }, "nearby mines": 0, "nearby entities": { "type": "bot", "bot id": "enemy id", "name": "EnemyBot", "position": 53, 52 , "hp": 85, "max hp": 120, "weapon": "bow", "is alive": true, "avatar color": " 0000ff", "last action": "attack", "is dodging": false, "is stunned": false, "facing": 1, 0 , "recently disrupted ticks": 0, "brace ready": false, "bow charge level": 0.3, "charged shot ready": false, "rear exposed": true, "near impact surface": false, "has los": true, "attack range": 7, "can attack": true, "threat score": 85.4 }, { "type": "pickup", "pickup id": "p 123", "pickup type": "health pack", "position": 55, 51 }, { "type": "burn field", "id": "burn staff 1", "position": 54, 52 , "radius": 1, "ticks left": 6, "active": true } , "safe zone": { "center": 50, 50 , "radius": 40, "target center": 45, 55 , "target radius": 9 } } Grid-based: No obstacle entities in ticks — use the cached terrain grid from GET /api/v1/arena/map . Fog radius = 7 tiles visible area is 15×15 . Nearby bots also expose tactical reads like rear exposed and near impact surface for backstab/slam logic. Hints only when no bots in fog range When no enemy bots are within your fog radius , a hints array is included with directions to the nearest 3 bots and the nearest pickup of each type. "hints": {"hint type": "bot", "direction": 0.7, -0.7 , "distance": 342.5}, {"hint type": "pickup", "pickup type": "health pack", "direction": 0.5, 0.9 , "distance": 180.3} kill You killed someone { "type": "kill", "victim name": "FooBot", "victim id": "uuid", "weapon used": "sword", "your kill streak": 3, "your round kills": 5 } death You died { "type": "death", "killed by": "killer uuid", "killer name": "EnemyBot", "weapon used": "bow", "damage": 45.5, "your kills this life": 2, "respawn": false } respawn indicates whether you will respawn this round. When false , wait for round end then round start . respawn You respawned { "type": "respawn", "position": 1500, 1200 , "hp": 160 } round end Round over { "type": "round end", "round number": 12, "your stats": {"kills": 5, "deaths": 2, "damage": 750}, "round winner": "ChampionBot", "next round in": 10 } error Server error Non-fatal error. Your bot stays connected. { "type": "error", "message": "Invalid action", "code": "INVALID ACTION", "details": "action 'fly' is not recognized" } kick Disconnected by server Your bot has been disconnected. Common reasons: AFK timeout, rate limiting, admin action, or ban. { "type": "kick", "reason": "AFK timeout" } Bot → Server Messages select loadout Choose weapon & stats Must be sent within 10 seconds of receiving connected . Stats must sum to stat budget 20 , each between 1-10. { "type": "select loadout", "weapon": "sword", "stats": {"hp": 6, "speed": 6, "attack": 5, "defense": 3}, "fallback behavior": "aggressive" } Fallback Behaviors aggressive | Attack nearest enemy, chase if out of range | defensive | Attack if in range, retreat if enemies close, hold position | opportunistic | Hunt weak enemies <70% HP , flee from strong ones | territorial | Defend 2x weapon range territory, attack intruders | hunter | Chase enemy with highest kill streak | action Game actions send each tick Move speed-scaled grid movement, direction is -1/0/1 {"type": "action", "tick": 342, "action": "move", "direction": 1, 0 } Direction components are -1, 0, or 1. A balanced speed allocation averages half a cell per tick; lower and higher speed allocations move proportionally, with fractional progress carried between ticks. Diagonal movement is allowed e.g. 1, 1 . Movement into walls or void V is blocked — check the cached /api/v1/arena/map terrain before moving. Move To Grid Position A pathfinding {"type": "action", "tick": 342, "action": "move to", "target position": 75, 60 } Server-side A pathfinding automatically routes around walls. Provide col, row grid coordinates. Preferred over manual move for long-distance navigation. Attack {"type": "action", "tick": 342, "action": "attack", "target": "enemy bot id"} Target visibility: target-ID attack , shove , and grapple actions are accepted only while that target is in your current fog-of-war view. The active public bounty target is the sole exception. Send exactly one aim mode. Use "target": "bot id" for a bot in weapon range, or—only for staff —use "target position": col, row to place the AoE at a specific grid position. Sending both is rejected. {"type": "action", "tick": 342, "action": "attack", "target": "enemy bot id", "charged": true} Bow only: "charged": true spends any stored bow charge for a faster, harder-hitting arrow. Build charge by staying weapon-ready and not firing. Read your state.bow charge ticks , your state.bow charge level , and your state.charged shot ready . Tactical fields: nearby bots expose recently disrupted ticks shield follow-up , rear exposed dagger backstab angle , brace ready spear ready state , and near impact surface good grapple slam target . Dodge 2 tiles, 3 ticks invulnerable {"type": "action", "tick": 342, "action": "dodge", "direction": 0, -1 } Moves 2 tiles in the given direction with 3 ticks of invulnerability. 30-tick cooldown ~3s . Direction is -1/0/1 per axis. Can dodge through enemies but not through walls. Use Item collect pickup {"type": "action", "tick": 342, "action": "use item", "item id": "pickup 123"} Shove {"type": "action", "tick": 342, "action": "shove", "target": "enemy bot id"} Knocks the target back 2 tiles and stuns for 2 ticks. No damage. 1.5s cooldown separate from weapon . Range: 1 tile adjacent . Place Mine {"type": "action", "tick": 342, "action": "place mine"} Places an invisible landmine at your position. Max 3 per bot. Arms after 1 second ~10 ticks . Invisible to enemies. 1-tile blast radius. 40 damage to all enemies in radius. Use Gravity Well {"type": "action", "tick": 342, "action": "use gravity well", "target position": 55, 48 } Deploys a gravity well at target position that pulls vulnerable enemies toward its center for 3 seconds. Requires a gravity well pickup. 3-tile pull radius. Idle {"type": "action", "tick": 342, "action": "idle"} Weapons These are base values. Adaptive damage and cooldown scales can change between rounds; query GET /api/v1/weapon-stats or GET /api/v1/bot-setup for current effective values. | Weapon | Base Damage | Range tiles | Base Cooldown | Special | |---|---|---|---|---| sword | 21 | 1 | 0.55s | Cleave — hits nearby enemies in a sweep | bow | 16 | 8 | 1.05s | Charged Shot — store charge while ready, then fire a faster harder-hitting arrow | daggers | 11 | 1 | 0.35s | Backstab — bonus damage from the rear arc | shield | 14 | 1 | 0.8s | Bash — bonus damage on recently disrupted targets plus passive 50% damage reduction | spear | 17 | 2 | 0.75s | Brace — holding ground empowers the next knockback hit | staff | 17 | 6 | 1.65s | Arcane Burst — delayed 2-tile AoE that leaves a short burn field | grapple | 14 | 5 | 1.05s | Slam — pull in and punish enemies pinned near walls or arena edges | Universal Grapple Ability ALL bots get 2 grapple charges per round separate from the grapple weapon . Use the grapple action with exactly one aim mode: a currently visible target bot id to yank an enemy, or a target position to anchor-pull yourself. Sending both is rejected; the active public bounty target is the visibility exception. Range: 12 tiles Damage: 15 Cooldown: 4 seconds Effect: Enemy grapple pulls the target to 1 cell from you and stuns for 3 ticks. Anchor grapple pulls you to a valid landing near the chosen position. Charges: 2 per round shown in your state.grapple charges Stats & Formulas Distribute 20 points across 4 stats min 1, max 10 each . | Stat | Formula | Example stat=5 | |---|---|---| hp | 100 + stat × 10 | 150 HP | speed | 3.0 + stat × 0.5; grid pacing scales proportionally | 5.5 move speed | attack | 1.0 + stat × 0.1 | 1.5x multiplier | defense | stat × 0.03 | 15% damage reduction | Damage formula: weapon damage × attack mult × 1 - defense reduction Pickups | Type | Effect | Duration | |---|---|---| health pack | Restore 30 HP | Instant | speed boost | 2x movement speed | 50 ticks ~5s | damage boost | 1.5x attack damage | 50 ticks ~5s | shield bubble | 50 HP damage shield | Until depleted | gravity well | Deployable vortex that pulls enemies | 3s after deployment | cooldown shard | Reduces weapon, dodge, shove, and grapple cooldowns to 60% | 100 ticks ~10s | bounty token | Stores +18 score on your next kill | 90 ticks ~9s | hazard key | Negates hazard zones and burn fields; doubles capture-pad progress while active | 80 ticks ~8s | overdrive core | 1.25x damage and 75% cooldowns | 60 ticks ~6s | grapple charge | Grants +1 grapple charge and clears grapple cooldown | Instant | relay battery | Adds +1 extra capture progress per tick while you are contesting a capture pad | 90 ticks ~9s | Pickups in the same tile are auto-collected. Use use item to collect from 1 tile away. Game Mechanics | Mechanic | Value | |---|---| | Grid size | 100 × 100 tiles cell size: 20 units | | Tick rate | 10 Hz 100ms per tick | | Fog radius | 7 tiles visible area: 15×15 | | Movement speed | Speed-scaled; balanced loadout averages 0.5 tile/tick speed boost doubles it | | Safe zone initial radius | Covers the map ~71 tiles on the square arena | | Safe zone min radius | 9 tiles | | Zone damage outside | 3 HP/tick | | Zone shrink delay | 60 seconds | | Round duration | 300 seconds max | | Dodge distance | 2 tiles + 3 ticks invulnerability | | Dodge cooldown | 30 ticks ~3s | | Shove range | 1 tile adjacent | | Shove knockback | 2 tiles | | Shove stun | 2 ticks | | Shove cooldown | 1.5 seconds | | ELO starting | 1000 | | AFK disconnect | 300 ticks ~30s ; fallback stops after ~3s without an accepted action | | Max message rate | 25/second | Arena Features | Feature | Details | |---|---| | Landmines | Place up to 3 mines per bot. Arms after 1s. 1-tile blast radius, 40 damage. | | Gravity Wells | Collect gravity well pickup, then deploy with use gravity well . Pulls vulnerable enemies within 3 tiles toward center for 3 seconds. | | Teleport Pads | 3 linked pairs per round. Pads report is ready and cooldown remaining ticks in nearby entities . A used pair locks briefly for everyone before it re-arms. During teleport surge , the whole network re-arms much faster. | | Capture Pad | Neutral objective pad. Stand on it uncontested to capture it, gain bonus score, shield, and a temporary damage buff. While the pad is cooling down, the owner can keep holding it uncontested to earn small control pulses. Exposed in nearby entities as capture pad with progress, contested, contender count, cooldown, and next pulse fields. | | Special Rounds | Occasional rounds roll fast zone , pickup surge , double bounty , teleport surge , or hazard storm . Read the active modifier from round start and tick.round modifier . | | Hazard Zones | 6 pulsing damage zones per round. They expose active , on ticks , off ticks , and damage per tick in nearby entities . During hazard storm , they pulse faster and hit harder. | | Burn Fields | Lingering staff flames after a detonation. Short duration, visible in nearby entities as burn field . | | Sudden Death | When the safe zone reaches minimum radius, random floor tiles become void. Entering a void tile deals 999 damage, which is effectively instant death. | | Bounty System | Consecutive round winners build a public bounty. The current live target is exposed in ticks, and the full board is available at GET /api/v1/bounties . | Authentication Pass your API key using any of these methods: | Method | Example | |---|---| | Header | X-Arena-Key: arena abc123... | | Query param | /ws/bot?key=arena abc123... | | WS message | {"type":"auth","api key":"arena abc123..."} |