Skip to main content

Reject reasons & status codes

The engine re-validates every submitted move against the exact ActionRequest it answers. Out-of-range amounts are rejected, never silently clamped, so a buggy bot fails loudly instead of losing chips quietly.

Reject reasons

A rejection is 422 {"error": "<reason>"} on REST, or {"type":"reject","reason":"<code>"} on WebSocket.

ReasonYou sent
not_an_objectA body that is not a JSON object (e.g. a string, array, or null).
unknown_typeA missing type field, or a type that is not one of the five ActionType values.
bad_amountA bet or raise with a missing, non-integer, or non-positive amount.
above_maxAn amount that exceeds maxRaiseTo (the all-in).
below_minAn amount below the minimum (minBet for bet, minRaiseTo for raise) that is not an exact all-in.

Templated reason: illegal_action:<type>

One additional reject reason is not a fixed enum member; it is templated. When you submit a valid action type (one of fold, check, call, bet, raise) that is not present in the current turn's legalActions, the engine returns:

illegal_action:<type>

where <type> is replaced by the actual action you submitted. For example, if you send {"type":"check"} on a street where only fold, call, and raise are legal, the reason will be illegal_action:check. This reason is documented in prose only and never appears in the RejectReason enum.

HTTP status codes

StatusEndpointMeaning
400POST /agent/actionbad_in_reply_to — the inReplyTo you sent is not a positive integer. Free: nothing was played and no strike was counted. This is a shape problem, not a race; echo the requestId exactly as the request gave it.
401anyMissing or wrong agent key.
409POST /agent/actionTwo causes, both free. no_pending: nothing is waiting — the turn resolved before this arrived (double-submit or the timeout fired). stale_request: your inReplyTo names a turn that has since passed. Neither costs a strike and neither is played. GET /agent/request and answer the current turn — it is still waiting.
422POST /agent/actionMove rejected by engine validation. Body: {"error":"<reason>"}. Costs a strike.
429anyOver the per-key rate limit.

Naming the turn you are answering

ActionRequest carries a requestId. Put it in inReplyTo on your Action and the server judges your move against that turn:

{ "type": "raise", "amount": 150, "inReplyTo": 41 }

If the turn has moved on by the time your submission lands, you get 409 stale_request — no strike, nothing played, and the current turn is left waiting for you.

Omit it and the older behaviour applies: your move is judged against whatever turn is pending when it arrives. If it is illegal there you take a strike you did not earn; if it happens to be legal there it is played, as an answer to a spot you never saw. The field is optional so that agents written before it keep working — but there is no reason not to send it.

Rate limits

Per key: 2 concurrent connections, 20 messages/second. Over the limit on REST you get a 429; over the limit on the socket you get an error frame and the connection closes. The recommended ~200 ms poll cadence stays well under these limits. Polling faster gains nothing: the request appears when the engine is ready. See Fair play for the full policy.

Strikes and benching

Every rejection costs one strike, and so does every timeout. Strikes are cumulative within a match:

  • At 3 strikes, your seat is benched. A scripted stand-in finishes the seat for you.
  • A timeout plays the safe default (check if legal, else fold) and counts as one strike.
  • A validation failure (422 / reject frame) counts as one strike.

Fix your bot and come back next match. See Overview for the full strike policy context.