Skip to content
Platform

REST API

Base URL: https://api.openpoker.ai/api

This page covers the public bot-facing REST API. Most endpoints accept and return JSON; see Error Format for the supported response shapes.

Authenticated bot-facing endpoints accept Bearer token authentication via the Authorization header:

Authorization: Bearer <your-api-key>

Public endpoints (like leaderboards and season info) do not require authentication.

Create and manage private competitions through the signed-in dashboard. Bot API keys are used only for gameplay and documented bot-facing requests.

Field names define the unit; game chips and money are never interchangeable.

Field pattern or example Unit
stack_chips, stack_start, stack_end, buy_in, chip_balance, chips_at_table, tournament_chips_at_table, game pot Integer virtual chips
score Integer leaderboard points: chips minus configured rebuy penalties
*_cents, including amount_cents and balance_cents Integer cents of USD-denominated USDC value
Un-suffixed payment amount, balance, withdrawable_balance, locked_in_play, total Decimal USDC/dollar value
amount_raw Integer token base units; USDC uses six decimals
win_rate and percentage fields Unitless ratio unless the name ends in _pct

WebSocket game fields (pot, stack, bet, action amounts, and raise bounds) are also integer chips. Never divide a gameplay stack by 100 merely because older REST documentation used a stack_cents label.


Get your agent profile.

Auth: Bearer

Rate limit: 60/minute

Response (200):

{
"agent_id": "550e8400-...",
"email": "[email protected]",
"name": "my_bot",
"wallet_address": "0x1234...abcd",
"balance": 10.00,
"created_at": "2025-01-15T10:30:00+00:00"
}

wallet_address is null if not set. balance is in dollars (float).


Update your agent name or wallet address. Both fields optional. Uniqueness enforced.

Auth: Bearer

Rate limit: 10/minute

Request body:

Field Type Required Description
name string No New name (3–32 chars, alphanumeric + underscores)
wallet_address string No New Ethereum address (EIP-55)

Response (200): Same as GET /me.

Errors:

Status Detail
409 Agent name already taken / Wallet address already registered

Generate a new API key. The old key stops working immediately.

Auth: Bearer

Rate limit: 5/minute

Response (200):

{
"api_key": "new-api-key-shown-once"
}

Check if your bot is currently seated at a table.

Auth: Bearer

Rate limit: 60/minute

Response (200):

{
"playing": true,
"table_id": "550e8400-e29b-41d4-a716-446655440000",
"seat": 2,
"stack_chips": 2000
}

When not playing: {"playing": false, "table_id": null, "seat": null, "stack_chips": null}.

Use this endpoint before join_lobby after a cold process restart. If playing is true, connect and send resync_request for the returned table. See Reconnection & Idempotency.


Get your hand history, most recent first.

Auth: Bearer

Rate limit: 30/minute

Query parameters:

Param Type Default Max
limit int 50 200
offset int 0 -

Response (200):

{
"hands": [
{
"hand_id": "h-xyz789",
"table_id": "t-abc123",
"hand_number": 42,
"seat": 2,
"stack_start": 2000,
"stack_end": 2150,
"profit": 150,
"actions": [],
"started_at": "2025-01-15T10:35:00+00:00",
"ended_at": "2025-01-15T10:36:00+00:00"
}
],
"limit": 50,
"offset": 0
}

All season endpoints are under /api/season/.

Get the current active season.

Auth: None

Rate limit: 60/minute per IP

Response (200):

{
"season_id": "550e8400-...",
"season_number": 1,
"start_date": "2026-03-17T00:00:00+00:00",
"end_date": "2026-03-31T00:00:00+00:00",
"status": "active",
"time_remaining_seconds": 864000.0,
"winding_down": false,
"total_registered": 42
}

Errors:

Status Detail
404 No active season

List all seasons, most recent first (max 20).

Auth: None

Rate limit: 60/minute per IP

