# Preflop ranges API

schemaVersion 1. Machine-readable spec: https://www.pokerstudy.ai/api/ranges/openapi.json

Auth is unchanged (`requireDataAccess`). pokerdata.io proxies the same paths at `/api/v1/ranges`.

## Units

| Field | Unit |
| --- | --- |
| stack, stacks, blinds, ante, sizeBb, openSizes.sizeBb | big blinds (bb) |
| sizePctPot | percent of the pot after calling |
| evs | small blinds (sb). evUnit is "sb". evBaseline "fold" means every stored fold EV is 0, so evs are relative to folding |
| omitted weight | 0. `full=1` on a hold'em node writes all 169 hands with explicit 0. EVs are only present for hands the bundle scored |

Two small blinds are one big blind. The API does not divide evs by 2.

Responses include `schemaVersion` and a `units` object.

## Endpoints

- `GET /api/ranges/{game}/node?stack=&history=` — every action at a decision. `history` ends in the seat to act.
- `GET /api/ranges/{game}/range?stack=&spot=` — one action's range. `spot` ends in the action.
- `GET /api/ranges/{game}/config?stack=` — seats, stacks, blinds, ante, openSizes, source, importedAt. Unknowns are null and listed in `unverified`.

Games: `nl`, `nl/v2`, `nl9`, `nlmtt8`, `plo`, `plo9`, `plo5`, `plo6hu`.

## Monker raise codes (nlmtt8)

A filename code `40000+pct` raises by `pct` percent of the pot after calling. Blinds and the dead ante are in the pot.

`raiseTo = currentBet + (pct/100) * (pot + toCall)`

`toCall` is `currentBet - chipsAlreadyIn`, so a posted blind is not paid twice.

| Code | Meaning |
| --- | --- |
| 0 | Fold |
| 1 | Call |
| 3 | All-in (raise-to = stack) |
| 5 | Min-raise |
| 14 | Raise-to 3 small blinds (1.5bb) |
| 40000+pct | Pot-percent raise |

Checked against the source pack:

- 9-max, no ante, 100bb, open `40120` → sizeBb 4, sizePctPot 120
- nlmtt8 40bb (blinds 0.5/1, 1bb BB ante), open `40034` → sizeBb 2.19, sizePctPot 34 (the pack lists this as 2.2x)
- same stack, SB `40060` → sizeBb 2.8, sizePctPot 60 (listed as 2.8x)

A re-import stores those on each raise under bundle `#meta` schema 1: `{ sizeBb, sizePctPot, monkerCode }`. Action keys stay `Open` / `3B` so old histories keep working. Published bundles do not have `#meta` yet, so `sizeBb` on a live node is null and `sizeReason` says so. `/config` `openSizes` is filled from the pack listing now.

## nlmtt8 open sizes (pack listing)

Source: MonkerGuy 8max BB-ante MTT pack. `stackIncludesAnte` is true: the named depth is the starting stack, and the 1bb BB ante is posted from that stack into the pot. 10bb is all-in/fold versus 50bb stacks, so per-seat `stacks` at 10bb stay null.

| Stack (bb) | Open | SB open |
| --- | --- | --- |
| 10 | all-in | all-in | All-in/fold versus 50bb stacks.
| 15 | 2x | 2.5x |
| 20 | 2x | 2.5x |
| 30 | 2x | 2.8x |
| 40 | 2.2x | 2.8x |
| 50 | 2.2x | 2.8x |
| 75 | 2.5x | 2.8x |
| 100 | 2.25x | 3x |
| 200 | 2.5x | 3x |
| 300 | 2.5x | 3x |

## Example

`GET /api/ranges/nlmtt8/config?stack=40`

`openSizes.open` is `{ "label": "2.2x", "sizeBb": 2.2 }` and `openSizes.sb` is `{ "label": "2.8x", "sizeBb": 2.8 }`. `ante` is 1, `antePostedBy` is BB, `evUnit` is sb, `source` is "MonkerGuy 8max BB-ante MTT pack".

`GET /api/ranges/nlmtt8/node?stack=40&history=UTG_Fold_UTG1_Fold_LJ_Fold_HJ_Fold_CO_Fold_BTN`

Actor BTN. Fold EVs are 0. Open AA is frequency 1 at 23.27sb. `sizeBb` on Open is null until the bundle is re-imported with code 40034, which stores 2.19bb.

Local re-import (does not upload to Blob):

`NLMTT8_DIR="/path/to/Hold'em/8-way" npx tsx scripts/reimport-nlmtt8-sizes.ts`

MCP: `get_node` and `get_config` return these same bodies. `get_node` `full: true` is `full=1`.
