GTOkiller
  • Home
  • Solver
  • MDA
  • Pricing
  • Invite a Friend
  • Blog
On this site
GTOKiller API
v1
Getting started
  • Quickstart
  • Authentication
Core concepts
  • Inputs
  • node id
  • Outputs
  • Discovery mode
  • Stateless mode
Reference
  • POST /v1/strategy
  • GET /v1/strategy
  • GET /v1/health
  • Errors
Need a key?

Request a trial key on Discord.

GTOKiller API
v1
Getting started
  • Quickstart
  • Authentication
Core concepts
  • Inputs
  • node id
  • Outputs
  • Discovery mode
  • Stateless mode
Reference
  • POST /v1/strategy
  • GET /v1/strategy
  • GET /v1/health
  • Errors
Need a key?

Request a trial key on Discord.

  1. Docs
  2. Reference
  3. POST /v1/strategy

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.

POST/v1/strategyX-API-Key
The recommended way to ask for a decision
GET /v1/strategy keeps working exactly as before. New integrations should use this POST: you no longer have to fit every spot to the tree yourself, and multiway pots, limped pots, odd sizes and preflop decisions get an answer instead of a 404.

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.

ParameterTypeDescription
platformRequired
stringPool 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
string3 cards as 6 chars, any suit order (e.g. AsKcQd)
turn
stringTurn card, 2 chars
river
stringRiver card, 2 chars. Requires turn
handRequired
stringHero's combo, 4 chars. No card may repeat between hand and board
hero_seatRequired
int 0..9Hero's seat. Must be in players and still in the hand
initial_pot_bbRequired
number > 0Pot in BB when the flop opened
players[]Required
array 2..10Each 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..200Flop, 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
objectHow 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
stringTree coordinate, as in GET (srp, 3bet...). situation, position and node go together or not at all
position
stringTree pair <aggressor>_<caller> (e.g. btn_bb), as in GET
node
stringTree 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
booleantrue adds the audit block: who answered, why and which checks failed
hand_id
stringYour hand identifier for tracing, 1 to 128 chars. Not echoed
Amounts and order
In bet and raise, amount_bb is the TOTAL for the street (raise-to), never the increment. players goes in postflop acting order, first to act first: that order says who is in position.
Request: BB to act on the flop, BTN vs BB single-raised pot
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:

ParameterTypeDescription
players[]Required
array 2..10seat, position and stack_bb at the START of the hand (greater than 0). No in_hand: folds go in history
history[]Required
array 0..200Preflop 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
numberAnte 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.

See Discovery mode →

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.

200The tree answered (brief) · 200
{
  "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"
}
200The engine answered a multiway flop (full) · 200
{
  "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"
}
200The engine answered preflop (brief) · 200
{
  "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.

ReasonStatusMeaning
street_preflop
activePreflop body: the tree starts on the flop
no_node
activeNo tree coordinates in the body
spot_not_configured
activeThe situation and position pair is not served
not_heads_up
activeMore than two players in the hand, or a fold in history
preflop_missing
activeNo preflop summary
scenario_mismatch
activeThe preflop scenario is not the tree's situation
position_mismatch
activeThe two live seats are not the pair in position
aggressor_mismatch
activeThe preflop aggressor is not the first seat of the pair
street_mismatch
activeThe node street is not the board street
board_card_mismatch
activeA turn or river card in node is not the board card
line_mismatch
activenode and history differ in number, order or type of actions
allin_mismatch
activeAll-in on one side only (tree or table)
hero_not_to_act
activeThe hero is not the player to act at node
*_drift
observationpot_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
activeThe tree has no node for that line. Same meanings as the 404 reason
combo_not_in_range
activeThe node exists but your hand is not in the range there
malformed_node
activeThe tree node could not be read

Response headers

The same headers as GET /v1/strategy (X-Request-ID, rate limit, HSTS), plus:

HeaderValueDescription
X-Engine
gtokiller | fallbackWho answered. On decisions only, not on discovery
X-Fallback-Reason
stringOnly when X-Engine is fallback: why the tree did not answer (table above)
X-Cache
HIT | MISSTree answers: HIT when the node came from the in-memory cache. Engine answers: always MISS
Server-Timing
metricsThe 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.

See the Errors page →

Previous
Stateless mode
Next
GET /v1/strategy