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
  • 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
  • GET /v1/strategy
  • GET /v1/health
  • Errors
Need a key?

Request a trial key on Discord.

  1. Docs
  2. Outputs

Outputs

Response body, headers, and behavior of /v1/strategy.

Brief response (verbosity=brief, default)

Returns the action mix for the combo at the requested node, plus the two context gates. Actions are ordered fold → check → call → bet → raise, then by size, and their frequencies sum to exactly 1.

200Response · 200
{
  "data": {
    "context": {
      "round_start_pot_bb": 5.5,
      "to_act": "ip"
    },
    "actions": [
      { "type": "check", "node_token": "x",     "frequency": 0 },
      { "type": "bet",   "node_token": "b1.82", "frequency": 0.01, "amount": 1.82, "pot_pct": 0.33 },
      { "type": "bet",   "node_token": "b2.75", "frequency": 0.99, "amount": 2.75, "pot_pct": 0.5 }
    ]
  },
  "node_id": "root-x",
  "source": "custom"
}
  • data.actions[] — the mix the solver wants for THIS combo at THIS node.
  • type — fold | check | call | bet | raise.
  • node_token — the action's key in the tree, verbatim (x, c, f, b1.82, r5.46). node_id + "-" + node_token is the child node.
  • frequency — fraction in [0, 1]. Frequencies are normalised to sum to exactly 1 (4 decimals). Zero-frequency actions are listed, even at 0 for every combo, and so is every action in the file: the strategy response never hides an option you can play.
  • amount — size in BB. Omitted for fold and check. For raise, ABSOLUTE raise-to. For call, incremental cost.
  • pot_pct — only on bet. Fraction of the pot at the node (0.33 = one third). Present in both verbosities.
  • data.context — round_start_pot_bb (pot when the current street opened) and to_act (ip or oop). The two live safety gates.
  • node_id — replayable as node on a follow-up request. Echoed in caller's basis.
  • source — custom (your account's strategy folder) or v1 (shared tree). Top level, next to node_id.

Walking the tree with node_token

Every action carries node_token, its key in the tree — in both verbosities, and including the actions the solver never takes. The next node id is a concatenation:

next_node_id = node_id + "-" + action.node_token

"root-x" + "-" + "b1.82"   →  "root-x-b1.82"

Do not rebuild the token from type and amount: they can disagree on purpose. An all-in facing a bet is a raise semantically, but the tree keys it b<total>.

// at node "root-x-b1.82-r5.46-r12.01" the all-in over the raise is keyed:
{ "type": "raise", "node_token": "b97.5", "amount": 97.5 }

"root-x-b1.82-r5.46-r12.01" + "-" + "b97.5"  →  "…-r12.01-b97.5"     ✅
"root-x-b1.82-r5.46-r12.01" + "-" + "r97.5"  →  404 not_found        ❌

404 with a cause

Composing a token that is not in the tree answers 404 — and the body tells you why, with the legal keys of the parent so you can snap back on tree:

404Response · 404
{
  "error": {
    "code": "not_found",
    "message": "The requested strategy node was not found.",
    "reason": "off_tree_sizing",
    "parent_node_id": "root-x-b1.82-r5.46-r12.01",
    "legal_tokens": ["f", "c", "r26.42", "b97.5"]
  }
}

Full response (verbosity=full)

Expands data.context and adds raise_ratio on raises. Same quota cost as brief. The response below is live data — BB facing the 1.82 BB bet:

200Response · 200
{
  "data": {
    "context": {
      "street": "flop",
      "hand": "QhJc",
      "board": ["2c", "2d", "2h"],
      "round_start_pot_bb": 5.5,
      "stacks_bb": { "ip": 95.68, "oop": 97.5 },
      "spr": 17.4,
      "positions": { "ip": "btn", "oop": "bb" },
      "to_act": "oop"
    },
    "actions": [
      { "type": "fold",  "node_token": "f",     "frequency": 0 },
      { "type": "call",  "node_token": "c",     "frequency": 1, "amount": 1.82 },
      { "type": "raise", "node_token": "r5.46", "frequency": 0, "amount": 5.46, "raise_ratio": 3 }
    ]
  },
  "node_id": "root-x-b1.82",
  "source": "custom"
}

context fields

  • street — flop | turn | river.
  • hand — the combo exactly as you sent it — same casing, same rank order. The server canonicalises internally for the lookup but never rewrites the echo.
  • board — cards in the suit basis the caller submitted. Length 3, 4 or 5 by street.
  • round_start_pot_bb — pot in BB at the moment the current betting street opened. Frozen mid-street. SPR anchor.
  • stacks_bb — effective stacks for in-position and out-of-position players, in BB.
  • spr — min(stacks_bb.ip, stacks_bb.oop) / round_start_pot_bb.
  • positions — names of IP and OOP roles (e.g. btn / bb).
  • to_act — ip or oop — who acts at the decision. Derived from the spot and the node id, never defaulted.

Action fields

  • raise_ratio — only on raise. raise_to / facing_bet, rounded to 1 decimal.

source

Both verbosities carry source at the top level, next to node_id: custom means your account's dedicated strategy folder served the reply (the normal case), v1 means the shared default tree. If you ever see an unexpected v1, tell support — it means your folder routing changed.

Audit block (audit=true)

Add audit=true to any /v1/strategy request (brief, full or discovery) and the response carries an audit object that identifies the decision for aggregation. On a 200 it sits at the top level, next to node_id. On 404 not_found and 422 combo_not_in_range it sits inside error, after reason, parent_node_id and legal_tokens, which keep their place. No other error carries it. Nothing else in the response changes, and the block adds no extra work to the request.

200Response · 200
{
  "data": {
    "context": {
      "round_start_pot_bb": 5.5,
      "to_act": "ip"
    },
    "actions": [
      { "type": "check", "node_token": "x",     "frequency": 0 },
      { "type": "bet",   "node_token": "b1.82", "frequency": 0.01, "amount": 1.82, "pot_pct": 0.33 },
      { "type": "bet",   "node_token": "b2.75", "frequency": 0.99, "amount": 2.75, "pot_pct": 0.5 }
    ]
  },
  "node_id": "root-x",
  "source": "custom",
  "audit": {
    "request_id": "req_9b2f4c1e-6a0d-4f2b-9c3e-1d7a5b8e2f40",
    "spot_id": "gg/10_50/regs/srp/btn_bb",
    "tree_family": "srp",
    "preflop_lineage": "srp",
    "hero_position": "btn",
    "villain_position": "bb",
    "player_count_at_node": 2,
    "street": "flop",
    "node_path": "AsKhQd/root-x",
    "coverage": "covered",
    "coverage_reason": null,
    "strategy_revision": "2026-07-26",
    "rake_model": "gg_5pct_8bb_v1"
  }
}
  • request_id — same value as the X-Request-ID header. Quote it when you report a decision to support.
  • spot_id — platform/stakes/playerType/situation/position, every segment exactly as you sent it.
  • tree_family — the situation you sent (srp, 3bet, iso, limped).
  • preflop_lineage — preflop line of the tree. Always equal to tree_family in this API.
  • hero_position — seat that acts at this node: the seat behind to_act (in btn_bb, root gives bb and root-x gives btn). On your own turn it is your seat; on a discovery request for a villain node it is the villain. Present on 404s too; null if the spot has no tree configuration.
  • villain_position — the other seat of the pair, the one not acting at this node.
  • player_count_at_node — players in the pot at this node. Always 2: every tree is heads-up.
  • street — flop, turn or river, read from the node id.
  • node_path — canonical flop + node id in the canonical suit basis. Your aggregation key, see below.
  • coverage — covered on a 200, not_found on 404 not_found, invalid on 422 combo_not_in_range.
  • coverage_reason — null when covered; the error reason when not_found; combo_not_in_range when invalid.
  • strategy_revision — date (UTC, YYYY-MM-DD) the trees of this spot were published. It changes when the spot is re-solved; null if the spot has no published trees.
  • rake_model — rake the trees were solved with. gg_5pct_8bb_v1 is 5% capped at 8 BB.

node_id vs node_path

node_id is in YOUR suit basis and is the value you send back as node. node_path is the same spot in the canonical basis: two requests for the same physical spot whose free suits you labelled differently return different node_id values but a single node_path. Group decisions by spot_id + node_path, and never send node_path back as node.

On 404 not_found and 422 combo_not_in_range the same block travels inside error, as the last key:

422Response · 422
{
  "error": {
    "code": "combo_not_in_range",
    "message": "The requested hand is not part of the player range at this node.",
    "audit": {
      "request_id": "req_4e1a7c20-3b9d-4f6e-8a15-c2d9e0b7f341",
      "spot_id": "gg/10_50/regs/srp/btn_bb",
      "tree_family": "srp",
      "preflop_lineage": "srp",
      "hero_position": "btn",
      "villain_position": "bb",
      "player_count_at_node": 2,
      "street": "flop",
      "node_path": "AsKhQd/root-x",
      "coverage": "invalid",
      "coverage_reason": "combo_not_in_range",
      "strategy_revision": "2026-07-26",
      "rake_model": "gg_5pct_8bb_v1"
    }
  }
}

500 spot_not_configured

Pot, stacks and to_act are all derived from the spot config and the node id. When the spot is not configured on our side the request fails with this error instead of returning defaulted values, so a 200 always carries a to_act you can gate on. It is our configuration gap, not yours: retrying will not help, report it with the X-Request-ID.

500Response · 500
{
  "error": {
    "code": "spot_not_configured",
    "message": "This spot is not configured on the server, so the node context cannot be computed. Contact support with the request id."
  }
}
Previous
node id
Next
Discovery mode