pokerai.bet API documentation
Two poker APIs, one key & quota: the GTO strategy API (/v1/gto/* — every street preflop/flop/turn/river, returns each action's mixed frequency) and the Poker engine / semantics API (/v1/pokerkit/* — board/hand semantics, eval, draws, range equity, game simulation). Both live in the interactive reference.
GTO strategy API · two ways to get a strategy: (1) Presolved solutions — preflop / flop tap a library of presolved GTO solutions, returned in milliseconds; (2) Real-time solver computation — flop / turn / river are computed live by the solver (starting from the flop takes a while). There is also range conversion to update ranges along an action line.
Scope: currently only 6-handed tables (6-max) and a 100BB starting effective stack are supported. More table sizes and stack depths are coming soon.
Stateless / self-contained requests: each request is independent, the server keeps no hand state, and you are not required to call earlier streets first — you can query any spot directly, just supply all the information that spot needs.
Two APIs, one key: this page is the GTO strategy API (/v1/gto/*). There is also a Poker engine & semantics API (/v1/pokerkit/*: board texture, made-hand/draws/outs/blockers, range equity, hand eval, ICM, game simulation…), reusing the same API key and quota (cheap endpoints charge the general bucket, Monte-Carlo the solve bucket). Both live in the interactive reference (switch in the top bar); its own docs/spec are at /v1/pokerkit/docs · openapi.json (auto-synced with the code).
Board / hand format (unified): across all endpoints the board and hole_cards inputs use an unseparated string (each card is 2 chars: rank AKQJT98765432 + suit c d h s), e.g. "board": "2c2h2s", "hole_cards": "AdKd". In responses board is always returned as an array, e.g. ["2c","2h","2s"].
Range notation: range strings such as oop_range / ip_range are comma-separated hand:weight. Hands use class notation — pairs AA, suited AKs, offsuit AKo (higher rank first); weight is 0–1, omitted defaults to 1. Example: "AA:1,KK,AKs:0.5,72o:0.1". Note: the keys of range_strategy and range_*_new_raw use specific combo notation (e.g. "AhKh").
Machine-readable spec: OpenAPI 3.0 spec — importable into Postman / Insomnia, or use openapi-generator to generate clients in any language. Interactive reference: online API reference (try it live) — enter your Key and fire requests endpoint by endpoint (this consumes real quota).
Auth
Every request must carry an API Key. Get a Key in 60 seconds:
- Open the console, enter your email → receive a verification code by email (passwordless login, no signup needed).
- Enter the code to log in → create an API Key → copy it (shown only once, store it safely).
- Set it as an environment variable:
export POKERAI_API_KEY=gto_xxxxxxxx, and you can run the Quickstart below.
From then on, send the same Key (the gto_xxx you just copied — this is the “Bearer token”) in a request header on every request. Pick either form below — it is one key, not two:
Authorization: Bearer gto_xxxxxxxx # standard (recommended; matches OpenAPI scheme BearerApiKey)
# or (fully equivalent, pick one)
X-API-Key: gto_xxxxxxxx # equivalent (OpenAPI scheme XApiKey); handy for some gateways / SDKs / quick tests
Quota
Two kinds of monthly quota, metered separately and reset on the 1st of each month; current usage is in the console:
- General quota (default 100/month): preflop, preflop range, flop decision-tree fetch, range conversion
/v1/gto/range, and flop projected-range each charge 1. - solve quota (default 100/month): the real-time solver
/v1/gto/solvercharges 1 each time it triggers a new solve; reusing a cached solve and fetching the tree/node are free (the response has asolve_quotafield when charged, and lacks it when not charged).
Error model
All errors return a uniform JSON: { "error": "<code>", "message": "<description>" }. A few variants: some 502s use reason instead of message; the 429 for all solvers being busy looks like { "status": "busy", "message": ... }. The endpoint-specific error values are in each endpoint's "Possible errors" table; the common status codes are below:
| HTTP | error / meaning | retry? |
|---|---|---|
| 400 | Invalid input or missing field (see each endpoint's table for the specific error). | No, fix the input |
| 401 | missing_api_key / invalid_api_key: missing or invalid Key. | No, check the Key |
| 403 | invalid_node_token / invalid_solve: token/handle invalid or not yours. | No, re-fetch the tree / reschedule first |
| 404 | no_solution: no GTO data for this spot yet. | No, change the spot |
| 429 | quota_exceeded (general) / solve_quota_exceeded (solve): monthly quota used up. | No, resets on the 1st or upgrade your quota |
| 429 | status: busy: all solvers busy (only /v1/gto/solver), not charged. | Yes, back off and retry |
| 502 | no_result (reason: timeout / no_worker_available) / auth_unavailable / solver_unreachable: backend transiently unavailable. | Yes, back off and retry 2–3 times |
Error response body examples
400 (invalid input):
{
"error": "invalid_board",
"message": "board must be 3 cards, e.g. \"2c2h2s\""
}
401 (missing Key) / 404 (no data) / 429 (quota used up) — single field or short message:
{ "error": "missing_api_key" }
{ "error": "no_solution", "message": "no GTO data for this spot/board" }
{ "error": "quota_exceeded" }
502 (backend transiently unavailable, retryable):
{
"error": "no_result",
"reason": "timeout"
}
Retry: only 429 busy and 502 are worth retrying — use exponential backoff (start ~1s, double, at most 2–3 times). The rest (400/401/403/404/quota exhausted) are terminal and retrying is useless: fix the input / change the Key / re-fetch the tree, or wait for the quota to reset on the 1st of the month. There is no per-second rate limit, so there is no Retry-After header.
Quickstart
Once you have a Key (see above), copy this curl (replace $POKERAI_API_KEY with yours) — the simplest successful call: Hero has an opening opportunity in UTG (RFI), and the preflop presolved solution returns in milliseconds:
curl -s https://pokerai.bet/v1/gto/preflop \
-H "Authorization: Bearer $POKERAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"hole_cards":"AhKh","positions":{"hero":"UTG"},"preflop_actions":[{"position":"SB","action":"small blind","amount":0.5},{"position":"BB","action":"big blind","amount":1}]}'
Real response (UTG with AKs, opening opportunity → opens 100% to 3BB):
{
"hole_cards": "AhKh",
"situation": "RFI",
"strategy": [
{ "action": "raise", "frequency": 1, "amount_bb": 3, "sizing_pot": 0.8 }
],
"quota": { "used": 6, "limit": 100 }
}
Facing a raise? Just add the opponent's action to preflop_actions (append {"position":"UTG","action":"raise","amount":3} and change Hero to MP → it becomes a 3bet spot, and situation returns Raise). Each action's mixed frequency is returned (no recommended action; choose yourself based on frequency). The sections below are organized in three parts: Presolved solutions (preflop / flop), real-time solver computation, and range conversion.
Client SDKs Python · TypeScript · MCP
Don't want to hand-write HTTP? The official clients are auto-generated from the OpenAPI spec and fully typed, so they always track the API. Auth is just your API key.
Python pip
pip install pokerai-bet # distribution name pokerai-bet, import as pokerai
The same Quickstart spot (Hero UTG open), typed:
from pokerai import AuthenticatedClient
from pokerai.api.lookup import preflop_strategy
from pokerai.models import (
PreflopRequest, PreflopRequestPositions, PreflopRequestPreflopActionsItem,
)
from pokerai.models.preflop_request_preflop_actions_item_action import (
PreflopRequestPreflopActionsItemAction as Act,
)
from pokerai.models.position import Position
client = AuthenticatedClient(base_url="https://pokerai.bet", token="gto_...")
resp = preflop_strategy.sync(client=client, body=PreflopRequest(
hole_cards="AhKh",
positions=PreflopRequestPositions(hero=Position.UTG),
preflop_actions=[
PreflopRequestPreflopActionsItem(position=Position.SB, action=Act.SMALL_BLIND, amount=0.5),
PreflopRequestPreflopActionsItem(position=Position.BB, action=Act.BIG_BLIND, amount=1.0),
],
))
print(resp.to_dict())
Real output (same spot as the Quickstart):
{"hole_cards": "AhKh", "situation": "RFI",
"strategy": [{"action": "raise", "frequency": 1, "sizing_pot": 0.8, "amount_bb": 3}],
"quota": {"used": 2702, "limit": 100000}}
TypeScript / JavaScript npm
npm install @pokerai/client
import { createPokeraiClient } from "@pokerai/client";
const client = createPokeraiClient({ apiKey: "gto_..." });
const { data, error } = await client.POST("/v1/gto/preflop", {
body: {
hole_cards: "AhKh",
positions: { hero: "UTG" },
preflop_actions: [
{ position: "SB", action: "small blind", amount: 0.5 },
{ position: "BB", action: "big blind", amount: 1 },
],
},
});
if (error) throw new Error(JSON.stringify(error));
console.log(data.situation, data.strategy);
// "RFI" [{ action: "raise", frequency: 1, amount_bb: 3, sizing_pot: 0.8 }]
Paths, request bodies, and response fields are all type-checked — your editor autocompletes the whole API. Types are generated from the spec by openapi-typescript; the runtime is openapi-fetch.
MCP (for LLM agents) npm
Let Claude / Cursor and friends call the Pokerai API as tools (@pokerai/mcp):
// mcp.json
{ "mcpServers": { "pokerai": {
"command": "npx", "args": ["-y", "@pokerai/mcp"],
"env": { "POKERAI_API_KEY": "gto_..." }
}}}
5 presolved-lookup tools by default; add "POKERAI_ENABLE_SOLVE": "1" to enable the real-time solver tools (consumes solve quota).
Concepts
The overall mental model — read this once and the per-endpoint sections below will go more smoothly.
Two ways to get a strategy
- Presolved solutions: preflop / flop tap presolved GTO solutions, returned in milliseconds, suited to use cases that need a fast response.
- Real-time solver computation: flop / turn / river are computed live by the solver (flop takes about 70 seconds; turn/river are faster), suited to any custom range/spot.
- Range conversion: update a range along an action line into the range that enters the next street, then feed it to the solver.
No recommended action is returned
Every strategy returns each action's mixed frequency (0–1) and does not pick the action for you — you implement it yourself from frequency (take the max probability, or sample randomly by frequency).
Bet sizing: amount_bb and sizing_pot
Each bet/raise carries two sizing fields: amount_bb (the absolute amount, i.e. the BB you raise to) and sizing_pot (the value relative to the pot, the standard % of pot convention):
- bet (first bet) = bet ÷ pot.
- raise (facing a bet) = (raise-to − current bet) ÷ pot after the call.
Example: 3bet to 9 facing an open of 3 — pot is 4.5, after the call 4.5+3=7.5, the raise exceeds by 9−3=6, so sizing_pot = 6/7.5 = 0.8. When all-in it also carries allin: true.
tree → node (fetch tree → fetch node)
Both the flop decision tree and the real-time solver are two steps: first fetch the whole tree (each decision node carries a token), then use the Hero node's token to fetch that step's strategy.
fetch tree /flop/tree or /solver (+ poll /solver/tree)
└─→ nodes[]: each node carries is_hero + token
└─→ pick the node with is_hero:true
fetch node /flop/node or /solver/node (body carries that node's token)
└─→ the mixed strategy of this step
- flop:
/flop/tree(charges 1) →/flop/node(free). Therootnode = Hero's first decision. - solver:
/solverscheduling returns asolvehandle (charges 1) →/solver/treepolling +/solver/node(both free). The range is given only once at scheduling, not passed again.
Node path notation (two conventions, depending on the tree's source): the flop decision tree uses BET_8 (underscore, integer BB); the solver tree uses BET 8.000000 (space, 6 decimals). When passing node_id / navigating, it must match the labels in that tree (or in solver_results) character for character.
Node state machine (real-time solving)
After scheduling, poll the spot_status of /solver/tree: available (not scheduled) → computing (solving, keep polling) → queryable (can fetch nodes) → expired (cache reclaimed by TTL, must reschedule). You neither need to nor can manually release compute resources.
Presolved solutions presolved · milliseconds
preflop and flop tap presolved GTO solutions, served instantly, suited to use cases that need a fast response. The effective stack is fixed at 100BB (presolved, not an input). To compute the flop live with the real solver, see the next section.
Preflop
POST https://pokerai.bet/v1/gto/preflop charges 1 general
Full parameter / response schema, and try it live → interactive reference.
You don't need to judge "how many bets the pot is" — give Hero's preceding preflop actions in order, and the server derives the spot automatically (unopened / facing a raise / 3bet / 4bet…).
Notes · request body
| field | type | description |
|---|---|---|
hole_cards | string | Hero's 2 hole cards, e.g. "AdKd". |
positions.hero | string | Hero's position, one of SB BB UTG MP CO BTN (placed under positions). |
preflop_actions | array | The complete, explicit action sequence from the small blind up to the player just before Hero (Hero is not in the sequence; Hero's position is given by positions.hero, and the sequence stops at the player before Hero). Each item is { position, action, amount, allin? }, see the table below. |
preflop_version | string | Optional. Which 6max preflop chart set to use: 6max (default) / 6max_RC_100bb_200NL / 6max_RC_100bb_100NL / 6max_RC_40bb. Omit for the platform default (6max); an unknown value -> 400 unsupported_preflop_version. Different versions give different frequencies for the same spot. |
Fields of each preflop_actions item
| field | type | description |
|---|---|---|
position | string | The position of this action, one of SB BB UTG MP CO BTN. |
action | string | ∈ "small blind" / "big blind" / "raise" / "call" / "fold" (note the blinds are two-word strings). |
amount | number | The incremental amount newly invested by this action (BB, not the cumulative total). Examples: small blind 0.5; big blind 1; an opening raise to 3 → amount 3 (from 0); a player who already invested 1 reraising to 9 → amount 8. fold omits it (counts as 0). Pot = sum of all amount. |
allin | boolean | Optional. Marks a short-stack all-in (bet/call amount below the minimum raise); when true, the minimum-raise check is skipped. |
Validation (violation → 400 invalid_actions): the sequence must start with small blind (0.5), then big blind (1); each raise/call needs a positive amount; a raise's cumulative total must exceed the current bet and meet the minimum raise (= current bet + the size of the previous raise; so opening ≥ 2BB, and a 3bet over an open-to-3 must be ≥ 5BB) — unless allin:true; a call's cumulative total must exactly equal the current bet — unless allin:true.
Exact amounts only affect the pot / sizing_pot, not the frequencies: giving exact amount values only makes the pot / sizing_pot exact; GTO frequencies are determined by the spot (RFI / 3bet / 4bet + position) and do not change with bet size.
Call example
curl -s https://pokerai.bet/v1/gto/preflop \
-H "Authorization: Bearer $POKERAI_API_KEY" -H "Content-Type: application/json" \
-d '{"hole_cards":"AhKh","positions":{"hero":"MP"},"preflop_version":"6max_RC_100bb_200NL",
"preflop_actions":[{"position":"SB","action":"small blind","amount":0.5},
{"position":"BB","action":"big blind","amount":1},
{"position":"UTG","action":"raise","amount":3}]}'
Omit preflop_version for the default (6max). Available versions are listed below, or query them live with GET /v1/gto/preflop/versions.
Response (200)
{"hole_cards": "AhKh", "situation": "Raise", "strategy": [{"action": "raise", "frequency": 1, "amount_bb": 9, "sizing_pot": 0.8}], "quota": {"used": 7, "limit": 100}}
| field | description |
|---|---|
hole_cards | The echoed hand. |
situation | The state the table faces when it's Hero's turn: RFI (no one in the pot, Hero has an opening opportunity) / Limp (someone limped, no raise) / Raise (facing one opening raise) / 3-Bet / 4-Bet / 5-Bet. Note: BB always lands on Limp (someone must have entered the pot before it acts). |
strategy[] | The mixed strategy of each action. raise carries amount_bb (the absolute amount raised to, in BB) and sizing_pot (see bet sizing). Preflop amount_bb is set by the number of raises before Hero, see the table below. No recommended action is returned; choose yourself based on frequency. |
quota | This month's general quota usage (used / limit). |
Preflop amount_bb (derived from the presolved flop pot, raised to):
| number of raises before Hero | spot | amount_bb |
|---|---|---|
| 0 | open | 3 |
| 1 | 3bet | 9 |
| 2 | 4bet | 25 |
| ≥3 | 5bet+ | all-in 100 (allin: true) |
Strategy versions preflop_version
Same 6max, different chart sets (frequencies differ for the same spot); omit for the default 6max. The authoritative list is the discovery endpoint GET /v1/gto/preflop/versions (free; returns id + label + default):
id (pass as preflop_version) | description |
|---|---|
6max (default) | 6 max 100bb Deepsolver |
6max_RC_100bb_200NL | 6 max 100bb GG 200NL 3b/f 2.2x - 2.5x |
6max_RC_100bb_100NL | 6 max 100bb GG 100NL 3b/f |
6max_RC_40bb | 6 max 40bb GG 100NL |
curl -s https://pokerai.bet/v1/gto/preflop/versions -H "Authorization: Bearer $POKERAI_API_KEY"
// {"versions": [{"id": "6max", "label": "6 max 100bb Deepsolver", "default": true}, …], "default": "6max"}
Possible errors
| HTTP | error | trigger / how to fix |
|---|---|---|
| 400 | invalid_hole_cards | hole_cards is not 2 cards (e.g. "AdKd"). |
| 400 | unsupported_table_size / invalid_positions / invalid_actions | Table type (currently only 6max), hero/positions, or preflop_actions is invalid. |
| 400 | unsupported_preflop_version | preflop_version is not in the allowed set (6max / 6max_RC_100bb_200NL / 6max_RC_100bb_100NL / 6max_RC_40bb). |
| 404 | no_solution | No presolved solution for this preflop spot; change the spot. |
Download: request.jsonresponse.json
Whole range (13×13) charges 1 general
POST https://pokerai.bet/v1/gto/preflop/range · the full 13×13 range for a spot (position + action line), returning all 169 hand types' fold/call/raise in one call. No hole_cards (the spot comes from positions + preflop_actions). Charges 1 general quota (one call, not 169). For rendering a range grid.
// Request (no hole_cards)
{"table_size": "6max", "positions": {"hero": "BTN"}, "preflop_actions": [{"position": "SB", "action": "small blind", "amount": 0.5}, {"position": "BB", "action": "big blind", "amount": 1}, {"position": "UTG", "action": "fold"}, {"position": "MP", "action": "fold"}, {"position": "CO", "action": "fold"}]}
// Response
{ "range": {"22": {"fold": 0.69, "call": 0, "raise": 0.31}, "33": {"fold": 0, "call": 0, "raise": 1}, "ATs": {"fold": 0, "call": 0, "raise": 1}, "AKs": {"fold": 0, "call": 0, "raise": 1}, "AKo": {"fold": 0, "call": 0, "raise": 1}, "KQs": {"fold": 0, "call": 0, "raise": 1}, "72o": {"fold": 1, "call": 0, "raise": 0}, "JJ": {"fold": 0, "call": 0, "raise": 1} , …169 hands total… },
"quota": {"used": 7, "limit": 100} }
Hand notation: pair AA, suited AKs, offsuit AKo (high card first); each entry's fold+call+raise≈1. Full schema / try it live → interactive reference.
Flop decision tree
The flop strategy is a decision tree: first fetch the tree (getting all decision nodes + each node's token), your system walks the path on the tree by the actual bets, and at Hero's node fetches that step's strategy. Two steps, same as the solver (tree → node).
1) Fetch the decision tree charges 1 general
POST https://pokerai.bet/v1/gto/flop/tree · input board + pot_type + positions (no hole_cards needed, the tree is independent of the hand).
Full parameter / response schema, and try it live → interactive reference.
board | The 3 flop cards, e.g. "2c2h2s". |
pot_type | "SRP" single-raised / "3BET" / "4BET" / "LIMP" limped. |
positions | The position of each role (SB BB UTG MP CO BTN), required per pot_type as below. |
flop_version | Optional. Which flop dataset (one solved per preflop version): 6max (default) / 6max_RC_100bb_200NL / 6max_RC_100bb_100NL / 6max_RC_40bb. Omit for the default (6max); if that version has no data for the spot it degrades gracefully to 6max; an unknown value -> 400 unsupported_flop_version. Independent of preflop_version. The node tokens carry this version, so /v1/gto/flop/node stays on the same dataset. |
| pot_type | required positions | Hero value |
|---|---|---|
SRP | hero, raiser, caller | raiser or caller |
3BET / 4BET | hero, raiser, three_bettor | raiser or three_bettor |
LIMP | hero, limper | one of Hero or limper must be BB |
// Request (no hole_cards)
{"board": "2c2h2s", "pot_type": "SRP", "positions": {"hero": "UTG", "raiser": "UTG", "caller": "BTN"}}
// Response (36 nodes total; first 5 shown)
{"board": ["2c", "2h", "2s"], "pot_type": "SRP", "pot": 7.5, "effective_stack": 97, "oop_range": "AA:1,AKs:1,AQs:1,AJs:1,ATs:1,A9s:1,A8s:1,A7s:1,A6s:1,A5s:1,…", "ip_range": "AQs:0.05,AJs:0.86,ATs:0.436,A9s:0.356,A8s:0.36,A7s:0.196,…", "node_count": 36, "nodes": [{"node": "root", "is_hero": true, "token": "eyJ…"}, {"node": "root/BET_4", "is_hero": false, "token": "eyJ…"}, {"node": "root/BET_8", "is_hero": false, "token": "eyJ…"}, {"node": "root/BET_97", "is_hero": false, "token": "eyJ…"}, {"node": "root/CHECK", "is_hero": false, "token": "eyJ…"}, …], "quota": {"used": 7, "limit": 100}}
| field | description |
|---|---|
oop_range / ip_range | The starting range of this spot (weighted combo string), used as the starting weights for range conversion. |
nodes[] | All decision nodes: node (action path, e.g. "root/CHECK/BET_8"), is_hero (whether it's a Hero decision point), token (the credential to fetch that node's strategy, passed to step 2; bound to account + spot, cannot be forged). |
pot / effective_stack / node_count | Pot, effective stack (BB), and total number of decision nodes. |
quota | This month's general quota usage (used / limit). |
2) Fetch a node's strategy free
POST https://pokerai.bet/v1/gto/flop/node · carries node (the token of some node from step 1). With hole_cards → the mixed strategy for that hand; without hole_cards → the full range strategy.
Full parameter / response schema, and try it live → interactive reference.
Every Hero decision goes through here: Hero's first action = the root node (OOP acts first, check/bet); when Hero is IP, facing a bet, or acting a second time, pick the corresponding is_hero:true node (e.g. root/CHECK/BET_8 = I checked and now face a bet → fold/call/raise). The flop tree is single-street; for cross-street (turn/river) use /v1/gto/solver/*.
// Request (Hero node, with hole_cards) -> Hero strategy
{"node": "eyJ…", "hole_cards": "AdKd"}
{"hole_cards": "AdKd", "node": "root", "is_hero": true, "strategy": [{"action": "check", "frequency": 0.7208}, {"action": "bet", "amount_bb": 4, "sizing_pot": 0.5333, "frequency": 0.0533}, {"action": "bet", "amount_bb": 8, "sizing_pot": 1.0667, "frequency": 0.2259}, {"action": "bet", "amount_bb": 97, "sizing_pot": 12.9333, "allin": true, "frequency": 0}]}
// Request (no hole_cards) -> whole-range strategy
{"node": "eyJ…"}
{"node": "root/BET_4", "is_hero": false, "actions": [{"action": "call"}, {"action": "raise", "amount_bb": 12, "sizing_pot": 0.5161}, {"action": "raise", "amount_bb": 20, "sizing_pot": 1.0323}, {"action": "raise", "amount_bb": 97, "sizing_pot": 6, "allin": true}, {"action": "fold"}], "range_strategy": {"3d3c": [0.93067, 4e-05, 0.06922, 1e-05, 7e-05], "3h3c": [0.92172, 6e-05, 0.07812, 1e-05, 0.0001], "3h3d": [0.94334, 4e-05, 0.05655, 1e-05, 7e-05], "…": "188 hands total"}, "range_hand_count": 188}
| field | description |
|---|---|
is_hero | Whether this node is Hero's decision. |
strategy[] | Hero node (with hole_cards): the mixed strategy for this hand. action ∈ check / bet / call / raise / fold (bet = first bet, raise = a raise facing a bet); bet/raise carry amount_bb and sizing_pot (see bet sizing), and all-in also carries allin: true; frequency is a 0–1 probability. No recommended action is returned; choose yourself based on frequency. |
actions[] + range_strategy | Opponent node (or no hole_cards): the full range strategy, with each hand's frequencies aligned to actions; includes range_hand_count. Can be used to assemble solver_results for "range conversion". |
The BET_8 / RAISE_20 in a node id is the absolute bet amount (BB).
Possible errors
| HTTP | error | trigger / how to fix |
|---|---|---|
| 400 | 1) invalid_board / invalid_positions; 2) missing_node / invalid_hole_cards | 1) board is not 3 cards, or hero/positions invalid; 2) missing node or hole_cards is not 2 cards. |
| 403 | invalid_node_token | (step 2) node token invalid or not yours — call /v1/gto/flop/tree to fetch the tree first. |
| 404 | no_solution | No presolved solution for this spot/board. |
Download: tree_request.jsontree_response.jsonnode_request.jsonnode_response.json
Solver (real-time) solver
POST https://pokerai.bet/v1/gto/solver family. Pure postflop solver, computed in real time, using a separate solve quota. The essence is input-to-solve: board + oop/ip ranges + pot + behind stack + who is Hero, with no history needed, so you can enter from any spot. Three steps: schedule → poll for the tree → fetch a node's strategy.
Full parameter / response schema, and try it live → interactive reference (includes /solver/tree, /solver/node).
board length sets the street: 3=flop / 4=turn / 5=river. ⚠ Real-time solving starting from the flop takes a while (measured SRP flop ~70 seconds), and is not suited to use cases that need a fast response — if you want a fast flop, use "Presolved solutions" above (served instantly in milliseconds); turn / river are faster (seconds to tens of seconds).
Call example (three steps: 1) schedule → 2) poll for the tree → 3) fetch a node)
# 1) schedule (board 3/4/5 = flop/turn/river; flop ~70 seconds)
curl -s https://pokerai.bet/v1/gto/solver -H "Authorization: Bearer $POKERAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"board":"2c2h2s","oop_range":"AA,KK,QQ,AKs","ip_range":"JJ,TT,AQs,KQs","pot":7.5,"effective_stack":97,"hero":"OOP"}'
# 2) poll for the tree until spot_status=queryable; 3) fetch the node
curl -s https://pokerai.bet/v1/gto/solver/tree -H "Authorization: Bearer $POKERAI_API_KEY" \
-H "Content-Type: application/json" -d '{"solve":"eyJ..."}'
1) Schedule a solve charges 1 solve
board | 3=flop / 4=turn / 5=river, e.g. "2c2h2s9d" (turn). |
oop_range / ip_range | Required. The two players' ranges entering this street, weighted combo strings, e.g. "AsKs:1,QQ:0.75,...". |
pot / effective_stack | Required. The pot and behind effective stack (BB) entering this street; they determine the bet sizes and must be real. |
hero | Required: "OOP" or "IP". |
bet_sizes | Optional: override the bet sizes per street, e.g. {"flop":[33,75],"turn":[33,67,120],"river":[33,75,150]} (% of pot); if flop is omitted it defaults to 50%. |
// Request (flop)
{"board": "2c2h2s", "oop_range": "AA,KK,QQ,AKs", "ip_range": "JJ,TT,AQs,KQs", "pot": 7.5, "effective_stack": 97, "hero": "OOP"}
// Response (first trigger, charges 1 solve)
{"status": "computing", "solve": "eyJ0Ijoic2x2XzJmMjk2MWU3Y2Y0ODU3OGUiLCJ1IjoiYjk4Y2Y5NzAtZTVjMS00Njk5LTg4ODQtOGQzYzcwMGIyNGJlIiwidHMiOjE3ODIxMjkxMTUyMDJ9.rtmeNcZoBNWJRhkAGkTSSR58ZafpAh35mi510bgPU6c", "solve_quota": {"used": 1, "limit": 100}}
| field / case | description |
|---|---|
solve | The solve handle, used by the subsequent /tree and /node; the range is given only once in this step. |
status = computing | A new solve was triggered, charges 1 solve quota (see solve_quota). |
status = queryable | A cached solve already exists for this spot, not charged (no solve_quota field), poll /tree directly. |
429 status = busy | All solvers are busy, not charged, retry later. |
Cache hit (not charged again): re-scheduling the same spot (same board/range/pot/stack/hero/bet_sizes) no longer charges solve quota — if the response has no solve_quota field it means not charged; just use the returned solve handle to poll /tree. Examples: request / response.
2) Fetch the decision tree + node status free
POST https://pokerai.bet/v1/gto/solver/tree · carries the solve handle, poll until spot_status = queryable. To reach a later street, pass the dealt cards: turn_card (a flop solve → a specific turn) and/or river_card. A river spot from a flop solve needs BOTH turn_card + river_card (river alone isn't unique); a turn solve needs only river_card; omit both for the solve's own street.
// (2) fetch this street (flop) tree
{"solve": "eyJ…"}
{"street": "flop", "spot_status": "queryable", "pot": 7.5, "effective_stack": 97, "solve_seconds": 25.84, "node_count": 14, "nodes": [{"node": "root", "is_hero": true, "status": "queryable", "token": "eyJ…"}, {"node": "root/BET 4.000000", "is_hero": false, "status": "queryable", "token": "eyJ…"}, {"node": "root/BET 4.000000/RAISE 12.000000", "is_hero": true, "status": "queryable", "token": "eyJ…"}, …]}
// turn tree (flop solve + turn_card)
{"solve": "eyJ…", "turn_card": "9d"}
{"street": "turn", "spot_status": "queryable", "pot": 7.5, "effective_stack": 97, "solve_seconds": 25.84, "node_count": 104, "nodes": [{"node": "root/BET 4.000000/CALL/9d", "is_hero": false, "status": "queryable", "token": "eyJ…"}, {"node": "root/BET 4.000000/CALL/9d/BET 10.000000", "is_hero": true, "status": "queryable", "token": "eyJ…"}, {"node": "root/BET 4.000000/CALL/9d/BET 10.000000/RAISE 34.000000", "is_hero": false, "status": "queryable", "token": "eyJ…"}, …]}
// river tree (flop solve + turn_card + river_card)
{"solve": "eyJ…", "turn_card": "9d", "river_card": "Qh"}
{"street": "river", "spot_status": "queryable", "pot": 7.5, "effective_stack": 97, "solve_seconds": 25.84, "node_count": 308, "nodes": [{"node": "root/BET 4.000000/CALL/9d/BET 10.000000/CALL/Qh", "is_hero": true, "status": "queryable", "token": "eyJ…"}, {"node": "root/BET 4.000000/CALL/9d/BET 10.000000/CALL/Qh/BET 27.000000", "is_hero": false, "status": "queryable", "token": "eyJ…"}, {"node": "root/BET 4.000000/CALL/9d/BET 10.000000/CALL/Qh/BET 27.000000/RAISE 83.000000", "is_hero": true, "status": "queryable", "token": "eyJ…"}, …]}
| field | description |
|---|---|
spot_status | Solve status: available (not scheduled) / computing (solving, keep polling) / queryable (fetchable) / expired (cache reclaimed, reschedule at step 1) / no_nodes (solve converged but the queried runout/street has no decision nodes in the tree — terminal, stop polling). |
nodes[] | Each decision node: node (action path), is_hero, status, token (the credential to fetch that node's strategy, passed to step 3). The river runout is a chance node, navigated by river card. |
street / pot / effective_stack / node_count | Street, pot, effective stack, and total number of decision nodes. |
solve_seconds | Wall-clock time of this real-time solve, from schedule to convergence (seconds). Returned only when queryable; all runouts of the same solve share this value. |
3) Fetch a node's strategy free
POST https://pokerai.bet/v1/gto/solver/node · carries the node token. A Hero node returns the Hero strategy; an opponent node (or no hole_cards) returns the range strategy.
{"node": "eyJ…", "hole_cards": "AhKh"}
{"hole_cards": "AhKh", "node": "root", "is_hero": true, "strategy": [{"action": "check", "frequency": 0}, {"action": "bet", "amount_bb": 4, "sizing_pot": 0.5333, "frequency": 0.2666}, {"action": "bet", "amount_bb": 97, "sizing_pot": 12.9333, "allin": true, "frequency": 0.7334}]}
| field | description |
|---|---|
is_hero | Whether this node is Hero's decision. |
strategy[] | Hero node: Hero's mixed strategy for this hand (action / amount_bb / sizing_pot (see bet sizing) / frequency), with allin: true added when all-in. |
actions[] + range_strategy | Opponent node (or no hole_cards): in range_strategy each hand's frequencies align to the actions order; also includes range_hand_count. |
You neither need to nor can manually release compute resources; the system reclaims them automatically by TTL. When the cache has been reclaimed or replaced by a newer solve, this step returns { "node_status": "expired" }; just reschedule at step 1. A per-node solve error returns { "node_status": "error", "message": … } (terminal — stop polling).
Possible errors
| HTTP | error | trigger / how to fix |
|---|---|---|
| 400 | invalid_board / missing_range / invalid_pot / invalid_effective_stack / invalid_hero | (scheduling) the board, oop/ip range, pot, behind stack, Hero, or other solve input is invalid. |
| 400 | missing_solve / missing_node | (fetch tree/node) missing the solve handle or node token. |
| 403 | invalid_solve / invalid_node_token | Handle/token invalid or not yours — reschedule / re-fetch the tree. |
| 429 | status: busy | All solvers are busy, not charged, back off and retry. |
| 502 | solve_failed | Triggering the solve failed; retry later. |
| 503 | upstream_unavailable | (tree/node) the solver is unreachable after the service's own retries (transport error / 5xx) — retryable. |
Download (turn, board=4): schedule_reqschedule_restree_reqtree_resnode_reqnode_res
Download (flop, board=3): schedule_reqschedule_restree_reqtree_resnode_reqnode_res
Download (river, board=5): schedule_reqschedule_restree_reqtree_resnode_reqnode_res
End-to-end polling example (Python)
import requests, time
H = {"Authorization": "Bearer $POKERAI_API_KEY"}
BASE = "https://pokerai.bet/v1/gto"
# 1) schedule (charges 1 solve quota; free on cache hit)
solve = requests.post(f"{BASE}/solver", headers=H, json={
"board": "2c2h2s9d", "oop_range": "AA,KK,QQ,AKs", "ip_range": "JJ,TT,AQs,KQs",
"pot": 20, "effective_stack": 90, "hero": "OOP"}).json()["solve"]
# 2) poll for the tree until queryable
while True:
tree = requests.post(f"{BASE}/solver/tree", headers=H, json={"solve": solve}).json()
if tree["spot_status"] == "queryable": break
time.sleep(2) # computing -> back off and retry
# 3) fetch Hero's root node strategy
root = next(n for n in tree["nodes"] if n["node"] == "root")
strat = requests.post(f"{BASE}/solver/node", headers=H,
json={"node": root["token"], "hole_cards": "AhKh"}).json()
print(strat["strategy"])
Range conversion range
POST https://pokerai.bet/v1/gto/range charges 1 general
Full parameter / response schema, and try it live → interactive reference.
Update the OOP/IP ranges along an action line. Mainly used to get the range that enters the next street (flop→turn / turn→river), then feed it to /v1/gto/solver. All of the range-update computation (including normalize and bluff discount) is done by the solver, and is consistent with the solver's results.
This is a pass-through proxy: you assemble all of the input yourself, including solver_results (the decision tree). The decision tree can be one of your own solves, or assembled from this platform's flop decision tree: call /v1/gto/flop/tree for the starting range, then for each node call /v1/gto/flop/node without hole_cards to fetch the full range_strategy, nested as {node_type,player,strategy,childrens}. See the script at the end.
Request body
{
"range_oop": "AQs:1,AJs:0.48,...", // required, starting OOP range
"range_ip": "AA:1,AKs:1,...", // required, starting IP range
"solver_results": { /* required: the decision tree */ },
"node_id": "root/CHECK", // required, the action line
"board": "2c2h2s", // optional, unseparated string (same as other endpoints)
"normalize": true, // optional, default true
"explain": false, // optional, per-hand change explanation
"track_hands": ["AA"], // optional, track only these hands
"bluff_discount_ratio": 0.8, // optional, bluff discount
"hero_position": "oop", // optional, "oop" / "ip" — which player is Hero (for card blocking)
"hero_hand": "AsKs" // optional, remove combos that contain Hero's cards (blockers); echoed back
}
Card blocking (optional): set hero_position ("oop"/"ip") + hero_hand to remove every combo that contains one of Hero's cards from the ranges — useful for range-vs-hand analysis. Both are echoed back in the response. (This pair is also honored by the projected-range wrappers; /v1/gto/flop/projected-range additionally supports partner_hands.)
Call example
# solver_results is large, put it in a file and use -d @
curl -s https://pokerai.bet/v1/gto/range -H "Authorization: Bearer $POKERAI_API_KEY" \
-H "Content-Type: application/json" -d @range_request.json
Response (200, real response, long strings truncated)
{"bluff_combos_ratio": 0.33, "bluff_discount_ratio": 1, "board": ["2c", "2h", "2s"], "hand_bottom_ranks_ip": "KsQh:2413,KsQd:2413,KsQc:2413,KhQs:2413,…", "hand_bottom_ranks_oop": "KcJh:2414,KcTs:2415,KsTs:2415,KsTh:2415,…", "hand_ranks_ip": "QcQh:313,QdQh:313,QdQs:313,QhQs:313,QcQs:313,…", "hand_ranks_oop": "Ad2d:155,AdAc:311,AhAc:311,AhAd:311,AsAc:311,…", "node_id": "root/CHECK", "path_length": 1, "range_ip_new": "33:0.193023,44:0.216279,54s:0.65814,…", "range_ip_new_raw": "3c3d:0.193023,3c3h:0.193023,3c3s:0.193023,…", "range_ip_new_raw_before_normalization": "3c3d:0.166,3c3h:0.166,3c3s:0.166,3d3h:0.166,…", "range_oop_new": "33:0.245724,44:0.272975,54s:0.420008,…", "range_oop_new_raw": "3d3c:0.245857,3h3c:0.245847,3h3d:0.24586,…", "range_oop_new_raw_before_normalization": "3d3c:0.245843,3h3c:0.245833,3h3d:0.245845,…", "quota": {"used": 7, "limit": 100}}
| field | description |
|---|---|
range_oop_new / range_ip_new | The normalized updated range (class notation), used directly as the next street's oop_range / ip_range fed to the solver. |
range_oop_new_raw / range_ip_new_raw | The intermediate value after bluff discount, not normalized (combo notation); use as needed. |
range_oop_new_raw_before_normalization / range_ip_new_raw_before_normalization | The raw value narrowed only along the action line, without bluff discount or normalization. |
hand_ranks_oop / hand_ranks_ip | Hand strength ranking (combo:rank, smaller value is stronger). |
hand_bottom_ranks_oop / hand_bottom_ranks_ip | Bottom-of-range ranking. |
node_id / board / path_length | The echoed action line, board, and number of action-line steps. |
bluff_discount_ratio / bluff_combos_ratio | The bluff discount parameters actually used this time. |
quota | This month's general quota usage (used / limit). |
Possible errors
| HTTP | error | trigger / how to fix |
|---|---|---|
| 400 | missing_field | Missing one of range_oop / range_ip / solver_results / node_id. |
| 400 | bad_request | The solver side rejected it (e.g. the node_id does not lead anywhere in the decision tree you supplied). |
Download: request.json (~1MB, includes the decision tree)response.jsonassemble_solver_results.py (end-to-end script)
Flop projected range projected-range
POST https://pokerai.bet/v1/gto/flop/projected-range charges 1 general
Full parameter / response schema, and try it live → interactive reference.
Narrow the OOP/IP ranges along a flop action line to directly get the starting range that enters the turn. Supply the full spot (board/pot_type/positions) and one action line (node_id), and the platform assembles the decision tree on the server automatically and completes the range update, returning the same fields as range conversion, plus an echoed pot_type.
This is a convenience wrapper over /v1/gto/range: it spares you the manual steps of calling /v1/gto/flop/tree + per-node /v1/gto/flop/node to assemble solver_results. It only applies to flop action lines (the decision tree is assembled from this platform's presolved flop results); for turn→river etc. where you must bring your own decision tree, still use /v1/gto/range.
Request body
{
"board": "2c2h2s",
"pot_type": "SRP",
"positions": { "hero": "UTG", "raiser": "UTG", "caller": "BTN" },
"node_id": "root/BET_4",
"normalize": true,
"bluff_discount_ratio": 0.8,
"bluff_combos_ratio": 0.5,
"hero_position": "oop",
"hero_hand": "AsKs",
"partner_hands": ["Ac9c"]
}
| field | description |
|---|---|
board | Required. The flop (unseparated string, same as other endpoints), e.g. "2c2h2s". |
pot_type | Required. The pot type, e.g. "SRP". |
positions | Required. { hero, raiser, caller } (same as flop); 3bet/limp spots may carry three_bettor / limper. |
node_id | Optional, default "root". The flop action line, using the api notation returned by /v1/gto/flop/tree (absolute bet amount in BB), e.g. "root/BET_4", "root/CHECK/BET_8/CALL". |
normalize | Optional, default true. Whether to normalize the updated range. |
bluff_discount_ratio / bluff_combos_ratio | Optional, each in [0,1] (out of range → 400). bluff_discount_ratio weights the bottom-of-range bluff combos; bluff_combos_ratio is the fraction of the range treated as bluffs. Omit to use the server's turn/river defaults. Both echoed back. |
hero_position / hero_hand | Optional card blocking. Set hero_position ("oop"/"ip") + hero_hand (e.g. "AsKs"): removes every combo containing one of Hero's cards from the villain's updated range (range_*_new_raw), and ensures Hero's own hand is present in Hero's own range. Both echoed back. Only range_*_new_raw is affected — hand_ranks / hand_bottom_ranks are board-filtered only. |
partner_hands | Optional array of 4-char combos (e.g. ["Ac9c"]), requires hero_position. Removes every combo containing one of those cards from the villain's range_*_new_raw — model known dead cards (folded hands, exposed cards). Input only (not echoed). Also supported on /v1/gto/turn/projected-range. |
flop_version | Optional. Which flop dataset (one solved per preflop version): 6max (default) / 6max_RC_100bb_200NL / 6max_RC_100bb_100NL / 6max_RC_40bb. Omit for the default (6max); if that version has no data for the spot it degrades gracefully to 6max; an unknown value -> 400 unsupported_flop_version. Independent of preflop_version. |
Call example
curl -s https://pokerai.bet/v1/gto/flop/projected-range -H "Authorization: Bearer $POKERAI_API_KEY" \
-H "Content-Type: application/json" -d @projected_range_request.json
Response (200, all fields listed, long strings truncated)
{"board": ["2c", "2h", "2s"], "pot_type": "SRP", "node_id": "root/BET_4", "bluff_combos_ratio": 0.5, "bluff_discount_ratio": 0.8, "hero_position": "oop", "hero_hand": "AsKs", "hand_bottom_ranks_ip": "AhTh:2405,AsTs:2405,Ac9c:2406,Ad9d:2406,…", "hand_bottom_ranks_oop": "AhJs:2404,AsJc:2404,AsJd:2404,AsJh:2404,…", "hand_ranks_ip": "QcQd:313,QcQh:313,QcQs:313,QdQh:313,…", "hand_ranks_oop": "Ad2d:155,AdAc:311,AdAh:311,AdAs:311,…", "path_length": 1, "range_ip_new": "33:0.193023,44:0.216279,54s:0.65814,55:0.344186,…", "range_ip_new_raw": "3c3d:0.193023,3c3h:0.193023,3c3s:0.193023,3d3h:0.193023,…", "range_ip_new_raw_before_normalization": "3c3d:0.166,3c3h:0.166,3c3s:0.166,3d3h:0.166,…", "range_oop_new": "44:0.0120032,55:0.0422217,66:0.181192,77:0.327323,…", "range_oop_new_raw": "4s4c:0.0666554,4s4h:0.0666554,5d5c:0.005,5d5h:0.005,…", "range_oop_new_raw_before_normalization": "4s4c:0.011816,4s4h:0.011816,5s5c:0.0392591,5s5h:0.0392591,…", "quota": {"used": 7, "limit": 100}}
| field | description |
|---|---|
range_oop_new / range_ip_new | The normalized updated range (class notation), used directly as the next street's oop_range / ip_range fed to the solver. |
range_oop_new_raw / range_ip_new_raw | The intermediate value after bluff discount, not normalized (combo notation); use as needed. |
range_oop_new_raw_before_normalization / range_ip_new_raw_before_normalization | The raw value narrowed only along the action line, without bluff discount or normalization. |
hand_ranks_oop / hand_ranks_ip | Hand strength ranking (combo:rank, smaller value is stronger). |
hand_bottom_ranks_oop / hand_bottom_ranks_ip | Bottom-of-range ranking. |
node_id / board / pot_type / path_length | The echoed action line, board, pot type, and number of action-line steps. |
bluff_discount_ratio / bluff_combos_ratio | The bluff discount parameters actually used this time. |
quota | This month's general quota usage (used / limit). |
Possible errors
| HTTP | error | trigger / how to fix |
|---|---|---|
| 400 | invalid_board / invalid_positions | board is not 3 cards, or positions.hero is invalid. |
| 404 | no_solution (and a server errorType such as no_ranges / no_root_node) | No presolved flop tree for this spot, or the node_id action line does not lead anywhere. |
Download: request.jsonresponse.json
Turn projected range (turn→river) projected-range
POST https://pokerai.bet/v1/gto/turn/projected-range free
Full parameter / response schema, and try it live → interactive reference.
The turn→river version of the flop projected range, for a real-time turn solve. Narrow the ranges along a turn action line (including the street-closing CALL/CHECK) to directly get the starting range that enters the river. The entering-turn OOP/IP ranges are read from the solve's own config (the ranges it was scheduled with via /v1/gto/solver), so you only pass the solve handle solve + one node_id. Free (the solve was already charged via /v1/gto/solver), like /v1/gto/solver/tree; returns the same fields as range conversion.
Poll /v1/gto/solver/tree until spot_status = queryable first. For turn→river you don't assemble solver_results manually (the platform reads it from the solve and assembles it).
Request body
{
"solve": "eyJ…",
"node_id": "root/CHECK/BET 6.000000/CALL",
"normalize": true,
"bluff_discount_ratio": 0.8,
"hero_position": "oop",
"hero_hand": "AsKs"
}
| field | description |
|---|---|
solve | Required. The solve handle returned by /v1/gto/solver (a turn solve). |
node_id | Required. A turn action line, may end in the street-closing CALL/CHECK. Solver node notation (with spaces), e.g. "root/CHECK/BET 6.000000/CALL" (from /v1/gto/solver/tree nodes). |
normalize | Optional, default true. Whether to normalize the updated range. |
bluff_discount_ratio | Optional. Bluff discount (0..1). |
hero_position / hero_hand | Optional card blocking: hero_position ("oop"/"ip") + hero_hand (e.g. "AsKs") removes every combo containing one of Hero's cards from the villain's updated range. |
partner_hands | Optional array of 4-char combos (e.g. ["Ac9c"]), requires hero_position. Removes every combo containing one of those cards from the villain's range_*_new_raw. Input only. |
Call example
curl -s https://pokerai.bet/v1/gto/turn/projected-range -H "Authorization: Bearer $POKERAI_API_KEY" \
-H "Content-Type: application/json" -d @turn_projected_range_request.json
Response (200, all fields listed, long strings truncated)
{"bluff_combos_ratio": 0.33, "bluff_discount_ratio": 1, "board": ["8d", "4h", "8s", "Qc"], "hand_bottom_ranks_ip": "TcTs:2943,TdTh:2943,TdTs:2943,9h9s:3020,…", "hand_bottom_ranks_oop": "AhKd:4646,AhKc:4646,AcKh:4646,JsTs:4746,…", "hand_ranks_ip": "Ac8c:2007,Ah8h:2007,KcKs:2645,KhKs:2645,…", "hand_ranks_oop": "8c8h:85,Ah8h:2007,Ac8c:2007,9c8c:2029,…", "node_id": "root/CHECK/BET 6.000000/CALL", "path_length": 3, "range_ip_new": "99:0.603064,A8s:0.5,AKs:0.70197,AQs:0.453337,…", "range_ip_new_raw": "9c9d:0.71487,9c9h:0.394271,9c9s:0.71487,…", "range_ip_new_raw_before_normalization": "9c9d:0.714699,9c9h:0.394176,9c9s:0.714699,…", "range_oop_new": "88:0.131148,98s:0.0902494,A8s:0.0344418,…", "range_oop_new_raw": "8c8h:0.777777,9c8c:0.185048,9h8h:0.17177,…", "range_oop_new_raw_before_normalization": "8c8h:0.418968,9c8c:0.0996806,9h8h:0.0925282,…"}
| field | description |
|---|---|
range_oop_new / range_ip_new | The normalized entering-river range (class notation), used directly as the river solve's oop_range / ip_range. |
range_*_raw / range_*_raw_before_normalization | The un-normalized / pre-discount intermediate values (combo notation). |
hand_ranks_* / hand_bottom_ranks_* | Hand strength ranking / bottom-of-range ranking (combo:rank, smaller is stronger). |
node_id / board / path_length | The echoed action line, board (4 cards), and number of action-line steps. |
bluff_discount_ratio / bluff_combos_ratio | The bluff discount parameters actually used this time. |
Note: free, so the response has no quota field; and no pot_type (that is flop-only).
Possible errors / states
| HTTP | error / state | trigger / how to fix |
|---|---|---|
| 200 | { "spot_status": "computing" } | The solve has not converged yet — keep polling /v1/gto/solver/tree until queryable. |
| 400 | missing_solve / missing_node_id | Missing the solve handle or node_id. |
| 410 | expired | The solve expired (TTL) — reschedule via /v1/gto/solver. |
| 502 | no_solution etc. | Upstream solve error. |
Download: request.jsonresponse.json
Node EVs solver
POST https://pokerai.bet/v1/gto/evs free
Full parameter / response schema, try it live → interactive reference.
Per-hand, per-action expected values at one node of a completed solve. Give the solve handle solve (from /v1/gto/solver) + a node_id (from /v1/gto/solver/tree); optional hand filters to one hand. Free (the solve was already charged). Poll /v1/gto/solver/tree until spot_status = queryable first.
Request body
{
"solve": "eyJ0Ijoic2x2X3h4eXoi...(handle from /v1/gto/solver)",
"node_id": "root",
"hand": "2c2d"
}
| field | description |
|---|---|
solve | Required. The solve handle from /v1/gto/solver. |
node_id | Required. A node from /v1/gto/solver/tree (solver notation, e.g. "root", "root/CHECK/BET 6.000000"). |
hand | Optional. Filter to one hand's EVs (e.g. "2c2d"); omit for all hands. |
Response (200, each hand's EV array aligned with actions)
{"node_id": "root", "task_id": "slv_srp_…", "player": 1, "round": "FLOP", "actions": ["CHECK", "BET 4.000000", "BET 97.000000"], "evs": {"2c2d": [-0.826359, -0.792223, -1.643411], "2c2h": […], …}}
| field | description |
|---|---|
actions | The node's actions in order; each hand's EV array aligns with this. |
evs | Per hand → array of each action's EV (bb). With hand given, evs is a single array for that hand. |
player / round / node_id / task_id | Which player acts, the street, and the echoed node / solve id. |
Note: free, so no quota field. Returns { "spot_status": "computing" } if the solve has not converged (poll /v1/gto/solver/tree).
Play a hand (full flow)
Stringing the endpoints together: one SRP hand (UTG opens, BTN calls), Hero = UTG. The curls below omit the auth header (same as Quickstart).
1) Preflop — should we open
POST /v1/gto/preflop
{ "hole_cards": "AdKd", "positions": { "hero": "UTG" },
"preflop_actions": [ { "position": "SB", "action": "small blind", "amount": 0.5 }, { "position": "BB", "action": "big blind", "amount": 1 } ] }
Just the SB + BB posts (no raises) = Hero is the first to act (an opening spot). Returns the opening frequency and sizing.
2) Flop — flop strategy
Flop 2c2h2s. The flop is a decision tree, two steps: first fetch the tree, then use Hero's node token to fetch the strategy.
// 1) fetch the tree (no hole_cards needed)
POST /v1/gto/flop/tree
{ "board": "2c2h2s", "pot_type": "SRP",
"positions": { "hero": "UTG", "raiser": "UTG", "caller": "BTN" } }
// → in nodes[], root (is_hero:true) carries a token
// 2) use root's token to fetch Hero strategy
POST /v1/gto/flop/node
{ "node": "<token of root>", "hole_cards": "AdKd" }
Hero's first decision = root; facing a bet / acting a second time → pick the corresponding is_hero:true node. See Flop decision tree.
3) Turn — real-time solve, range derived from the flop
The turn (e.g. 9d) needs a real-time solve, which requires both players' ranges entering the turn. Get them with the flop decision tree + range conversion:
/v1/gto/flop/treefor the startingoop_range/ip_rangeand decision nodes.- Along a flop action line (e.g.
root/CHECK/BET_8/CALL), call/v1/gto/flop/nodeper node (without hole_cards) to fetchrange_strategyand assemblesolver_results— see theassemble_solver_results.pyscript in the range conversion section. /v1/gto/rangeupdates along that line →range_oop_new/range_ip_neware the ranges entering the turn.- Feed them to the solver (board with 4 cards), poll for the tree until
queryable, then fetch the node:POST /v1/gto/solver { "board": "2c2h2s9d", "oop_range": "<OOP range entering turn>", "ip_range": "<IP range entering turn>", "pot": <turn pot>, "effective_stack": <turn effective stack>, "hero": "OOP" }
Already have your own range/spot? At step 3 give oop_range / ip_range directly and skip the derivation.
4) River — real-time solve
The river (board with 5 cards) is the same as the turn: you can solve it standalone (give the range entering the river directly), or use range conversion to update the turn action line once more to get the range entering the river and then feed it to the solver.
Poker engine / semantics API
Wraps pokerkit-plus (a superset of pokerkit 0.7.3): board/hand semantics, ranges & equity, eval/equity/ICM/notation, and game simulation. Reuses the same API key & quota — cheap endpoints charge the general bucket, Monte-Carlo endpoints the solve bucket. Base path /v1/pokerkit/*; card inputs are unseparated strings (AsKsQs), enums return {name,value}. Full per-endpoint params/schema & try-it-live → interactive reference; machine-readable spec → /v1/pokerkit/openapi.json (auto-synced with the code).
Board / hand semantics charges 1 general
Board (Hero-independent): /texture (wetness/connectivity/draws available), /nuts (nuts + tying combos), /category-combos, /board-report (texture+nuts). Hero: /hand-tier (made-hand tier), /draws, /outs, /blockers, /hand-report (texture+tier+draws+outs in one call).
Request fields (shared in this group)
| field | description |
|---|---|
board | Required. 3/4/5 community cards, unseparated, e.g. "AsKsQs". |
hole | Required for Hero endpoints (hand-tier / draws / outs / blockers / hand-report). 2 hole cards, e.g. "JhTh"; board endpoints (texture / nuts / category-combos / board-report) omit it. |
hand_type | Optional, default StandardHighHand (v1 only; other types return 400). |
dead | Optional. Dead / removed cards (accepted by all endpoints in this group). |
Texture charges 1 general
POST https://pokerai.bet/v1/pokerkit/texture — Board structure: wetness/connectivity/rank band/draw availability/suit shape. Full params / try it live →
{"board": "AsKsQs"}
{"result": {"cards": ["As", "Ks", "Qs"], "wetness": {"name": "WET", "value": "Wet"}, "connectivity": {"name": "HIGH", "value": "High"}, "rank_band": {"name": "HIGH", "value": "High"}, "straight_draw": {"name": "OPEN_ENDED", "value": "Open-ended"}, "flush_draw": {"name": "LIVE", "value": "Live"}, "are_two_tone": false, "are_monotone": true, "are_rainbow": false}}
Nuts charges 1 general
POST https://pokerai.bet/v1/pokerkit/nuts — Strongest makeable hand + all tying two-card combos (with is_royal / board_is_nuts). Full params / try it live →
{"board": "AsKsQs"}
{"result": {"hand": "TsJsKsQsAs", "combos": [{"cards": ["Ts", "Js"], "hand": "TsJsKsQsAs", "as_frozenset": ["Js", "Ts"]}], "candidate_count": 1176, "board_is_nuts": false, "is_royal": true, "label": {"name": "STRAIGHT_FLUSH", "value": "Straight flush"}}}
Category combos charges 1 general
POST https://pokerai.bet/v1/pokerkit/category-combos — Every live two-card combo grouped by made category. Full params / try it live →
{"board": "AsKsQs"}
{"result": {"by_category": {"HIGH_CARD": [{"cards": ["2c", "3c"], "hand": "2c3cKsQsAs", "as_frozenset": ["2c", "3c"]}, {"cards": ["2c", "3d"], "hand": "2c3dKsQsAs", "as_frozenset": ["2c", "3d"]}], …}}}
Board report charges 1 general
POST https://pokerai.bet/v1/pokerkit/board-report — texture + nuts in one call (board overview). Full params / try it live →
{"board": "AsKsQs"}
{"result": {"texture": {"cards": ["As", "Ks", "Qs"], "wetness": {"name": "WET", "value": "Wet"}, "connectivity": {"name": "HIGH", "value": "High"}, "rank_band": {"name": "HIGH", "value": "High"}, "straight_draw": {"name": "OPEN_ENDED", "value": "Open-ended"}, "flush_draw": {"name": "LIVE", "value": "Live"}, "are_two_tone": false, "are_monotone": true, "are_rainbow": false}, "nuts": {"hand": "TsJsKsQsAs", "combos": [{"cards": ["Ts", "Js"], "hand": "TsJsKsQsAs", "as_frozenset": ["Js", "Ts"]}], "candidate_count": 1176, "board_is_nuts": false, "is_royal": true, "label": {"name": "STRAIGHT_FLUSH", "value": "Straight flush"}}}}
Hand tier charges 1 general
POST https://pokerai.bet/v1/pokerkit/hand-tier — Hero made-hand tier (pair / two-pair / trips / kicker tiers, is_nut). Full params / try it live →
{"hole": "JhTh", "board": "AsKsQs"}
{"result": {"category": {"name": "STRAIGHT", "value": "Straight"}, "is_nut": false, "pair_tier": null, "two_pair_tier": null, "three_of_a_kind_tier": null, "kicker_tier": null, "nut_rank": {"name": "NON_NUT", "value": "Non-nut"}}}
Draws charges 1 general
POST https://pokerai.bet/v1/pokerkit/draws — Hero draws (straight / flush draw, nut rank). Full params / try it live →
{"hole": "Ah5h", "board": "Kh7h2c"}
{"result": {"straight_draw": null, "flush_draw": {"name": "LIVE", "value": "Live"}, "nut_rank": {"name": "NUT", "value": "Nut"}}}
Outs charges 1 general
POST https://pokerai.bet/v1/pokerkit/outs — Hero outs that upgrade the made category, grouped + count. Full params / try it live →
{"hole": "Ah5h", "board": "Kh7h2c"}
{"result": {"by_category": {"ONE_PAIR": ["2d", "2s", "5c", "5d", "5s", "7c", "7d", "7s", "Kc", "Kd", "Ks", "Ac", "Ad", "As"], "FLUSH": ["2h", "3h", "4h", "6h", "8h", "9h", "Th", "Jh", "Qh"]}, "count": 23}}
Blockers charges 1 general
POST https://pokerai.bet/v1/pokerkit/blockers — how many nut combos Hero blocks (blocker cards / fraction). Full params / try it live →
{"hole": "AhAd", "board": "AsKsQs"}
{"result": {"nut_combos_total": 1, "nut_combos_blocked": 0, "blocker_cards": [], "block_fraction": 0.0, "blocks_nuts": false}}
Hand report charges 1 general
POST https://pokerai.bet/v1/pokerkit/hand-report — texture + tier + draws + outs in one call (Hero overview, flagship). Full params / try it live →
{"hole": "JhTh", "board": "AsKsQs"}
{"result": {"texture": {"cards": ["As", "Ks", "Qs"], "wetness": {"name": "WET", "value": "Wet"}, "connectivity": {"name": "HIGH", "value": "High"}, "rank_band": {"name": "HIGH", "value": "High"}, "straight_draw": {"name": "OPEN_ENDED", "value": "Open-ended"}, "flush_draw": {"name": "LIVE", "value": "Live"}, "are_two_tone": false, "are_monotone": true, "are_rainbow": false}, "tier": {"category": {"name": "STRAIGHT", "value": "Straight"}, "is_nut": false, "pair_tier": null, "two_pair_tier": null, "three_of_a_kind_tier": null, "kicker_tier": null, "nut_rank": {"name": "NON_NUT", "value": "Non-nut"}}, "draws": {"straight_draw": null, "flush_draw": null, "nut_rank": null}, "outs": {"by_category": {}, "count": 0}}}
Ranges / equity charges 1 general
/range/expand (notation → concrete combos), /range/value (value range by made-category floor; aggression ∈ NO_BET/SINGLE_BET/RAISED), /range/nut-advantage (exact nut share, no sampling).
Request fields (shared in this group)
| field | description |
|---|---|
notation | (expand) range-notation array, e.g. ["AA","KQs","QQ+"]. |
hero / villain | (nut-advantage) one range-notation array each. |
board | Community cards (required for value / nut-advantage; expand omits it). |
aggression | (value) NO_BET / SINGLE_BET / RAISED sets the category floor; or floor to set it explicitly. |
Range expand charges 1 general
POST https://pokerai.bet/v1/pokerkit/range/expand — Expand range notation into concrete two-card combos. Full params / try it live →
{"notation": ["AA", "KQs"]}
{"result": [["Ac", "Ad"], ["Ac", "Ah"], ["Ac", "As"], ["Ad", "Ah"], ["Ad", "As"], ["Ah", "As"], ["Kc", "Qc"], ["Kd", "Qd"], ["Kh", "Qh"], ["Ks", "Qs"]]}
Range value charges 1 general
POST https://pokerai.bet/v1/pokerkit/range/value — Build a value range by made-category floor (aggression). Full params / try it live →
{"board": "AsKsQs", "aggression": "SINGLE_BET"}
{"result": [["2s", "3s"], ["2s", "4s"], ["2s", "5s"], ["2s", "6s"], ["2s", "7s"], ["2s", "8s"], …]}
Range nut-advantage charges 1 general
POST https://pokerai.bet/v1/pokerkit/range/nut-advantage — Exact nut-share split by combo count (no sampling, deterministic). Full params / try it live →
{"hero": ["AA", "KK"], "villain": ["QQ", "JJ"], "board": "AsKsQs"}
{"result": {"hero_share": 0.5, "villain_share": 0.5, "basis": {"name": "NUT_SHARE", "value": "Nut share"}}}
Eval · equity · ICM · notation
charges 1 general /eval/hand, /eval/compare (rank + ties), /icm (deterministic), /notation/parse (.phh → structured). charges 1 solve Monte-Carlo: /equity, /hand-strength, /range/equity-advantage — with sample_count (capped) and seed (reproducible, ~2s@10k).
Request fields (shared in this group)
Monte-Carlo endpoints (equity / hand-strength / range/equity-advantage) charge the solve bucket; the rest charge general.
| field | description |
|---|---|
hole / holdings / board | Eval inputs: single hole + board (eval/hand), or multi-hand holdings (2+) + board (eval/compare). |
ranges / hole_range / hero / villain | Range notation (equity / hand-strength / range/equity-advantage). |
sample_count / seed | Monte-Carlo: sample count (capped; over the cap clamps) / RNG seed (reproducible). |
payouts / chips | (icm) payout structure / per-player chips. |
text | (notation/parse) a .phh hand-history string. |
Eval hand charges 1 general
POST https://pokerai.bet/v1/pokerkit/eval/hand — Evaluate hole + board into a made hand (5 cards + category label). Full params / try it live →
{"hole": "JhTh", "board": "AsKsQs"}
{"result": {"hand": "JhThAsKsQs", "label": {"name": "STRAIGHT", "value": "Straight"}}}
Eval compare charges 1 general
POST https://pokerai.bet/v1/pokerkit/eval/compare — Rank 2+ holdings on a board (with ties). Full params / try it live →
{"holdings": ["AcAd", "KsKh", "JhTh"], "board": "AsKsQs"}
{"result": [{"index": 2, "hole": "JhTh", "hand": "JhThAsKsQs", "rank": 1, "label": {"name": "STRAIGHT", "value": "Straight"}}, {"index": 0, "hole": "AcAd", "hand": "AcAdAsKsQs", "rank": 2, "label": {"name": "THREE_OF_A_KIND", "value": "Three of a kind"}}, {"index": 1, "hole": "KsKh", "hand": "KsKhAsKsQs", "rank": 3, "label": {"name": "THREE_OF_A_KIND", "value": "Three of a kind"}}]}
Equity charges 1 solve
POST https://pokerai.bet/v1/pokerkit/equity — Equity of multiple ranges on a board (Monte-Carlo). Full params / try it live →
{"ranges": [["AA"], ["KK"]], "board": "", "sample_count": 2000, "seed": 7}
{"result": {"equities": [0.8235, 0.1765], "sample_count": 2000}}
Hand strength charges 1 solve
POST https://pokerai.bet/v1/pokerkit/hand-strength — Hero win fraction vs N players (Monte-Carlo). Full params / try it live →
{"hole_range": ["AhKh"], "board": "Qh7c2d", "player_count": 3, "sample_count": 2000, "seed": 7}
{"result": {"hand_strength": 0.3945, "player_count": 3, "sample_count": 2000}}
ICM charges 1 general
POST https://pokerai.bet/v1/pokerkit/icm — ICM equity split from chips / payouts (deterministic). Full params / try it live →
{"payouts": [50, 30, 20], "chips": [5000, 3000, 2000]}
{"result": {"icm": [38.392857142857146, 32.75, 28.857142857142854]}}
Range equity-advantage charges 1 solve
POST https://pokerai.bet/v1/pokerkit/range/equity-advantage — Equity-share split between two ranges (Monte-Carlo). Full params / try it live →
{"hero": ["AA", "KK"], "villain": ["QQ", "JJ"], "board": "AsKsQs", "sample_count": 2000, "seed": 7}
{"result": {"hero_share": 0.80325, "villain_share": 0.19675, "basis": {"name": "EQUITY", "value": "Equity"}, "sample_count": 2000}}
Notation parse charges 1 general
POST https://pokerai.bet/v1/pokerkit/notation/parse — Parse a .phh hand history into structured config (variant / blinds / stacks / actions…). Full params / try it live →
{"text": "variant = \"NT\"\nante_trimming_status = true\nantes = [0, 0]\nblinds_or_straddles = [1, 2]\nmin_bet = 2\nstarting_stacks = [200, 200]\nactions = [\"d dh p1 AhKh\", \"d dh p2 QsQd\", \"p1 cbr 6\", \"p2 cc\", \"d db Qh7c2d\"]\n"}
{"result": {"variant": "NT", "ante_trimming_status": true, "antes": [0, 0], "blinds_or_straddles": [1, 2], "bring_in": null, "small_bet": null, "big_bet": null, "min_bet": 2, "starting_stacks": [200, 200], "actions": ["d dh p1 AhKh", "d dh p2 QsQd", "p1 cbr 6", "p2 cc", "d db Qh7c2d"], "automations": [{"name": "ANTE_POSTING", "value": "Ante posting"}, …], "author": null, "event": null, "day": null, "month": null, "year": null, "hand": null, "currency": null}}
Game simulation charges 1 general
Full-information, stateless replay: the client carries the action list, the server rebuilds the state with pokerkit (zero server state). /games/state (current-spot snapshot), /games/step (apply next_action; expected_action_count is an optimistic-concurrency token, mismatch → 409), /notation/replay (config or .phh text → per-step snapshots), /cards/normalize (validate/normalize a card string).
Request fields (shared in this group)
| field | description |
|---|---|
variant | Required. Variant code, e.g. "NT" (no-limit hold'em); full list at /v1/pokerkit/meta. |
antes / starting_stacks | Required. Per-player antes / starting stacks (integer arrays). |
blinds_or_straddles / min_bet | Blinds / straddles; min_bet required for no-limit / pot-limit (fixed-limit / stud use small_bet / big_bet / bring_in). |
actions | Action list so far (pokerkit notation: d dh p1 AhKh deal hole, p2 cbr 6 raise to 6, p1 cc check/call, p1 f fold). |
next_action / expected_action_count | (step) action to apply / concurrency token (= length of the list you extend; mismatch → 409). |
text / index | (replay) a .phh text instead; index returns just that step. cards/normalize takes only cards (a card string). |
Games state charges 1 general
POST https://pokerai.bet/v1/pokerkit/games/state — Action list → current-spot snapshot (all hole cards shown). Full params / try it live →
{"variant": "NT", "antes": [0, 0], "blinds_or_straddles": [1, 2], "min_bet": 2, "starting_stacks": [200, 200], "actions": ["d dh p1 AhKh", "d dh p2 QsQd", "p2 cbr 6"]}
{"result": {"snapshot": {"terminal": false, "street_index": 0, "actor_index": 0, "pot": 8, "bets": [2, 6], "stacks": [198, 194], "board": [], "hole_cards": [{"player": 0, "cards": ["Ah", "Kh"]}, {"player": 1, "cards": ["Qs", "Qd"]}], "legal_actions": [{"action": "fold"}, {"action": "check_or_call", "amount": 4}, {"action": "complete_bet_or_raise_to", "min": 10, "max": 200}]}, "actions": ["d dh p1 AhKh", "d dh p2 QsQd", "p2 cbr 6"]}}
Games step charges 1 general
POST https://pokerai.bet/v1/pokerkit/games/step — Apply next_action → new snapshot (concurrency token; 409 on mismatch). Full params / try it live →
{"variant": "NT", "antes": [0, 0], "blinds_or_straddles": [1, 2], "min_bet": 2, "starting_stacks": [200, 200], "actions": ["d dh p1 AhKh", "d dh p2 QsQd", "p2 cbr 6"], "next_action": "p1 cc", "expected_action_count": 3}
{"result": {"snapshot": {"terminal": false, "street_index": 1, "actor_index": null, "pot": 12, "bets": [0, 0], "stacks": [194, 194], "board": [], "hole_cards": [{"player": 0, "cards": ["Ah", "Kh"]}, {"player": 1, "cards": ["Qs", "Qd"]}], "legal_actions": [{"action": "deal_board"}]}, "actions": ["d dh p1 AhKh", "d dh p2 QsQd", "p2 cbr 6", "p1 cc"]}}
Notation replay charges 1 general
POST https://pokerai.bet/v1/pokerkit/notation/replay — config or .phh → per-step snapshots (optional index for a single step). Full params / try it live →
{"variant": "NT", "antes": [0, 0], "blinds_or_straddles": [1, 2], "min_bet": 2, "starting_stacks": [200, 200], "actions": ["d dh p1 AhKh", "d dh p2 QsQd", "p2 cbr 6", "p1 f"], "index": 2}
{"result": {"snapshot": {"terminal": false, "street_index": 0, "actor_index": 1, "pot": 3, "bets": [2, 1], "stacks": [198, 199], "board": [], "hole_cards": [{"player": 0, "cards": ["Ah", "Kh"]}, {"player": 1, "cards": ["Qs", "Qd"]}], "legal_actions": [{"action": "fold"}, {"action": "check_or_call", "amount": 1}, {"action": "complete_bet_or_raise_to", "min": 4, "max": 200}]}, "step_count": 5}}
Cards normalize charges 1 general
POST https://pokerai.bet/v1/pokerkit/cards/normalize — Validate / normalize a card string to standard 2-char cards. Full params / try it live →
{"cards": "Ah Ks Qs"}
{"result": ["Ah", "Ks", "Qs"]}
Also GET /v1/pokerkit/meta (versions / variant codes / hand types / enum vocabularies). Full parameter schema and try-it-live for every endpoint → interactive reference (browse both APIs from the top).
All example downloads
A summary of each endpoint's complete request/response (real data, no fields omitted) is in the "Download" of each section above, or: preflop · flop tree · flop node · solver · range · projected range · projected range response · assemble script.
Versioning & changelog
Versioning policy
- The
/v1in the path is the major version. Breaking changes (removing/changing fields, changing semantics) go to a new major version/v2, and/v1stays available. - Backward-compatible additions (new endpoints, new optional fields, new response fields) are added directly to
/v1without separate notice — please parse by "ignoring unknown fields" and do not strictly allowlist-validate response fields. - Field deprecation will be flagged in the changelog below ahead of time, and removed only after a transition period.
Changelog
| date | change |
|---|---|
| 2026-07-04 | Added /v1/gto/evs (per-hand, per-action node EVs of a completed solve); /v1/gto/turn/projected-range now also supports partner_hands. |
| 2026-07-04 | /v1/gto/flop/projected-range now honors bluff_discount_ratio / bluff_combos_ratio from the request, injects Hero's own hand into Hero's range, and adds partner_hands (block partner dead cards from the villain range). |
| 2026-06-22 | Added the Poker engine / semantics API /v1/pokerkit/* (board/hand semantics, ranges/equity, eval/equity/ICM/notation, game simulation; same key & quota). See below. |
| 2026-06-22 | Added /v1/gto/preflop/range (the whole 13×13 preflop range, 169 hands in one call). |
| 2026-06-18 | Split flop: removed the single-query /v1/gto/flop, replaced by /v1/gto/flop/tree (fetch tree) + /v1/gto/flop/node (fetch node, free), aligned with the solver's tree/node. |
| 2026-06-17 | Added /v1/gto/flop/projected-range (a convenience wrapper over /v1/gto/range: supply the full flop spot + action line, the server assembles the decision tree automatically and returns the turn range directly). |
| 2026-06-17 | Unified board / hand format: input is an unseparated string, response board is always returned as an array. |
| 2026-06-17 | Added the OpenAPI 3.0 spec; added range-notation / concepts / full-flow tutorial docs. |
| 2026-06-17 | /v1/gto/solver now supports real-time flop solving (board=3); bet_sizes.flop can customize the flop bet sizes. |
| 2026-06-17 | Fix: flop-query raise amount_bb was wrongly 0 (now returns the correct absolute BB). |
| 2026-06-17 | Added range conversion /v1/gto/range; /flop/tree exposes the starting oop_range / ip_range; /flop and /solver/node return the full range strategy when hole_cards is not passed. |
| 2026-06-16 | Added the turn / river real-time solver /v1/gto/solver family (separate solve quota); added the flop decision tree /v1/gto/flop/tree. |
| 2026-06-15 | Removed the recommendation field from all responses — please choose actions yourself based on frequency. |
pokerai.bet · GTO API v1