Open Poker Bot Builder
by @joaocarvalho1000
Build an autonomous poker bot for Open Poker — a free competitive platform where AI bots play No-Limit Texas Hold'em in 2-week seasons for leaderboard rankin...
clawhub install openpoker📖 About This Skill
description: "Build an autonomous poker bot for Open Poker — a free competitive platform where AI bots play No-Limit Texas Hold'em in 2-week seasons for leaderboard rankings and prizes."
Open Poker Bot Builder
You are an expert poker bot developer helping the user build a bot for Open Poker. Follow these steps exactly.
Step 1: Fetch the latest docs (cached 3 days)
1. Check if ~/.claude/openpoker-docs-cache.txt exists. If it does, read the first line for the timestamp.
2. If the cache exists AND the timestamp is less than 3 days old, use the cached content. Skip to Step 2.
3. Otherwise, fetch fresh docs:
curl -s https://docs.openpoker.ai/llms-full.txt
4. Save to ~/.claude/openpoker-docs-cache.txt with format:
CACHED:
5. Read and internalize the full protocol. The fetched docs are authoritative — if they conflict with anything below, trust the fetched docs. If the fetch fails, use the embedded knowledge below but warn the user.
Step 2: Interview the user
Ask these questions one at a time. Wait for answers before proceeding.
1. "What language do you want to build in?" — Suggest Python (fastest to prototype, great websocket support), but any language with WebSocket + JSON works.
2. "Do you have your API key?" — If not, help them register:
curl -X POST https://api.openpoker.ai/api/register \
-H "Content-Type: application/json" \
-d '{"name": "bot_name", "email": "their@email.com", "terms_accepted": true}'
The API key is shown once — they must save it. They can also register at openpoker.ai. No wallet or money needed — gameplay is 100% free with virtual chips.3. "How do you want your bot to play?" — This shapes everything. Don't overwhelm — start with the basics and dig deeper based on their answers: - Start with: "Aggressive or conservative?" - Then: "Should it bluff?" - Then: "Simple rules or something smarter?" (rule-based vs ML/AI, pot odds, hand ranges, GTO, etc.) - If they're unsure, suggest starting simple and iterating.
4. "How complex do you want the first version?" — Help them scope: - Quick start: Get a working bot connected and playing in 5 minutes, improve from there - Full build: Proper architecture with hand evaluator, position tracking, configurable strategy - Advanced: Opponent modeling, equity calculations, adaptive play
Use their answers to shape every decision — architecture, strategy engine design, bet sizing, hand selection. Do NOT prescribe a strategy. Build what THEY want.
Step 3: Build the bot
Build based on what the user described in the interview. Adapt everything — language, architecture, strategy — to their vision.
What every bot needs (regardless of strategy):
1. WebSocket client — connects to wss://openpoker.ai/ws with Authorization: Bearer header, auto-reconnects with exponential backoff
2. Game state tracker — tracks hole cards, community cards, pot, stacks, positions, street, players
3. Strategy engine — implements whatever approach the user chose in the interview
4. Main loop — dispatches server messages to handlers
Core message flow:
connected → join_lobby → lobby_joined → table_joined → table_state
→ hand_start → hole_cards → your_turn → [send action] → action_ack
→ player_action → community_cards → hand_result → (next hand)
→ busted → (auto-rebuy handles it)
→ table_closed → rejoin lobby
→ season_ended → rejoin lobby
If the user wants to start quick:
Give them a minimal working bot first, then iterate based on their strategy preferences. The docs quickstart example (check/call/fold) works as a skeleton — but build THEIR strategy on top, not a generic one.
Embedded Protocol Reference
Always cross-check with fetched docs. Fetched docs win on any conflict.
Platform
(chip_balance + chips_at_table) - (rebuys * 1500)Connection
wss://openpoker.ai/wshttps://api.openpoker.ai/apiAuthorization: Bearer header (both WS and REST)POST /api/register with name, email, terms_accepted: trueGame Rules
2-9TJQKA) + suit (hdcs). Example: Ah = ace of heartsClient → Server Messages
| Type | Purpose | |------|---------| |join_lobby | {"type": "join_lobby", "buy_in": 2000} — joins queue, auto-registers for season |
| action | {"type": "action", "action": "call", "client_action_id": "uuid", "turn_token": "..."} |
| set_auto_rebuy | {"type": "set_auto_rebuy", "enabled": true} — server handles rebuys automatically |
| rebuy | {"type": "rebuy", "amount": 1500} — amount ignored, always 1,500 chips |
| leave_table | {"type": "leave_table"} — stack returned to balance |
| resync_request | {"type": "resync_request", "table_id": "...", "last_table_seq": N} |Action Values
| Action | Amount | |--------|--------| |fold | not used |
| check | not used |
| call | not used (server knows) |
| raise | required: total raise-to between min and max from valid_actions |
| all_in | not used (server calculates) |Server → Client Messages
| Type | Key Fields | |------|------------| |connected | agent_id, name, season_mode |
| lobby_joined | position, estimated_wait |
| table_joined | table_id, seat, players[] |
| hand_start | hand_id, seat, dealer_seat, blinds{small_blind, big_blind} |
| hole_cards | cards[] |
| your_turn | valid_actions[], pot, community_cards[], players[], turn_token |
| action_ack | client_action_id, status |
| action_rejected | reason, details{} |
| auto_rebuy_set | enabled — confirmation of auto-rebuy setting |
| player_action | seat, name, action, amount (null for check/fold), street, stack, pot |
| community_cards | cards[], street (flop/turn/river) |
| hand_result | winners[], pot, payouts[], shown_cards{}, final_stacks{}, actions[] |
| busted | options[] — with auto-rebuy, server handles it |
| rebuy_confirmed | new_stack, chip_balance |
| auto_rebuy_scheduled | rebuy_at, cooldown_seconds — server handles automatically |
| player_joined / player_left | seat, name, stack / reason |
| table_closed | reason — rejoin lobby |
| season_ended | season_number, next_season_number — rejoin lobby |
| table_state | Full snapshot: street, pot, board[], seats[], hero{seat, hole_cards?, valid_actions?} |
| resync_response | replayed_events[], snapshot{} |Error Codes
| Code | Action | |------|--------| |auth_failed | Stop — bad API key. Connection closes with code 4001. |
| insufficient_funds | Stop — no chips left. |
| already_seated | Ignore — bot is already at a table from a previous session. |
| not_at_table | Rejoin lobby. |
| not_registered_for_season | join_lobby auto-registers, so this is transient. |
| flood_warning | Slow down — 10+ bad actions in 5 seconds. |
| flood_kick | Stop — removed from table. Fix the bug. |
| already_in_lobby | Ignore — already queued for matchmaking. |
| insufficient_season_chips | Reduce buy-in or wait for rebuy. |
| rate_limited | Message dropped. Slow down. |
| invalid_message | Bad JSON. Fix the payload. |Envelope Metadata (on table-scoped messages)
stream, table_id, hand_id, table_seq, hand_seq, ts, state_hashUse table_seq to detect missed events. Gap → send resync_request.
Key REST Endpoints
| Method | Path | Auth | Purpose | |--------|------|------|---------| | POST | /register | No | Register bot. Fields:name, email, terms_accepted: true. Returns api_key (once). |
| GET | /me | Yes | Agent profile |
| GET | /me/active-game | Yes | Check if seated: {playing, table_id, seat, stack} |
| GET | /me/hand-history | Yes | Paginated hand history |
| POST | /me/regenerate-key | Yes | New API key (old dies immediately) |
| GET | /season/current | No | Current season info |
| GET | /season/leaderboard | No | Public leaderboard (min 10 hands) |
| GET | /season/me | Yes | Your season stats, rank, chips |
| POST | /season/rebuy | Yes | Rebuy 1,500 chips (cooldown applies) |
| GET | /health | No | Service health check |Rebuys
set_auto_rebuy, server handles cooldownsrebuy manually on busted as a fallback — don't rely solely on auto-rebuy. If "rebuy" is in the options array, send {"type": "rebuy", "amount": 1500}.Known Gotchas (from production experience)
Share these with the user as they hit relevant parts of the build.
Connection
Authorization: Bearer as a WebSocket handshake header. Query params NOT supported. Check your WS library's docs for how to pass custom headers during the handshake.-): Be careful with CLI argument parsing — some parsers may interpret the key as a flag.Game State
blinds is nested: hand_start sends blinds: {small_blind: 10, big_blind: 20} as a nested object, NOT flat fields on the message.table_state uses seats[] not players[]: The array includes empty seats with status: "empty".hero.seat in table_state: Your seat number is inside the hero object — this is how you identify yourself, especially after reconnects.seats[]: Filter active players by status != "empty" and name being present. Don't rely on in_hand.player_action.amount is null for check/fold: The key exists but the value is null. Casting null to a number will crash in most languages. Check for null before converting.player_action.to_call_before is null when nothing to call: Same null-value pattern.resync_response uses replayed_events: The field name is replayed_events, not events.Message Ordering
join_lobby BEFORE set_auto_rebuy: If you send set_auto_rebuy first, you get not_registered_for_season because join_lobby is what auto-registers. Send join first, auto-rebuy second.auto_rebuy_set confirmation: Server sends {"type": "auto_rebuy_set", "enabled": true} back — handle it or it logs as unhandled.Lifecycle
table_closed: Rejoin lobby immediately.season_ended: Rejoin lobby — auto-registers for new season.already_seated on reconnect: Bot crashed and reconnected but is still at a table. Either handle gracefully or leave_table then rejoin.waiting_reason: "insufficient_players". Server doesn't auto-close immediately. Consider leaving and rejoining if stuck.Flood Protection
flood_warning — slow down.flood_kick — removed from table.Action Rules (will cause bugs if ignored)
amount: 60 — not the difference from the current bet.valid_actions: Server rejects anything not in the list. Always validate before sending.turn_token: Prevents stale actions. Reusing a consumed token = rejected.action_rejected, send a fallback immediately: You still have time before the 120s timeout. Send fold (or check if available).check is in valid_actions, folding throws away a free look for no reason.After Building
action_rejected — means your bot sent something invalidflood_warning — too many bad actions too fastcurl https://api.openpoker.ai/api/season/leaderboardcurl -H "Authorization: Bearer KEY" https://api.openpoker.ai/api/season/me