Preflop action tree
How every preflop spot is addressed — the action-path grammar, tokens, and a worked walk of the tree.
The preflop API does not take a street, pot type, or free-text line. It takes an action path: an underscore-joined chain of POSITION_ACTION pairs that walks the solver tree in acting order. The same grammar is used for NLHE 6-max, PLO 6-max, and PLO 9-max. Only the raise tokens and hand notation change by game.
UTG_60%_HJ_Call
│ │ │ └─ …and HJ calls. (path ends in an ACTION → names a range)
│ │ └─ then HJ…
│ └─ UTG raises to 60% (a size token)
└─ UTG acts first…
UTG_60%_HJ
└─ path ends in a POSITION → names a decision node (HJ to act)
The two forms map onto the two lookup endpoints:
- A path ending in an action names a single range — pass it as
spotto/range. - A path ending in a position names a whole decision — pass it as
historyto/node.
Walking the tree
Think of the dataset as a tree of decision nodes. Each node belongs to one seat. Each legal action is an edge to the next node (or to a range at a terminal for that street).
A 6-max cash hand always starts UTG, then HJ, CO, BTN, SB, BB. Every seat that folds is written out. There is no BTN_60% shortcut for a button open — the folds in front are part of the address:
UTG_Fold_HJ_Fold_CO_Fold_BTN_60%
That string is a spot (ends in an action): UTG/HJ/CO folded, BTN opened 60%. To ask what BB should do facing that open, after SB folds:
history = UTG_Fold_HJ_Fold_CO_Fold_BTN_60%_SB_Fold_BB
spot = UTG_Fold_HJ_Fold_CO_Fold_BTN_60%_SB_Fold_BB_Call
history ends on BB (BB to act). spot appends _Call (the Call range at that node).
Raise sizes are percent of pot as they appear in the tree: 60%, 100%, plus Fold, Call, AI.
| You want | Path | Endpoint |
|---|---|---|
| UTG's RFI decision | UTG | /node?history=UTG |
| UTG opens 60% | UTG_60% | /range?spot=UTG_60%25 |
| HJ facing that open | UTG_60%_HJ | /node?history=UTG_60%25_HJ |
| HJ 3-bets 100% | UTG_60%_HJ_100% | /range?spot=UTG_60%25_HJ_100%25 |
| BTN open after three folds | UTG_Fold_HJ_Fold_CO_Fold_BTN_60% | /range?spot=… |
% in size tokens must be encoded as %25 in the query string.
Raises are labelled by betting depth, not a percent: Open, 3B, 4B, 5B, … plus Fold, Call, AI.
| You want | Path | Endpoint |
|---|---|---|
| UTG's RFI decision | UTG | /node?history=UTG |
| UTG opens | UTG_Open | /range?spot=UTG_Open |
| HJ facing that open | UTG_Open_HJ | /node?history=UTG_Open_HJ |
| HJ 3-bets | UTG_Open_HJ_3B | /range?spot=UTG_Open_HJ_3B |
| BTN open after three folds | UTG_Fold_HJ_Fold_CO_Fold_BTN_Open | /range?spot=… |
When a node offers more than one raise size, the label carries a raw size id, e.g. Open-40100. Copy the exact token from /node — do not guess the suffix.
Action tokens
| Game | Tokens |
|---|---|
| NLHE 6-max | Fold, Call, AI (all-in), and raise sizes as percentages, e.g. 60%, 100% |
| NLHE MTT 8-max | Fold, Call, Limp, AI, and raises by betting depth: Open, 3B, 4B, 5B. The chip raise-to is sizeBb on the action, not the token. See NLHE tournament (8-max). |
| PLO | Fold, Call, AI, and raises labelled by betting depth: Open, 3B, 4B, 5B, … |
Two encoding rules matter in query strings:
- Encode
%in NLHE size tokens as%25—UTG_60%_HJ_Callbecomesspot=UTG_60%25_HJ_Call. - When a PLO node offers more than one raise size, the label carries a raw size id suffix, e.g.
Open-40100. Never guess these — read the exact tokens from/nodeor/spots.
Betting depth
/spots tags every decision node with a depth for filtering:
| Depth | Meaning |
|---|---|
1 | RFI — unraised pot |
2 | Facing a single raise |
3 | 3-bet pot |
4 | 4-bet pot or deeper (capped at 4) |
Depth counts raises made, not seats acted: a fold-around to a BTN open is still depth 1.
Discovery-first workflow
- Call the game discovery endpoint (
/nl/v2,/nlmtt8,/plo, or/plo9) — keyless — for livestacksandpositions. For 8-max MTT, also call keyless/nlmtt8/config?stack=for blinds, the BB ante, and open sizes. GET /spots?stack=100for every validhistoryat that depth (and each row'sdepth).GET /node?stack=100&history=…foravailable_actionsand the mixed strategy at that seat.- Append one of those action tokens to build a
spot, thenGET /range.
Constructing common lines by hand (UTG open, BTN RFI after folds) works; anything with mixed raise sizes should come from step 3.
Hand notations
NLHE uses the 169-class grid: pairs (AA), suited (AKs), offsuit (AKo). Weights are per class in [0, 1]; the combos field is the weighted concrete-combo count (a pair is 6 combos, suited 4, offsuit 12).
PLO uses suit-grouping notation over the 16,432 four-card starting-hand classes:
| Example | Meaning |
|---|---|
AAKK | Rainbow-agnostic AAKK — no suit constraints stated |
(2A)AA | Trip aces with exactly two suited to the ace |
(KA)(KA) | Double-suited AK+AK |
Encode the parentheses in query strings: ( → %28, ) → %29. So hand=(KA)(KA) is sent as hand=%28KA%29%28KA%29.
Common mistakes
| Symptom | Cause |
|---|---|
404 on a path you "know" exists | Missing fold-arounds, wrong raise token, or that line is not in this stack's tree |
400 malformed path | Unencoded %, or a token the grammar does not allow |
| Empty / huge PLO payload | Omit minFreq and the 16k-class range comes back in full |
Guessing Open-40100 | Size-id suffixes are tree-specific; copy them from /node |