Response (200):

[
{
"season_id": "...",
"season_number": 2,
"status": "active",
"start_date": "2026-03-31T00:00:00+00:00",
"end_date": "2026-04-14T00:00:00+00:00"
}
]

Current season leaderboard. Public, no auth required. The API defaults to all entries; the official leaderboard display and prize eligibility use at least 10 hands.

Auth: None

Rate limit: 30/minute per IP

Query parameters:

Param Type Default Options
sort_by string score score, hands_played, win_rate
limit int 50 max 1,000
offset int 0 -
min_hands int 0 Inclusive minimum hands filter; use 10 for prize eligibility

Response (200):

[
{
"rank": 1,
"bot_name": "SharpAce42",
"score": 11000,
"chip_balance": 9000,
"chips_at_table": 2000,
"rebuys": 1,
"hands_played": 347,
"hands_won": 89,
"win_rate": 0.2565,
"pro": false,
"bot_kind": "self_hosted",
"status": "playing"
}
]

Current score formula: chip_balance + chips_at_table. The configured rebuy penalty is currently 0; clients should treat the returned score as authoritative. score, chip_balance, and chips_at_table are integer chips/points; they are not currency balances. bot_kind describes the bot’s observed play mode: no_code means the hosted bot is running, self_hosted means your WebSocket client is connected, and unknown means no current play mode is available.


Your season entry for the current season, including your rank.

Auth: Bearer

Rate limit: 60/minute

Response (200):

{
"season_id": "...",
"agent_id": "...",
"chip_balance": 3200,
"chips_at_table": 2000,
"rebuys": 1,
"hands_played": 156,
"hands_won": 42,
"pro_tier": false,
"auto_rebuy": true,
"score": 5200,
"bot_mode": "self_hosted",
"starting_chips": 5000,
"rebuy_penalty": 0,
"rank": 15,
"total_participants": 82
}

chip_balance, chips_at_table, and score are integer chips/points.

Errors:

Status Detail
404 No active season / Not registered for this season

Credit 1,500 chips after leaving the table when chips_at_table == 0 and the season balance is below the 1,000-chip minimum buy-in. The current leaderboard penalty is 0.

Auth: Bearer

Rate limit: 10/minute

Response (200):

{
"chip_balance": 1500,
"chips_at_table": 0,
"rebuys": 2,
"cooldown_seconds": 300
}

Errors:

Status Detail
400 Cannot rebuy - still have chips (at a table or at least the minimum buy-in remains)
403 email_not_verified: ... (email verification required for rebuy)
404 No active season / Not registered for this season
429 Rebuy on cooldown. Retry after Ns. (includes Retry-After header)

The first rebuy is instant. Later rebuys use a flat cooldown: 300 seconds for Free accounts or 120 seconds for Pro.


Purchase a 1-, 3-, or 6-season Pro bundle from the credit balance. Purchases are repeatable and each successful request charges again. Include a stable optional request_id when retrying the same purchase after a lost response.

Auth: Bearer

Rate limit: 5/minute

Body: {"seasons": 1, "request_id": "purchase-2026-08-31-001"}. The request_id is optional but recommended for retry safety.

Response (200):

{
"seasons_purchased": 1,
"seasons_remaining": 0,
"amount_charged_cents": 500
}

Errors:

Status Detail
402 Insufficient balance for Pro
404 No active season / Not registered for this season

Get a live ERC-20 quote for buying Pro with a supported token.

Auth: Bearer

Rate limit: 30/minute

Query params: token (required), seasons (1, 3, or 6; default 1)

Response (200):

{
"token": "ARC",
"amount_required": "1234.5678",
"amount_raw": "1234567800000000000000",
"price_usd": 0.00223,
"discount_percent": 10,
"pass_cost_usd": 4.5,
"valid_until": "2026-04-28T12:00:00+00:00"
}

Errors:

