On this site
POST /v1/strategy
Send the real table and get a decision: the solver tree answers when the spot fits it, the fallback engine answers otherwise.
Who answers
You send the whole hand: real stacks, real bet sizes, the preflop summary and, if you have them, the tree coordinates (situation, position, node). The API picks who decides:
- The solver tree (engine: gtokiller) when the spot really is the tree's spot: tree coordinates sent, heads-up, the preflop scenario, positions and aggressor match the tree pair, the node line matches your history (streets, board cards, number and type of actions, all-ins) and the hero is the player to act. data is exactly what GET /v1/strategy returns for the same coordinates
- The fallback engine (engine: fallback) in every other case: multiway, limped or 4-bet pots, no node, a line or size the tree does not hold, a hand outside the range, any preflop decision. It decides with your real table, not with the tree's fixed pot and stacks
Both answer with the tree shape (data.actions[] with frequencies and data.context) plus engine. The X-Engine and X-Fallback-Reason headers say who answered and why. The API never derives the node from your history: without node, the engine answers.
Three body shapes
The server picks the shape from the body itself:
- With flop: a postflop decision. Stacks when the flop opened, initial_pot_bb, the preflop summary, the flop, turn and river history and, optionally, the tree coordinates
- Without flop: a preflop decision. Stacks at the start of the hand and the preflop history without the blinds (they come from the sb and bb positions). Always answered by the engine
- With discovery: true: the structure of a tree node, no table and no hand. Same response as GET /v1/strategy?discovery=true and free of charge
Postflop body
JSON, at most 32 KiB. The schema is strict: an unknown key answers 422 validation_error.
| Parameter | Type | Description |
|---|---|---|
platformRequired | string | Pool or site identifier (e.g. gg). Same rules as in GET |
stakesRequired | string | <sb>_<bb> (e.g. 10_50). Must be in your stake allowlist or the request answers 403 stake_not_allowed |
player_type | "regs" | "fish" | regs (default) or fish. Note the snake case: the GET query parameter is playerType |
flopRequired | string | 3 cards as 6 chars, any suit order (e.g. AsKcQd) |
turn | string | Turn card, 2 chars |
river | string | River card, 2 chars. Requires turn |
handRequired | string | Hero's combo, 4 chars. No card may repeat between hand and board |
hero_seatRequired | int 0..9 | Hero's seat. Must be in players and still in the hand |
initial_pot_bbRequired | number > 0 | Pot in BB when the flop opened |
players[]Required | array 2..10 | Each player: seat (0 to 9), position as your room names it (utg, hj, co, btn, sb, bb...), stack_bb when the flop opened and an optional in_hand (false for a player no longer in the hand). Distinct seats, at least 2 in the hand, listed in postflop acting order |
history[]Required | array 0..200 | Flop, turn and river actions so far, in order: street, seat, kind (check, call, fold, bet, raise) and amount_bb on bet and raise. Empty at the first flop decision |
preflop | object | How the pot got to the flop: aggressor_seat, raises, callers, limpers, scenario (limped, iso, srp, 3bet, 4bet, 5bet), open_size_bb, last_raise_to_bb, squeeze. The tree needs it to answer; without scenario it is derived from raises and limpers |
situation | string | Tree coordinate, as in GET (srp, 3bet...). situation, position and node go together or not at all |
position | string | Tree pair <aggressor>_<caller> (e.g. btn_bb), as in GET |
node | string | Tree node for this decision, in the suit basis of your flop and already fitted to the tree sizes. Max 200 chars |
verbosity | "brief" | "full" | brief (default) or full. Same meaning as in GET |
audit | boolean | true adds the audit block: who answered, why and which checks failed |
hand_id | string | Your hand identifier for tracing, 1 to 128 chars. Not echoed |
curl -X POST 'https://api.gtokiller.com/v1/strategy' \
-H "X-API-Key: $GTOKILLER_KEY" \
-H 'Content-Type: application/json' \
-d '{
"platform": "gg",
"stakes": "10_50",
"situation": "srp",
"position": "btn_bb",
"node": "root",
"flop": "AsKcQd",
"hand": "QhJc",
"hero_seat": 1,
"initial_pot_bb": 5.5,
"players": [
{ "seat": 1, "position": "bb", "stack_bb": 97.5 },
{ "seat": 6, "position": "btn", "stack_bb": 97.5 }
],
"history": [],
"preflop": { "aggressor_seat": 6, "raises": 1 }
}'Preflop body
Same as the postflop body without flop, turn, river, initial_pot_bb, preflop, situation, position and node. Always answered by the engine. The fields that change:
| Parameter | Type | Description |
|---|---|---|
players[]Required | array 2..10 | seat, position and stack_bb at the START of the hand (greater than 0). No in_hand: folds go in history |
history[]Required | array 0..200 | Preflop actions so far, in order and without the blinds: seat, kind (fold, check, call, raise) and amount_bb on raise, as the raise-to total. street is optional and only accepts preflop |
ante_bb | number | Ante per player in BB, if the table plays with antes |
Discovery body
discovery: true plus platform, stakes, player_type, situation, position, flop, turn, river and node (no table, no hand). The response is the same flat body as GET /v1/strategy?discovery=true. Free: it does not count against the monthly quota or the hard cap; the per-second limit still applies.
Response
Always the tree shape. Postflop, the engine returns ONE action at frequency 1; preflop it returns its mix. With verbosity full and three or more players in the hand, the engine context carries format: multiway and players instead of stacks_bb and positions.
{
"data": {
"context": { "round_start_pot_bb": 5.5, "to_act": "oop" },
"actions": [
{ "type": "check", "node_token": "x", "frequency": 0.62 },
{ "type": "bet", "node_token": "b1.82", "frequency": 0.38, "amount": 1.82, "pot_pct": 0.33 }
]
},
"node_id": "root",
"source": "custom",
"engine": "gtokiller"
}{
"data": {
"context": {
"street": "flop",
"hand": "AhKd",
"board": ["9h", "7d", "2c"],
"round_start_pot_bb": 8,
"spr": 12.19,
"to_act": "oop",
"format": "multiway",
"players_in_hand": 3,
"hero_position": "co",
"players": [
{ "seat": 1, "position": "bb", "stack_bb": 97.5 },
{ "seat": 3, "position": "co", "stack_bb": 97.5 },
{ "seat": 6, "position": "btn", "stack_bb": 120 }
]
},
"actions": [
{ "type": "bet", "node_token": "b2.64", "frequency": 1, "amount": 2.64, "pot_pct": 0.33 }
]
},
"engine": "fallback",
"engine_version": "abc1234"
}{
"data": {
"context": { "street": "preflop", "pot_bb": 4, "to_call_bb": 1.5 },
"actions": [
{ "type": "fold", "node_token": "f", "frequency": 0.25 },
{ "type": "call", "node_token": "c", "frequency": 0.5, "amount": 1.5 },
{ "type": "raise", "node_token": "r11", "frequency": 0.25, "amount": 11 }
]
},
"engine": "fallback",
"engine_version": "abc1234"
}- engine: gtokiller (solver tree) or fallback (engine). Same value as the X-Engine header. Absent on discovery
- data.actions[]: same fields as in GET (type, node_token, frequency, amount, pot_pct, and raise_ratio in full). The engine keys an all-in raise over a bet as b<total>, like the tree, so node_id + "-" + node_token stays a valid child
- data.context: brief carries round_start_pot_bb and to_act. Full carries the GET context heads-up, the multiway context with three or more players, and street: preflop, pot_bb and to_call_bb preflop
- node_id: always on tree answers. On engine answers it echoes your node, except when node is missing or does not match your history (no_node, line_mismatch, street_mismatch, board_card_mismatch, allin_mismatch, hero_not_to_act): then it is absent, so you never append a token to a node that is not your hand
- source: tree answers only (custom or v1). engine_version: engine answers only; quote it with X-Request-ID when you report a decision
- audit (with audit: true): request_id, coverage (covered or fallback), coverage_reason (null when the tree answered), fallback_triggers and shadow_triggers, each a list of { rule, detail } with the real and tree figures compared. Ignore keys you do not know: the block can grow
Fallback reasons
When the engine answers, X-Fallback-Reason and audit.coverage_reason carry the reason. The list can grow: treat an unknown value as a generic engine decision. Checks in observation are measured and reported in audit.shadow_triggers but never send the request to the engine.
| Reason | Status | Meaning |
|---|---|---|
street_preflop | active | Preflop body: the tree starts on the flop |
no_node | active | No tree coordinates in the body |
spot_not_configured | active | The situation and position pair is not served |
not_heads_up | active | More than two players in the hand, or a fold in history |
preflop_missing | active | No preflop summary |
scenario_mismatch | active | The preflop scenario is not the tree's situation |
position_mismatch | active | The two live seats are not the pair in position |
aggressor_mismatch | active | The preflop aggressor is not the first seat of the pair |
street_mismatch | active | The node street is not the board street |
board_card_mismatch | active | A turn or river card in node is not the board card |
line_mismatch | active | node and history differ in number, order or type of actions |
allin_mismatch | active | All-in on one side only (tree or table) |
hero_not_to_act | active | The hero is not the player to act at node |
*_drift | observation | pot_drift, stack_drift, size_drift, pot_at_node_drift: real pot, effective stack, a bet size or the pot at the node too far from the tree's |
flop_not_covered, line_not_in_tree, off_tree_sizing, illegal_action, unknown | active | The tree has no node for that line. Same meanings as the 404 reason |
combo_not_in_range | active | The node exists but your hand is not in the range there |
malformed_node | active | The tree node could not be read |
Response headers
The same headers as GET /v1/strategy (X-Request-ID, rate limit, HSTS), plus:
| Header | Value | Description |
|---|---|---|
X-Engine | gtokiller | fallback | Who answered. On decisions only, not on discovery |
X-Fallback-Reason | string | Only when X-Engine is fallback: why the tree did not answer (table above) |
X-Cache | HIT | MISS | Tree answers: HIT when the node came from the in-memory cache. Engine answers: always MISS |
Server-Timing | metrics | The GET stages plus tree_match (the fit checks) and fallback (the engine call). 200 responses only |
Billing and quota
One call per 200, whoever decided. 422 no_decision and 503 fallback_unavailable are not billed. Discovery is free. Rate limits are the same as for GET.
Errors
A postflop body never gets a 404 for a node the tree lacks: the engine answers instead. The only 404 is a line with nothing left to decide: reason terminal (the hand is over) or street_closed (append the next card). Discovery keeps the full 404 taxonomy. Specific to POST: 422 no_decision (the engine replayed your table and found no decision: impossible line, repeated card or no legal action for the hero; fix the state you send) and 503 fallback_unavailable (the tree could not answer and the engine did not respond; retry or use a local fallback). Plus the usual 401, 403, 422 validation_error and 429.