PLO Strategy Endpoint
This page documents how to query the strategy API for PLO (Pot Limit Omaha) through the POST /lookup endpoint. It is self-contained: it covers every request and response field, with a full request and response example.
Endpoint
Endpoint: POST /lookup
All requests require an authorization token in the Authorization header:
curl --location '<glue-URL>/lookup' \
--header 'Content-Type: application/json' \
--header 'Authorization: <Auth-TOKEN>' \
--data '{ ... }'The actual <glue-URL> for your environment (dev / stg / prod) and the <Auth-TOKEN> are provided separately.
Possible HTTP error statuses: 400 (bad request), 401 (missing Authorization header), 403 (invalid token), 422 (validation error), 502 (upstream error). Note that some engine-level failures are reported inside an HTTP 200 body via the error / error_code response fields — clients should always check them (see Response Fields).
Selecting the PLO Variant
The variant is selected with hand.game_type — the hole-card count is part of the game type:
game_type | Variant | Hero hole cards | Players |
|---|---|---|---|
"plo4" | Pot Limit Omaha, 4 cards | 4 | 2–8 |
"plo5" | Pot Limit Omaha, 5 cards | 5 | 2–6 |
"plo6" | Pot Limit Omaha, 6 cards | 6 | 2–5 |
These are the only accepted values — there is no bare "plo". hand.game_mode_code is always "normal": PLO is served as a cash game only.
PLO specifics:
- Full 52-card deck; standard Omaha rules (exactly 2 hole cards + 3 board cards make a hand).
- Pot-limit betting — the action menu never exceeds a pot-sized raise.
- 2-blind and 3-blind (straddle) structures and antes are supported, as is rake configuration via
rake_ratio_pct/rake_cap_bb.
Card Notation
Cards use a two-character notation: uppercase rank (A K Q J T 9 8 7 6 5 4 3 2) followed by lowercase suit (c d h s). A hand is the concatenation of its cards with no separators. Board cards are sent as entries of the action list (see the Action object below), also concatenated: "Tc6hKc" for a flop, "Qd" for a turn or river.
game_type | hole_cards example | Length |
|---|---|---|
plo4 | "9hAhJs9c" | 8 chars (4 cards) |
plo5 | "7cQs9h9dTc" | 10 chars (5 cards) |
plo6 | "8cQhTs2d9h4h" | 12 chars (6 cards) |
A hole-card string whose length does not match the game type is rejected with error E106 (e.g. sending 2 cards with game_type: "plo4"). Only the hero — the player the strategy is requested for — carries hole_cards; every other player must omit the field.
Request Format
{
"request_id": "unique-request-id",
"hand": { <Hand-object> },
"protocol_ver": "v2"
}Request Fields
Top Level
| Field | Type | Required | Description |
|---|---|---|---|
request_id | string | No | Caller-chosen identifier, echoed back inside the response's request echo. Use it to correlate requests; use hand.gameuuid to track whole hands (a hand spans multiple requests). |
hand | Hand-object | Yes | The full game state — players, blinds, seats and the action history. See the Hand object below. |
protocol_ver | string | Yes | Protocol version. Use "v2" for PLO. |
Hand Object
"hand": {
"gameuuid": "<hand-unique-identifier>",
"game_type": "<plo4 | plo5 | plo6>",
"game_mode_code": "normal",
"players": [ <Player-object> ],
"actions": { "entries": [ <Action-object> ] },
"big_blind": <big-blind-size>,
"small_blind": <small-blind-size>,
"ante": <ante-size>,
"dealer_seat": <dealer-player-seat>,
"sb_seat": <small-blind-player-seat>,
"bb_seat": <big-blind-player-seat>,
"straddle_seat": <straddle-seat-or-minus-1>
}| Field | Type | Required | Description |
|---|---|---|---|
gameuuid | string | Yes | Unique hand identifier for tracking. Any value is accepted. |
game_type | string | Yes | "plo4", "plo5" or "plo6" (see Selecting the PLO Variant). |
game_mode_code | string | Yes | Always "normal" for PLO. |
players | Player-objects | Yes | List of player objects — see below. |
actions | Action-objects | Yes | Chronological list of everything that happened in the hand so far, including board cards — see below. |
big_blind | integer | Yes | Big blind value in actual chips. |
small_blind | integer | No | Small blind in chips. When absent it is derived from the big blind. |
ante | integer | Yes | Ante per player in chips; 0 when the game has no ante. |
dealer_seat | integer | Yes | Dealer (button) seat. |
sb_seat | integer | Yes | Small blind seat. |
bb_seat | integer | Yes | Big blind seat. |
straddle_seat | integer | Yes | Straddle seat for 3-blind games; -1 when there is no straddle. |
rake_ratio_pct | double | No | Rake percentage. Explicit 0 means rake-free; absent applies the server default. |
rake_cap_bb | double | No | Rake cap in big blinds. Explicit 0 means uncapped. |
roomid | string | No | Caller room/table identifier, passed through. |
Player Object
"players": [
{
"seat_no": <player-seat-number>,
"stack": <chip-amount>,
"hole_cards": "<hero-cards-or-field-removed>"
}
]| Field | Type | Required | Description |
|---|---|---|---|
seat_no | integer | Yes | The player's seat number. Must match the seat indexes used in the hand fields (sb_seat, bb_seat, dealer_seat, straddle_seat) and in action entries. |
stack | integer | Yes | The player's chip count at the start of the hand, before blinds and antes. |
hole_cards | string | Yes (hero only) | The hero's hole cards in card notation — 4, 5 or 6 cards as dictated by game_type (e.g. "9hAhJs9c" for plo4). Exactly one player must carry this field; remove it for all other players. |
uid | string | No | Caller player identifier, passed through. |
Action Object
"actions": {
"entries": [
{ "action": "raise", "amount": 1000, "seat_no": 3 },
{ "action": "call", "seat_no": 4 },
{ "action": "Tc6hKc" },
{ "action": "check", "seat_no": 3 }
]
}entries is a single flat chronological list mixing player actions and board deals:
| Field | Type | Required | Description |
|---|---|---|---|
action | string | Yes | Either an action verb — fold, check, call, bet, raise, allin — or, for a board deal, the dealt cards concatenated (e.g. "Tc6hKc" for the flop, "Qd" for turn/river). |
seat_no | integer | Yes for player actions | Seat of the acting player. Removed on board-deal entries — that is how the two entry kinds are distinguished. |
amount | integer | For bet/raise/allin | Chips added by the player in this action on the current street. |
The street is implied by the position of the board-deal entries — there is no explicit street field. An empty list ("entries": []) means it is preflop and the first player is to act. Blinds, antes and straddles are not listed as entries; they are derived from the hand fields.
Response Format
{
"request": { <echo-of-the-request> },
"strategy": {
"actions": [ <StrategyAction> ],
"decision_action_seq": { <ActionList> },
"suggested_action": { <ActionDetails> }
},
"supportScore": <0-to-1>,
"solution_information": { <Solution-information-object> },
"strategy_profile": null,
"warnings": [],
"flags": []
}Response Fields
Top Level
| Field | Type | Description |
|---|---|---|
request | object | Echo of the request. |
strategy | Strategy-object | The strategy solution at the decision point. |
supportScore | double | Model confidence that it covers this spot, 0 to 1. 0.5 is the neutral default. |
solution_information | object | Diagnostics, including situation_key — how the engine classified the spot (game_type, street, flop, players_bin, bb_depth). |
strategy_profile | object | null — strategy profiles are not applied to PLO. |
warnings | string[] | Human-readable warnings. |
flags | string[] | Machine-readable processing flags. |
error / error_code | string | Present on engine soft failures even with HTTP 200 — e.g. "error_code": "E500" with an explanatory error text. Always check these before consuming strategy. |
Strategy Object
| Field | Type | Description |
|---|---|---|
actions | StrategyAction[] | All candidate actions for the hero with their probabilities and EVs. |
decision_action_seq | Action-objects | The action line of the decision point (echo of the request's entries, normalized). |
suggested_action | ActionDetails | The single action the engine recommends taking — the sampled/most likely action of the strategy. |
Strategy Action Object
| Field | Type | Description |
|---|---|---|
action | ActionDetails | The concrete action: action verb (fold, check, call, bet, raise, allin), amount — the raise-to / bet chip amount (null for fold/check/call), pot_size — the bet as a ratio of the pot if called, and seat_no of the hero. |
action_name | string | Compact action code: f, c, x, b<pct>, r<pct>, allin<pct> where the number is the pot percentage — "b75" is a bet of 75% pot, "b100" a pot-sized bet. |
action_seq | Action-objects | The full action line ending in this candidate action (the request's entries plus this action appended). |
hand_strategy | float | Probability the strategy assigns to this action for the hero hand, 0 to 1. |
hand_ev | float | Expected value of taking this action, in big blinds. |
Request Example
5-handed PLO4 with a straddle (seat 2) and a 50-chip ante. Preflop, seat 3 raised to 1,000, the hero (seat 4, on the button, holding 9hAhJs9c) called, everyone else folded. The flop is Tc6hKc; seat 3 checked and the hero is to act:
curl --location '<glue-URL>/lookup' \
--header 'Content-Type: application/json' \
--header 'Authorization: <Auth-TOKEN>' \
--data '{
"request_id": "plo4-flop-demo",
"hand": {
"gameuuid": "plo4_5p_game",
"game_type": "plo4",
"game_mode_code": "normal",
"players": [
{ "seat_no": 0, "stack": 5000 },
{ "seat_no": 1, "stack": 31900 },
{ "seat_no": 2, "stack": 19000 },
{ "seat_no": 3, "stack": 131620 },
{ "seat_no": 4, "stack": 41370, "hole_cards": "9hAhJs9c" }
],
"big_blind": 100,
"ante": 50,
"dealer_seat": 4,
"sb_seat": 0,
"bb_seat": 1,
"straddle_seat": 2,
"actions": {
"entries": [
{ "action": "raise", "amount": 1000, "seat_no": 3 },
{ "action": "call", "seat_no": 4 },
{ "action": "fold", "seat_no": 0 },
{ "action": "fold", "seat_no": 1 },
{ "action": "fold", "seat_no": 2 },
{ "action": "Tc6hKc" },
{ "action": "check", "seat_no": 3 }
]
}
},
"protocol_ver": "v2"
}'Reading the request: hole_cards is 8 characters — exactly 4 cards, matching "plo4". The straddle is declared with straddle_seat: 2 (making this a 3-blind game); blinds, straddle and antes are not repeated in the action entries — the first entry is already the first voluntary action (seat 3's raise). The flop deal "Tc6hKc" carries no seat_no, and seat 3's check after it leaves the hero to act.
Response Example
{
"request": { ... },
"strategy": {
"actions": [
{
"action": { "action": "fold", "amount": null, "pot_size": null, "seat_no": 4 },
"action_name": "f",
"action_seq": { ... },
"hand_strategy": 0.0,
"hand_ev": -10.5
},
{
"action": { "action": "check", "amount": null, "pot_size": null, "seat_no": 4 },
"action_name": "x",
"action_seq": {
"entries": [
{ "action": "raise", "amount": 1000, "seat_no": 3 },
{ "action": "call", "seat_no": 4 },
{ "action": "fold", "seat_no": 0 },
{ "action": "fold", "seat_no": 1 },
{ "action": "fold", "seat_no": 2 },
{ "action": "Tc6hKc" },
{ "action": "check", "seat_no": 3 },
{ "action": "check", "seat_no": 4 }
]
},
"hand_strategy": 0.7657038121459327,
"hand_ev": 13.478801727294922
},
{
"action": { "action": "bet", "amount": 850, "pot_size": 0.32692307233810425, "seat_no": 4 },
"action_name": "b33",
"action_seq": { ... },
"hand_strategy": 0.0013135409935110887,
"hand_ev": 13.478801727294922
},
{
"action": { "action": "bet", "amount": 1300, "pot_size": 0.5, "seat_no": 4 },
"action_name": "b50",
"action_seq": { ... },
"hand_strategy": 0.231213544309503,
"hand_ev": 13.478801727294922
},
{
"action": { "action": "bet", "amount": 1950, "pot_size": 0.75, "seat_no": 4 },
"action_name": "b75",
"action_seq": { ... },
"hand_strategy": 0.0017282659289257333,
"hand_ev": 13.478801727294922
},
{
"action": { "action": "bet", "amount": 2600, "pot_size": 1.0, "seat_no": 4 },
"action_name": "b100",
"action_seq": { ... },
"hand_strategy": 0.00004083662212741418,
"hand_ev": 13.478801727294922
}
],
"decision_action_seq": { "entries": [ ...same-entries-as-the-request... ] },
"suggested_action": {
"action": "check",
"amount": null,
"pot_size": null,
"seat_no": 4
}
},
"supportScore": 0.7780449986457824,
"solution_information": {
"situation_key": {
"game_type": "plo4",
"street": "flop",
"flop": "Tc6hKc",
"players_bin": "5",
"bb_depth": 50.0
}
},
"strategy_profile": null,
"warnings": [],
"flags": []
}({ ... } marks elided parts: the full request echo, the repeated action_seq objects, and the diagnostic members of solution_information.)
How to read it: the suggested_action is a check, which the strategy plays 76.6% of the time (hand_strategy: 0.7657); the main alternative is a half-pot bet of 1,300 chips ("b50", 23.1%). The remaining bet sizes carry negligible probability. All bet sizes stop at pot_size: 1.0 — pot-limit betting means the menu never exceeds a pot-sized raise. Folding when checking is free would be a blunder: probability 0 and an EV of −10.5 big blinds, far below the +13.48 bb of the played actions.
Limitations
- PLO is served as a cash game only —
game_mode_codeis always"normal". - Player counts: PLO4 2–8; PLO5 2–6; PLO6 2–5.
- Hole-card count must match
game_type— 4 / 5 / 6 cards; mismatches fail withE106. - Soft failures can arrive with HTTP 200 — check
error/error_codein the response body. - Use
protocol_ver: "v2"for PLO requests.