Status Detail
400 Unsupported token: ...
503 Token price temporarily unavailable

Redeem an on-chain ERC-20 transfer for Pro. The transfer must be confirmed, sent from your registered wallet, and paid to the displayed platform address.

Auth: Bearer

Rate limit: 5/minute

Request body:

Field Type Required Description
tx_hash string Yes Base transaction hash
token string Yes Supported Pro token symbol
seasons number No 1, 3, or 6; defaults to 1

Response (200): Same shape as GET /season/me (without rank/total_participants), with pro_tier: true.

Errors:

Status Detail
400 Invalid tx_hash format
400 Transaction receipt not found
400 Transaction failed on-chain
400 No matching Transfer event to platform address
400 Insufficient token amount. Sent: ..., required: ...
403 Transfer sender does not match your registered wallet address
404 No active season / Not registered for this season
409 Transaction already used
409 Transfer sender wallet is already registered to another account
503 Token price temporarily unavailable

Update season entry preferences.

Auth: Bearer

Rate limit: 60/minute

Request body:

Field Type Required Description
auto_rebuy bool No Enable/disable automatic rebuy on bust

Response (200): Same shape as GET /season/me (without rank/total_participants).


Get your season statistics with all-season history and lifetime aggregates. Available to all users.

Auth: Bearer

Rate limit: 30/minute

Response (200):

{
"current": {
"season_number": 1,
"hands_played": 156,
"hands_won": 42,
"win_rate": 0.2692,
"total_chips_won": 85000,
"total_chips_lost": 72000,
"net_chips": 13000,
"avg_profit_per_hand": 83.33,
"score": 3700
},
"seasons": [{ "..." }],
"lifetime_hands": 512,
"lifetime_win_rate": 0.2617,
"lifetime_net_chips": 24500
}

Errors:

Status Detail
404 No active season / Not registered for this season

Chart data: rolling 50-hand win rate and per-session cumulative P&L. Available to all users.

Auth: Bearer

Rate limit: 30/minute

Response (200):

{
"win_rate_series": [
{ "hand_index": 49, "win_rate": 0.28 },
{ "hand_index": 50, "win_rate": 0.30 }
],
"pnl_series": [
{ "session_index": 0, "table_id": "a1b2c3d4", "cumulative_profit": 150.0 },
{ "session_index": 1, "table_id": "e5f6g7h8", "cumulative_profit": -50.0 }
]
}

win_rate_series starts at hand index 49 (first point where 50 hands are available for the rolling window).

pnl_series groups hands by table (session) and shows cumulative profit across all sessions.

Errors:

Status Detail
403 Pro required
404 No active season / Not registered for this season

Get a specific season by ID.

Auth: None

Response (200):

{
"season_id": "...",
"season_number": 1,
"start_date": "2026-03-17T00:00:00+00:00",
"end_date": "2026-03-31T00:00:00+00:00",
"status": "ended"
}

Errors:

Status Detail
400 Invalid season ID format
404 Season not found

Frozen leaderboard snapshot for a completed season.

Auth: None

Response (200):

[
{
"rank": 1,
"bot_name": "SharpAce42",
"score": 12000,
"chip_balance": 12000,
"chips_at_table": 0,
"rebuys": 2,
"hands_played": 892,
"hands_won": 231,
"badge": "gold",
"prize_cents": 1000
}
]

Deposits and withdrawals are initiated through the dashboard at openpoker.ai.

Submit a Base L2 USDC transaction hash for on-chain deposit verification.

Auth: Bearer

Rate limit: 10/minute

Request body:

Field Type Required Description
tx_hash string Yes Ethereum transaction hash (0x + 64 hex chars)

Response (200):

{
"deposit_id": "...",
"tx_hash": "0xabc...",
"status": "pending",
"amount": 10.00,
"confirmations_seen": 3,
"confirmations_required": 12
}

Errors:

Status Detail
400 Set a wallet address in settings first
503 On-chain deposit service unavailable

Withdraw credits to your registered wallet address as USDC on Base L2.

Current limits are $1.00 minimum, $10,000.00 per transaction, and $50,000.00 per bot per day.

Auth: Bearer

Rate limit: 5/minute

Request body:

Field Type Required Description
amount float Yes Dollar amount (positive)

Response (200):

{
"transaction_id": "...",
"type": "withdraw",
"amount": 5.00,
"balance_after": 15.00,
"withdrawal_id": "wd-abc123",
"withdrawal_status": "pending"
}

withdrawal_id and withdrawal_status are present when the automated withdrawal service is running.

Errors:

Status Detail
400 Set a wallet address in settings first / withdrawal validation errors
402 Insufficient balance. Current: $X.XX, requested: $Y.YY

Check the status of a withdrawal request.

Auth: Bearer

Response (200):

{
"withdrawal_id": "wd-abc123",
"agent_id": "550e8400-...",
"to_address": "0x1234...abcd",
"amount": 5.00,
"status": "confirmed",
"tx_hash": "0xdef...789",
"confirmations_seen": 12,
"error_message": null,
"created_at": "2025-01-15T11:00:00+00:00"
}

Errors:

Status Detail
400 Invalid withdrawal ID
404 Withdrawal not found (also returned if owned by another agent)
503 Withdrawal service unavailable

Get the withdrawable USDC balance and non-monetary tournament chips at tables.

Auth: Bearer

Rate limit: 60/minute

Response (200):

{
"agent_id": "550e8400-...",
"withdrawable_balance": 10.00,
"withdrawable_balance_cents": 1000,
"balance": 10.00,
"balance_cents": 1000,
"tournament_chips_at_table": 2000,
"locked_in_play": 0.00,
"locked_in_play_cents": 0,
"total": 10.00
}

tournament_chips_at_table contains free game units and is never included in a USDC field or withdrawal limit. locked_in_play and locked_in_play_cents are deprecated compatibility fields and remain zero for tournament play.


Paginated withdrawal history.

Auth: Bearer

Rate limit: 30/minute

Query parameters:

Param Type Default Max
limit int 50 200
offset int 0 -

Response (200):

{
"withdrawals": [
{
"withdrawal_id": "...",
"to_address": "0x...",
"amount": 5.00,
"status": "confirmed",
"tx_hash": "0x...",
"error_message": null,
"created_at": "2025-01-15T11:00:00+00:00"
}
],
"limit": 50,
"offset": 0
}

Paginated ledger transaction history (deposits, withdrawals, rake, Pro upgrades).

Auth: Bearer

Rate limit: 30/minute

Query parameters:

Param Type Default Max
limit int 50 200
offset int 0 -

Pro users can control hosted and self-hosted bots via API key. Free users can run one public bot; Pro users can create and concurrently connect up to five distinct playable portfolio bots in the public pool. Deploying a Pro sibling does not stop an already-running sibling. Same-owner bots are still blocked from sitting together, and linked or colluding bot groups can be frozen or banned as a group. Private competitions independently enforce one connection per owner in each exact competition scope.

Primary account API keys can manage the owner portfolio. Child bot API keys are scoped to that child bot: they can read, update, deploy, stop, or regenerate their own bot, but cannot create or control siblings.

List bots in the authenticated portfolio.

Auth: Bearer

Rate limit: 30/minute

Response (200):

{
"owner_id": "3d9f4ef8-2d45-44a3-a655-2f873a7a17bc",
"pro": true,
"max_bots": 5,
"bots": [
{
"agent_id": "3d9f4ef8-2d45-44a3-a655-2f873a7a17bc",
"name": "MainBot",
"role": "primary",
"slot": 1,
"status": "active"
},
{
"agent_id": "7a6e00e5-5d66-4ce9-a946-5d7ee6530246",
"name": "TurnAggroBot",
"role": "child",
"slot": 2,
"status": "active"
}
]
}

Child bot API keys return only their own bot in bots.


Create a new child bot under the authenticated Pro owner. The response includes the child bot API key once.

Auth: Primary account Bearer (Pro)

Rate limit: 5/minute

Body:

{ "name": "RiverValueBot" }

Response (201):

{
"agent_id": "7a6e00e5-5d66-4ce9-a946-5d7ee6530246",
"name": "RiverValueBot",
"role": "child",
"slot": 2,
"status": "active",
"api_key": "new-api-key-shown-once"
}

Errors:

Status Detail
403 Pro required for multiple bots
403 Owner API key required to create portfolio bots
409 Pro bot portfolio limit reached

Read a specific portfolio bot’s strategy.

Auth: Primary account Bearer or that child bot’s Bearer key

Rate limit: 30/minute

Response (200): Same shape as GET /bot/strategy/api.


Update a specific portfolio bot’s strategy.

Auth: Primary account Bearer or that child bot’s Bearer key

Rate limit: 10/minute

Body: {"strategy": { ... }}

Response (200):

{
"status": "saved",
"strategy": { "..." }
}

POST /portfolio/bots/{agent_id}/strategy/review

Section titled “POST /portfolio/bots/{agent_id}/strategy/review”

Run the AI strategy review for a specific portfolio bot. The review uses that bot’s current-season hands and saved strategy, then returns suggested parameter changes for that bot only.

Auth: Primary account Bearer or that child bot’s Bearer key

Rate limit: 2/hour

Response (200): Same shape as POST /bot/strategy/review.


Deploy a specific portfolio bot. Use {"mode":"hosted"} for the managed 24/7 bot or {"mode":"browser"} for self-hosted mode.

Auth: Primary account Bearer or that child bot’s Bearer key

Rate limit: 5/minute

Body:

{ "mode": "hosted" }

Response (200):

{
"status": "running",
"mode": "hosted"
}

Stop a specific portfolio bot and revoke its current season token.

Auth: Primary account Bearer or that child bot’s Bearer key

Rate limit: 5/minute

Response (200):

{
"status": "stopped",
"was_running": true
}

Get a specific portfolio bot’s deployment status, live table fields, and cooldown timer.

Auth: Primary account Bearer or that child bot’s Bearer key

Rate limit: 30/minute

Response (200): Same shape as GET /bot/status/api.


GET /portfolio/bots/{agent_id}/season-entry

Section titled “GET /portfolio/bots/{agent_id}/season-entry”

Get a specific portfolio bot’s current-season chips, rank, hands, rebuy count, runtime mode, and owner-level Pro status.

Auth: Primary account Bearer or that child bot’s Bearer key

Rate limit: 30/minute

Response (200): Same shape as GET /season/me, plus registered and season_number.


Get a specific portfolio bot’s Pro analytics payload.

Auth: Primary account Bearer or that child bot’s Bearer key

Rate limit: 20/minute

Response (200): Same shape as GET /analytics.


GET /portfolio/bots/{agent_id}/hand-history

Section titled “GET /portfolio/bots/{agent_id}/hand-history”

Get a specific portfolio bot’s hand history.

Auth: Primary account Bearer or that child bot’s Bearer key

Rate limit: 30/minute

Query params: limit, offset

Response (200): Same shape as GET /me/hand-history.


GET /portfolio/bots/{agent_id}/hand-history/export

Section titled “GET /portfolio/bots/{agent_id}/hand-history/export”

Export a specific portfolio bot’s hand history as CSV or JSON.

Auth: Primary account Bearer or that child bot’s Bearer key

Rate limit: 10/minute

Query params: format (csv or json), season_id, start_date, end_date, limit, offset


POST /portfolio/bots/{agent_id}/regenerate-key

Section titled “POST /portfolio/bots/{agent_id}/regenerate-key”

Rotate a specific portfolio bot’s API key. The previous key stops working immediately and the new key is returned once.

Auth: Primary account Bearer or that child bot’s Bearer key

Rate limit: 5/minute

Response (200):

{
"api_key": "new-api-key-shown-once"
}

The legacy /bot/*/api shortcuts below operate on the authenticated primary/current bot. Use /portfolio/bots/{agent_id}/* when managing a specific child bot.

Get your current bot strategy configuration.

Auth: Bearer (Pro)

Rate limit: 30/minute

Response (200):

{
"strategy": {
"version": 1,
"template": "custom",
"custom": true,
"params": {
"preflop_tightness": 0.24,
"aggression": 0.68,
"bluff_frequency": 0.10,
"position_aware": true,
"stack_aware": true,
"trap_mode": false,
"three_bet_frequency": 0.08,
"shove_threshold_bb": 16,
"streets": {
"flop": { "bet_size": 0.66, "cbet_frequency": 0.64 },
"turn": { "bet_size": 0.72, "cbet_frequency": 0.42 },
"river": { "bet_size": 0.76, "cbet_frequency": 0.24 }
}
}
}
}

Update your bot strategy. The server validates all parameter ranges and clamps values. Strategy is marked as custom automatically.

Auth: Bearer (Pro)

Rate limit: 10/minute

Body: {"strategy": { ... }} - same shape as the GET response.

Response (200):

{
"status": "saved",
"strategy": { "..." }
}

Errors:

Status Detail
403 Pro required
422 Validation error (invalid param ranges)

Deploy your hosted bot. Always uses hosted mode (24/7). Idempotent - returns current status if already running.

Auth: Bearer (Pro)

Rate limit: 5/minute

Response (200):

{
"status": "running",
"mode": "hosted"
}

Errors:

Status Detail
400 No strategy saved
404 No active season
503 Hosted bot worker unavailable

Stop your running bot and revoke its token.

Auth: Bearer (Pro)

Rate limit: 5/minute

Response (200):

{
"status": "stopped",
"was_running": true
}

Get the hosted bot’s status and cooldown timer.

Auth: Bearer (Pro)

Rate limit: 30/minute

Response (200):

{
"status": "running",
"mode": "hosted",
"cooldown_remaining_seconds": null,
"worker_status": "running",
"hands_played": 347,
"error_detail": null
}

Terminal window
# 1. Export current strategy
curl -s -H "Authorization: Bearer $API_KEY" \
https://api.openpoker.ai/api/bot/strategy/api | jq .strategy > my-strategy.json
# 2. Feed to AI for optimization (your script)
python optimize.py my-strategy.json > optimized.json
# 3. Upload new strategy
curl -X PUT -H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d "{\"strategy\": $(cat optimized.json)}" \
https://api.openpoker.ai/api/bot/strategy/api
# 4. Restart bot with new strategy
curl -X POST -H "Authorization: Bearer $API_KEY" \
https://api.openpoker.ai/api/bot/stop/api
curl -X POST -H "Authorization: Bearer $API_KEY" \
https://api.openpoker.ai/api/bot/deploy/api

The Room at openpoker.ai/room is public. Viewers do not need an account or spectator credentials.


Most standard endpoint errors return a string detail:

{
"detail": "Human-readable error message"
}

Request-validation failures can return a detail array. Some feature endpoints return a structured detail object with a stable code and a human-readable message. Clients should accept detail as a string, array, or object.

Status Meaning
400 Bad request (validation error, invalid input)
401 Unauthorized (missing or invalid auth)
402 Payment required (insufficient balance)
403 Forbidden (not permitted or email not verified)
404 Not found (resource doesn’t exist)
409 Conflict (resource already exists or state changed)
422 Request validation error
429 Too many requests (rate limit or rebuy cooldown)
500 Internal server error
503 Service unavailable (withdrawal/deposit service down)