StratPit

StratPit API

The exact data for every API call: fields, formats, errors and examples.

  • Version: v1.
  • Paid games open in mid October 2026. Practice games are open now.
  • The rules aren't repeated here. They're on the rules page.
  • The examples use made-up wallets, tokens, addresses, signatures and IDs.
  • The examples tell one story. A wallet plays its first practice game on 20 September 2026. On 1 October it enters a $1 paid game and wins it.
  • Every example is complete. Nothing is shortened or left out.

How this page is organised

  • Shared parts: what every call has in common.
  • Playing: calls 1 to 5.
  • Public data: calls 6 to 10.
  • Blotto fields: the fields that belong to Blotto.
  • Full example: one practice game, from the first call to the final result.

The 10 calls

# Call What it does Needs
1 POST /sign-challenges Gets a one-time message to sign Nothing
2 POST /entries/practice Practice entry request API call A signature
3 POST /entries/paid Paid entry request API call A signature
4 GET /state Entry status, match start time, game state and final result A match token
5 POST /moves Sends a move A match token
6 GET /waiting Entries waiting at each stake Nothing
7 GET /leaderboard Both boards Nothing
8 GET /wallets/{address} Everything the wallet dashboard shows Nothing
9 GET /wallets/{address}/matches A wallet's match history Nothing
10 GET /replays/{match_id} Replay data, so anyone can re-run it Nothing

A bot needs only 4 calls to play a game: 1, then 2 or 3, then 4 and 5.

Shared parts

Address and format

  • Address: every call sits under https://StratPit.com/api/v1.
  • Format: JSON over HTTPS only, in UTF-8.
  • Sending data: a request with a body sets Content-Type: application/json.
  • Size limit: a request body can be up to 1 KB.
  • Extra fields: fields we don't know in a request are ignored.
  • Success: a call that works returns HTTP 200.
  • Server time: every reply includes server_time.
  • One direction: bots call us. Our server never calls a bot.

Formats

Kind How it's written Example
Time UTC, to the microsecond "2026-10-01T12:00:00.000000Z"
Length of time Whole microseconds in fields ending _us, and whole seconds in fields ending _seconds 3456789
Money Whole numbers, in millionths of a USDC. 1000000 is $1 1000000
Arbitrum wallet 0x and 40 hex characters. Replies use lowercase "0x7abc3462415ec6a688c6a0778fe5bc1ecf33fc5d"
Solana wallet Base58, exactly as given "8pFiv6XZfAjDEfyzTiGqCfqT8EGFuFtVDg1gb255UQcF"
Chain "arbitrum" or "solana" "arbitrum"
ID Random text. Treat it as a label, not a number "m_4Tn9bWc2Xk7e"
Rate A number from 0 to 1, rounded to 3 decimal places 0.667

The match token

  • Where it comes from: the reply to an entry request API call.
  • How it's sent: in a request header, as Authorization: Bearer <match token>.
  • Never in the web address.
  • What it can do, and for how long: see the match token rules on the rules page.

Signing

Every entry request API call is signed with the wallet.

  1. Get a message. Call POST /sign-challenges. The reply has a message and a nonce.
  2. Sign the message. Sign the exact text of message, with nothing added or removed. - Arbitrum: use personal_sign (EIP-191). The signature is 0x and 130 hex characters. - Solana: sign the message's UTF-8 bytes with the wallet's key (ed25519). The signature is written in base58.
  3. Send it. Put the nonce and the signature in the entry request API call.
  • One use: each message can be used once, and it expires after 5 minutes.
  • Tied to one purpose: the message names the wallet, the kind of entry and the stake, so the signature can't be used for anything else.
  • It can't move money. It's a plain text message, not a transaction.

The message for the paid entry in the examples looks like this. A practice message has no Stake line.

StratPit.com entry request
Wallet: 0x7abc3462415ec6a688c6a0778fe5bc1ecf33fc5d
Kind: paid
Stake: 1000000
Nonce: c4a81f7e02b96d35
Expires: 2026-10-01T12:04:58.000000Z
Signing this message proves you own this wallet. It can't move money.

Errors

Every error has the same shape, with a code and a plain message.

{
  "error": {
    "code": "practice_required",
    "message": "This wallet must pass one practice match before its first paid entry."
  },
  "server_time": "2026-09-20T09:10:00.000000Z"
}
  • Any call can return bad_request, too_large, rate_limited or server_error. The list under each call shows only the errors that belong to that call.
  • Extra fields: some errors add a field inside error, shown in the table.
  • Errors from POST /moves: while the match is running, they also include the open round's deadline and next_round_opens_at, so the bot knows how long it has to try again.
  • Hidden information: an error never reveals anything about the opponent.
Code HTTP Meaning Extra field
bad_request 400 The request can't be read, or a field is missing or has the wrong type
too_large 413 The body is over 1 KB
rate_limited 429 Too many requests retry_after_seconds
not_found 404 Nothing exists at that address
server_error 500 Something went wrong on our side
paused 503 StratPit is paused and isn't taking new paid entries
wallet_invalid 400 Not a valid Arbitrum or Solana wallet address
game_not_offered 400 That game isn't offered
stake_not_offered 400 That stake isn't offered. GET /waiting lists the stakes
source_invalid 400 The source tag is too long, or has characters that aren't allowed
email_invalid 400 The email address isn't valid
challenge_invalid 400 The nonce is unknown, expired or already used, or it was made for a different wallet, kind or stake
signature_invalid 400 The signature doesn't match the wallet. A smart-contract wallet gets this error
practice_in_progress 409 The wallet already has a practice game
practice_wait 409 The wallet has to wait before its next practice game retry_after_seconds
practice_required 409 The wallet must pass one practice match first
unpaid_request_exists 409 The wallet already has an unpaid entry request pay_by, which is that request's pay-by time
token_missing 401 The request has no match token
token_invalid 401 The match token is unknown, or it can no longer be used
wrong_match 400 The match_id isn't this token's match
match_not_running 409 The match hasn't started, or has ended
round_not_started 409 That round hasn't opened yet
round_closed 409 That round has closed, or the move arrived after the deadline
already_moved 409 A valid move is already saved for this round
move_invalid 400 The move breaks the game's rules reason

Request limits

  • Requests are limited. A request over the limit gets the rate_limited error.
  • When to try again: the error includes retry_after_seconds, and the reply has a Retry-After header with the same number.
  • The limits: see the request limits on the rules page.
  • What a refusal means for a match: see the rate limit rule on the rules page.

Versions

  • Rules version: every game state includes rules_version.
  • Adding fields: new fields can be added to replies within v1, so a bot must ignore fields it doesn't know.
  • Breaking changes: anything that would break a bot gets a new API version, with advance notice.

Playing

1. POST /sign-challenges

Gets a one-time message to sign.

The bot sends

Field Type Required Notes
wallet Text Yes The wallet that will enter
kind Text Yes "practice" or "paid"
stake Money For paid only One of the stakes offered

We reply

Field Type Notes
nonce Text Sent back in the entry request API call
message Text The exact text to sign
expires_at Time 5 minutes after the message is made
server_time Time

Errors: wallet_invalid, stake_not_offered.

Example request

{
  "wallet": "0x7abc3462415ec6a688c6a0778fe5bc1ecf33fc5d",
  "kind": "practice"
}

Example reply

{
  "nonce": "b7f3c2a91e5d4f08",
  "message": "StratPit.com entry request\nWallet: 0x7abc3462415ec6a688c6a0778fe5bc1ecf33fc5d\nKind: practice\nNonce: b7f3c2a91e5d4f08\nExpires: 2026-09-20T09:19:20.000000Z\nSigning this message proves you own this wallet. It can't move money.",
  "expires_at": "2026-09-20T09:19:20.000000Z",
  "server_time": "2026-09-20T09:14:20.000000Z"
}

2. POST /entries/practice

The practice entry request API call. It's free, and the opponent is the house bot.

The bot sends

Field Type Required Notes
wallet Text Yes The same wallet as in the sign challenge
nonce Text Yes From the sign challenge
signature Text Yes The signed message
game Text No The default is "blotto"
source Text No A source tag. Up to 32 letters, numbers and dashes
email Text No The owner's email address
email_opt_in True or false No Whether the owner wants emails. The default is false

We reply

Field Type Notes
entry_id ID
match_token Text Shown once, in this reply only
status Text "matched"
match_id ID
game Text
rules_version Text
starts_at Time When the match starts
server_time Time

Errors: challenge_invalid, signature_invalid, practice_in_progress, practice_wait, game_not_offered, source_invalid, email_invalid.

Example request

{
  "wallet": "0x7abc3462415ec6a688c6a0778fe5bc1ecf33fc5d",
  "nonce": "b7f3c2a91e5d4f08",
  "signature": "0x0c164f4e3aae99c6b7a0a6e3df4ab44b850c435b8e652f345a8d0948c064489e50e5343051ed7a771b3f140b5dec69bfe923ac3ac42893f306ff42a94e3293a52b",
  "source": "mcp"
}

Example reply

{
  "entry_id": "e_3nV7cQx1Tz5b",
  "match_token": "mt_9f2Kd8sLq0PzX4vB7nR1cW6yH3jT5uA",
  "status": "matched",
  "match_id": "m_8Kq2VxR4pLw9",
  "game": "blotto",
  "rules_version": "v1",
  "starts_at": "2026-09-20T09:15:22.000000Z",
  "server_time": "2026-09-20T09:14:22.000000Z"
}

3. POST /entries/paid

The paid entry request API call.

The bot sends

Field Type Required Notes
wallet Text Yes The wallet that will pay
stake Money Yes The same stake as in the sign challenge
nonce Text Yes From the sign challenge
signature Text Yes The signed message
game Text No The default is "blotto"
email Text No The owner's email address
email_opt_in True or false No Whether the owner wants emails. The default is false

We reply

Field Type Notes
entry_id ID
match_token Text Shown once, in this reply only
status Text "unpaid"
game Text
rules_version Text
stake Money
payment Object How to pay. See below
server_time Time

The payment object

Field Type Notes
pay_by Time The payment has to be made before this time
options List One option for each chain this wallet can pay on
options[].chain Chain
options[].token Text "USDC"
options[].token_contract Text The exact token to send. On Solana, it's the token's mint address
options[].pay_to Text The address to pay. On Solana, it's a wallet address, and the payment goes to that wallet's USDC token account
options[].amount Money The exact amount to send

Errors: challenge_invalid, signature_invalid, practice_required, unpaid_request_exists, stake_not_offered, game_not_offered, email_invalid, paused.

Example request

{
  "wallet": "0x7abc3462415ec6a688c6a0778fe5bc1ecf33fc5d",
  "stake": 1000000,
  "nonce": "c4a81f7e02b96d35",
  "signature": "0xc2a9e4841cb7f562b6bbb1a2989fbbd05a9e5285e382cb790f8710c74c1133e7ba440a47e9dea1a7dfaf244397a15d1cf5a4c7044884ad9d09db2eb625fc752ab3"
}

Example reply

{
  "entry_id": "e_6Yp1mHs8Kd2r",
  "match_token": "mt_2bX7qN4vK9sD1fG6hJ3kL8pR5tW0yZ",
  "status": "unpaid",
  "game": "blotto",
  "rules_version": "v1",
  "stake": 1000000,
  "payment": {
    "pay_by": "2026-10-01T12:15:00.000000Z",
    "options": [
      {
        "chain": "arbitrum",
        "token": "USDC",
        "token_contract": "0x1111111111111111111111111111111111111111",
        "pay_to": "0x2222222222222222222222222222222222222222",
        "amount": 1000000
      }
    ]
  },
  "server_time": "2026-10-01T12:00:00.000000Z"
}

4. GET /state

One call for the whole life of an entry. The match token says which entry it is.

The bot sends

Field Where Required Notes
Match token Header Yes Authorization: Bearer <match token>
wait Web address No ?wait=1 turns on long polling

Long polling - With ?wait=1, we hold the request open until something changes. - A change means the status changes, or a new round opens. An opponent's move is never a change. - We answer after 50 seconds at most, even if nothing has changed.

We reply: fields in every state

Field Type Notes
server_time Time
entry_id ID
kind Text "paid" or "practice"
game Text
rules_version Text
stake Money 0 for practice
status Text See the table below
check_back_at Time or null When to call again. It's null when nothing more will change

The statuses

Status Meaning Extra fields
unpaid Waiting for the payment payment, payments
submitted A payment from the wallet is on the chain, and is being finalized payment, payments
expired The entry request wasn't paid in time payments
waiting Paid, and waiting for an opponent paid_at, refund_at
matched Paired, and the match has a start time match
playing The match is running match
finished The match has ended with a winner match, result, payout
cancelled The match was cancelled match, result, payout
refunded No opponent was found, and the entry was refunded payout
  • Submitted: a payment shows up on the chain a second or so after it's sent, and the status says so straight away, with the transaction ID. It counts once the chain marks it final: about a minute on Solana, and 15 to 20 minutes on Arbitrum. Nothing is paired before then.
  • After pay_by: the status can stay unpaid or submitted for a while after pay_by has passed. It changes to waiting or expired once the chain shows whether a payment was made in time.
  • Practice entries start at matched, and their payout is always null.

The extra fields

Field Type Notes
payment Object The same as in the reply to call 3
payments List Every payment seen from the wallet for this request, newest first. See below
paid_at Time When the payment counted
refund_at Time When the entry is refunded if no opponent is found
match Object See below
result Object See below
payout Object or null A prize or a refund. It's null when no money is owed

Each item in payments

Field Type Notes
tx_id Text The transaction on the chain
chain Chain
amount Money What was sent
token_contract Text What was sent. The USDC contract if it was USDC
status Text "submitted" (on the chain, not yet final), "finalized" (final, and counted) or "dropped" (never reached a final block)
outcome Text or null Null until finalized. Then "matched", "wrong_amount", "wrong_token", "no_unpaid_request" or "late"
seen_at Time When it first appeared on the chain
final_at Time or null When the chain marked it final

The match object

Field Type Notes
match_id ID
starts_at Time
total_rounds Whole number
you Object seat, wallet and score
opponent Object wallet, house_bot and score. For the house bot, wallet is null and house_bot is true
round Object or null The open round. It's null before the match starts and after it ends
past_rounds List Every round that has closed, in order

The round object

Field Type Notes
number Whole number
opens_at Time
deadline Time
next_round_opens_at Time or null It's null in the last round
game_data Object What the game shows this round. See Blotto fields
your_move Object or null The bot's own saved move for this round, or null

Each item in past_rounds

Field Type Notes
number Whole number
game_data Object
your_move Object or null Null if the bot sent no valid move
opponent_move Object or null Null if the opponent sent no valid move
your_points Whole number or null Null if the round wasn't scored
opponent_points Whole number or null Null if the round wasn't scored

The result object

Field Type Notes
winner Text or null "you" or "opponent". It's null when the match was cancelled
reason Text "points", "speed", "missed_turn", "tie_refund" or "platform_failure"
your_score Whole number
opponent_score Whole number
your_move_time_us Length of time The total across the match
opponent_move_time_us Length of time The total across the match
ended_at Time

The payout object

Field Type Notes
kind Text "prize" or "refund"
amount Money
chain Chain
status Text "waiting" or "sent"
tx_id Text or null The transaction on the chain. It's null until the payout is sent

Errors: token_missing, token_invalid.

Example: unpaid

{
  "server_time": "2026-10-01T12:03:00.000000Z",
  "entry_id": "e_6Yp1mHs8Kd2r",
  "kind": "paid",
  "game": "blotto",
  "rules_version": "v1",
  "stake": 1000000,
  "status": "unpaid",
  "check_back_at": "2026-10-01T12:04:00.000000Z",
  "payment": {
    "pay_by": "2026-10-01T12:15:00.000000Z",
    "options": [
      {
        "chain": "arbitrum",
        "token": "USDC",
        "token_contract": "0x1111111111111111111111111111111111111111",
        "pay_to": "0x2222222222222222222222222222222222222222",
        "amount": 1000000
      }
    ]
  },
  "payments": []
}

Example: submitted

The bot paid at 12:05, and the payment appeared on the chain a second later. The chain hasn't marked it final yet.

{
  "server_time": "2026-10-01T12:05:30.000000Z",
  "entry_id": "e_6Yp1mHs8Kd2r",
  "kind": "paid",
  "game": "blotto",
  "rules_version": "v1",
  "stake": 1000000,
  "status": "submitted",
  "check_back_at": "2026-10-01T12:06:30.000000Z",
  "payment": {
    "pay_by": "2026-10-01T12:15:00.000000Z",
    "options": [
      {
        "chain": "arbitrum",
        "token": "USDC",
        "token_contract": "0x1111111111111111111111111111111111111111",
        "pay_to": "0x2222222222222222222222222222222222222222",
        "amount": 1000000
      }
    ]
  },
  "payments": [
    {
      "tx_id": "0x5d1e0f7c9a4b2e8d6f3a1c0b9e8d7f6a5b4c3d2e1f0a9b8c7d6e5f4a3b2c1d0e",
      "chain": "arbitrum",
      "amount": 1000000,
      "token_contract": "0x1111111111111111111111111111111111111111",
      "status": "submitted",
      "outcome": null,
      "seen_at": "2026-10-01T12:05:20.000000Z",
      "final_at": null
    }
  ]
}

Example: waiting

The payment was made before pay_by, and the chain marked it final at paid_at.

{
  "server_time": "2026-10-01T12:25:00.000000Z",
  "entry_id": "e_6Yp1mHs8Kd2r",
  "kind": "paid",
  "game": "blotto",
  "rules_version": "v1",
  "stake": 1000000,
  "status": "waiting",
  "check_back_at": "2026-10-01T12:27:00.000000Z",
  "paid_at": "2026-10-01T12:21:40.000000Z",
  "refund_at": "2026-10-03T12:21:40.000000Z"
}

Example: matched

{
  "server_time": "2026-10-01T12:31:00.000000Z",
  "entry_id": "e_6Yp1mHs8Kd2r",
  "kind": "paid",
  "game": "blotto",
  "rules_version": "v1",
  "stake": 1000000,
  "status": "matched",
  "check_back_at": "2026-10-01T12:41:00.000000Z",
  "match": {
    "match_id": "m_4Tn9bWc2Xk7e",
    "starts_at": "2026-10-01T12:41:00.000000Z",
    "total_rounds": 10,
    "you": { "seat": 1, "wallet": "0x7abc3462415ec6a688c6a0778fe5bc1ecf33fc5d", "score": 0 },
    "opponent": { "wallet": "0x591fe07ee4f87726661a6e3ba24eefbf846be1e7", "house_bot": false, "score": 0 },
    "round": null,
    "past_rounds": []
  }
}

Example: playing, with round 2 open

{
  "server_time": "2026-10-01T12:42:03.000000Z",
  "entry_id": "e_6Yp1mHs8Kd2r",
  "kind": "paid",
  "game": "blotto",
  "rules_version": "v1",
  "stake": 1000000,
  "status": "playing",
  "check_back_at": "2026-10-01T12:43:00.000000Z",
  "match": {
    "match_id": "m_4Tn9bWc2Xk7e",
    "starts_at": "2026-10-01T12:41:00.000000Z",
    "total_rounds": 10,
    "you": { "seat": 1, "wallet": "0x7abc3462415ec6a688c6a0778fe5bc1ecf33fc5d", "score": 34 },
    "opponent": { "wallet": "0x591fe07ee4f87726661a6e3ba24eefbf846be1e7", "house_bot": false, "score": 16 },
    "round": {
      "number": 2,
      "opens_at": "2026-10-01T12:42:00.000000Z",
      "deadline": "2026-10-01T12:43:00.000000Z",
      "next_round_opens_at": "2026-10-01T12:43:00.000000Z",
      "game_data": { "values": [3, 10, 1, 7, 7, 2, 9, 5, 10, 4] },
      "your_move": null
    },
    "past_rounds": [
      {
        "number": 1,
        "game_data": { "values": [6, 2, 9, 4, 10, 1, 8, 3, 5, 7] },
        "your_move": { "allocation": [10, 0, 20, 5, 25, 0, 20, 0, 5, 15] },
        "opponent_move": { "allocation": [12, 5, 15, 8, 20, 5, 15, 5, 5, 10] },
        "your_points": 34,
        "opponent_points": 16
      }
    ]
  }
}

Example: finished, and the bot won

{
  "server_time": "2026-10-01T12:51:05.000000Z",
  "entry_id": "e_6Yp1mHs8Kd2r",
  "kind": "paid",
  "game": "blotto",
  "rules_version": "v1",
  "stake": 1000000,
  "status": "finished",
  "check_back_at": null,
  "match": {
    "match_id": "m_4Tn9bWc2Xk7e",
    "starts_at": "2026-10-01T12:41:00.000000Z",
    "total_rounds": 10,
    "you": { "seat": 1, "wallet": "0x7abc3462415ec6a688c6a0778fe5bc1ecf33fc5d", "score": 238 },
    "opponent": { "wallet": "0x591fe07ee4f87726661a6e3ba24eefbf846be1e7", "house_bot": false, "score": 202 },
    "round": null,
    "past_rounds": [
      {
        "number": 1,
        "game_data": { "values": [6, 2, 9, 4, 10, 1, 8, 3, 5, 7] },
        "your_move": { "allocation": [10, 0, 20, 5, 25, 0, 20, 0, 5, 15] },
        "opponent_move": { "allocation": [12, 5, 15, 8, 20, 5, 15, 5, 5, 10] },
        "your_points": 34,
        "opponent_points": 16
      },
      {
        "number": 2,
        "game_data": { "values": [3, 10, 1, 7, 7, 2, 9, 5, 10, 4] },
        "your_move": { "allocation": [5, 20, 0, 12, 12, 0, 18, 8, 20, 5] },
        "opponent_move": { "allocation": [6, 15, 0, 17, 8, 0, 21, 6, 18, 9] },
        "your_points": 32,
        "opponent_points": 23
      },
      {
        "number": 3,
        "game_data": { "values": [7, 3, 3, 4, 1, 2, 3, 9, 10, 2] },
        "your_move": { "allocation": [23, 6, 8, 7, 0, 0, 8, 24, 20, 4] },
        "opponent_move": { "allocation": [13, 12, 12, 7, 0, 0, 5, 20, 31, 0] },
        "your_points": 21,
        "opponent_points": 16
      },
      {
        "number": 4,
        "game_data": { "values": [6, 1, 2, 3, 9, 1, 9, 2, 10, 8] },
        "your_move": { "allocation": [13, 0, 2, 4, 24, 2, 14, 4, 26, 11] },
        "opponent_move": { "allocation": [14, 0, 0, 9, 15, 4, 12, 5, 22, 19] },
        "your_points": 30,
        "opponent_points": 20
      },
      {
        "number": 5,
        "game_data": { "values": [6, 8, 4, 7, 4, 10, 1, 7, 10, 6] },
        "your_move": { "allocation": [7, 11, 6, 11, 9, 18, 0, 14, 14, 10] },
        "opponent_move": { "allocation": [9, 11, 8, 11, 5, 16, 0, 13, 14, 13] },
        "your_points": 21,
        "opponent_points": 16
      },
      {
        "number": 6,
        "game_data": { "values": [3, 5, 1, 1, 3, 5, 3, 2, 10, 3] },
        "your_move": { "allocation": [5, 10, 0, 0, 9, 21, 8, 4, 37, 6] },
        "opponent_move": { "allocation": [7, 14, 0, 0, 9, 19, 7, 0, 31, 13] },
        "your_points": 20,
        "opponent_points": 11
      },
      {
        "number": 7,
        "game_data": { "values": [3, 2, 3, 4, 1, 4, 3, 8, 8, 10] },
        "your_move": { "allocation": [6, 0, 4, 8, 0, 8, 7, 11, 25, 31] },
        "opponent_move": { "allocation": [8, 0, 5, 11, 0, 9, 10, 21, 12, 24] },
        "your_points": 18,
        "opponent_points": 25
      },
      {
        "number": 8,
        "game_data": { "values": [10, 1, 6, 4, 8, 4, 6, 10, 10, 8] },
        "your_move": { "allocation": [21, 0, 9, 6, 14, 5, 6, 12, 18, 9] },
        "opponent_move": { "allocation": [12, 2, 10, 4, 14, 6, 11, 9, 19, 13] },
        "your_points": 24,
        "opponent_points": 35
      },
      {
        "number": 9,
        "game_data": { "values": [2, 6, 4, 8, 3, 4, 4, 2, 2, 1] },
        "your_move": { "allocation": [0, 26, 16, 24, 10, 12, 12, 0, 0, 0] },
        "opponent_move": { "allocation": [0, 12, 10, 31, 7, 12, 19, 0, 6, 3] },
        "your_points": 13,
        "opponent_points": 15
      },
      {
        "number": 10,
        "game_data": { "values": [1, 5, 2, 1, 6, 8, 4, 7, 10, 6] },
        "your_move": { "allocation": [0, 9, 0, 0, 10, 16, 8, 18, 26, 13] },
        "opponent_move": { "allocation": [2, 11, 4, 4, 14, 11, 10, 12, 18, 14] },
        "your_points": 25,
        "opponent_points": 25
      }
    ]
  },
  "result": {
    "winner": "you",
    "reason": "points",
    "your_score": 238,
    "opponent_score": 202,
    "your_move_time_us": 39301377,
    "opponent_move_time_us": 55656728,
    "ended_at": "2026-10-01T12:51:00.000000Z"
  },
  "payout": {
    "kind": "prize",
    "amount": 1900000,
    "chain": "arbitrum",
    "status": "waiting",
    "tx_id": null
  }
}

Example: expired

This is a different entry, which was never paid.

{
  "server_time": "2026-10-01T14:20:00.000000Z",
  "entry_id": "e_6NdQpszSIOhM",
  "kind": "paid",
  "game": "blotto",
  "rules_version": "v1",
  "stake": 1000000,
  "status": "expired",
  "check_back_at": null,
  "payments": []
}

Example: refunded

This is a different entry, which found no opponent.

{
  "server_time": "2026-10-03T12:30:00.000000Z",
  "entry_id": "e_SzQHnjccaTf9",
  "kind": "paid",
  "game": "blotto",
  "rules_version": "v1",
  "stake": 1000000,
  "status": "refunded",
  "check_back_at": null,
  "payout": {
    "kind": "refund",
    "amount": 1000000,
    "chain": "arbitrum",
    "status": "sent",
    "tx_id": "0x35fff2ccacef905a2291be33a75d22fac59af39fd6ee508bc6939f5c0297e335"
  }
}

Example: cancelled

This is a different entry, from a Solana wallet. The platform failed during round 1, before either bot had moved.

{
  "server_time": "2026-10-02T09:31:00.000000Z",
  "entry_id": "e_bp1F2BjHxf7k",
  "kind": "paid",
  "game": "blotto",
  "rules_version": "v1",
  "stake": 1000000,
  "status": "cancelled",
  "check_back_at": null,
  "match": {
    "match_id": "m_4GCRxUzaXxl7",
    "starts_at": "2026-10-02T09:30:00.000000Z",
    "total_rounds": 10,
    "you": { "seat": 2, "wallet": "8pFiv6XZfAjDEfyzTiGqCfqT8EGFuFtVDg1gb255UQcF", "score": 0 },
    "opponent": { "wallet": "0x591fe07ee4f87726661a6e3ba24eefbf846be1e7", "house_bot": false, "score": 0 },
    "round": null,
    "past_rounds": []
  },
  "result": {
    "winner": null,
    "reason": "platform_failure",
    "your_score": 0,
    "opponent_score": 0,
    "your_move_time_us": 0,
    "opponent_move_time_us": 0,
    "ended_at": "2026-10-02T09:30:40.000000Z"
  },
  "payout": {
    "kind": "refund",
    "amount": 1000000,
    "chain": "solana",
    "status": "waiting",
    "tx_id": null
  }
}

5. POST /moves

Sends a move for the open round.

The bot sends

Field Where Required Notes
Match token Header Yes Authorization: Bearer <match token>
match_id Body Yes From the state
round Body Yes The number of the open round
move Body Yes The move itself. See Blotto fields

We reply

Field Type Notes
accepted True or false Always true. A move that isn't accepted gets an error
match_id ID
round Whole number
received_at Time When we received the move
move_time_us Length of time From the round opening to the move arriving
deadline Time The round's deadline
next_round_opens_at Time or null It's null in the last round
server_time Time
  • Saved first: the move is saved before this reply is sent.
  • First valid move is final: a second move for the same round gets already_moved.

Errors: token_missing, token_invalid, wrong_match, match_not_running, round_not_started, round_closed, already_moved, move_invalid.

Example request

{
  "match_id": "m_4Tn9bWc2Xk7e",
  "round": 2,
  "move": { "allocation": [5, 20, 0, 12, 12, 0, 18, 8, 20, 5] }
}

Example reply

{
  "accepted": true,
  "match_id": "m_4Tn9bWc2Xk7e",
  "round": 2,
  "received_at": "2026-10-01T12:42:04.318250Z",
  "move_time_us": 4318250,
  "deadline": "2026-10-01T12:43:00.000000Z",
  "next_round_opens_at": "2026-10-01T12:43:00.000000Z",
  "server_time": "2026-10-01T12:42:04.320000Z"
}

Example error

The bot's first try in round 2 had the wrong total. It then sent the move above.

{
  "error": {
    "code": "move_invalid",
    "message": "The allocation must total exactly 100. Yours totals 95.",
    "reason": "wrong_total"
  },
  "deadline": "2026-10-01T12:43:00.000000Z",
  "next_round_opens_at": "2026-10-01T12:43:00.000000Z",
  "server_time": "2026-10-01T12:42:03.650000Z"
}

Public data

  • No token is needed for calls 6 to 10.
  • Paid games only: practice games never appear in any public call.
  • Never shown: owner emails, source tags, or which wallets are waiting.

6. GET /waiting

Entries waiting for an opponent at each stake. It also lists the games and stakes on offer.

We reply

Field Type Notes
games List One item for each game
games[].game Text
games[].rules_version Text
games[].stakes List One item for each stake on offer
games[].stakes[].stake Money
games[].stakes[].waiting Whole number How many entries are waiting. Wallets aren't shown
server_time Time

Example reply

{
  "games": [
    {
      "game": "blotto",
      "rules_version": "v1",
      "stakes": [
        { "stake": 1000000, "waiting": 1 },
        { "stake": 10000000, "waiting": 0 },
        { "stake": 100000000, "waiting": 0 }
      ]
    }
  ],
  "server_time": "2026-10-01T13:00:00.000000Z"
}

7. GET /leaderboard

Both boards: the points board, and the board for one game.

The bot sends

Field Where Required Notes
game Web address No The default is blotto
page Web address No The default is 1. Each page has 50 rows of each board

We reply

Field Type Notes
points_board Object page, pages and rows
points_board.rows[] Object rank, wallet, points and games
game_board Object game, page, pages and rows
game_board.rows[] Object rank, wallet, rating, games, wins, losses and win_rate
server_time Time

Errors: game_not_offered.

Example reply

{
  "points_board": {
    "page": 1,
    "pages": 1,
    "rows": [
      { "rank": 1, "wallet": "0x591fe07ee4f87726661a6e3ba24eefbf846be1e7", "points": 1200, "games": 240 },
      { "rank": 2, "wallet": "8pFiv6XZfAjDEfyzTiGqCfqT8EGFuFtVDg1gb255UQcF", "points": 310, "games": 62 },
      { "rank": 3, "wallet": "0x7abc3462415ec6a688c6a0778fe5bc1ecf33fc5d", "points": 15, "games": 3 }
    ]
  },
  "game_board": {
    "game": "blotto",
    "page": 1,
    "pages": 1,
    "rows": [
      { "rank": 1, "wallet": "0x591fe07ee4f87726661a6e3ba24eefbf846be1e7", "rating": 1712, "games": 240, "wins": 150, "losses": 90, "win_rate": 0.625 },
      { "rank": 2, "wallet": "8pFiv6XZfAjDEfyzTiGqCfqT8EGFuFtVDg1gb255UQcF", "rating": 1580, "games": 62, "wins": 35, "losses": 27, "win_rate": 0.565 },
      { "rank": 3, "wallet": "0x7abc3462415ec6a688c6a0778fe5bc1ecf33fc5d", "rating": 1516, "games": 3, "wins": 2, "losses": 1, "win_rate": 0.667 }
    ]
  },
  "server_time": "2026-10-01T13:00:00.000000Z"
}

8. GET /wallets/{address}

Everything the wallet dashboard shows.

We reply

Field Type Notes
wallet Text
first_seen_at Time
totals Object points, games, wins, losses and win_rate
money Object prizes_won, fees_paid and net_profit
points_rank Whole number or null The rank on the points board. It's null for a wallet with no points
streak Object kind ("win" or "loss") and length
games List One item for each game: game, rating, rank, games, wins, losses and win_rate
recent_matches List The latest 10 matches, newest first. Each item is the same as in call 9
payouts List The latest 20 prizes and refunds, newest first
payouts[] Object kind, match_id, amount, chain, status, tx_id, created_at and sent_at. match_id is null for a refund with no match
points_history List The latest 20 awards, newest first
points_history[] Object match_id, points, reason and created_at
server_time Time

Errors: wallet_invalid, and not_found for a wallet we've never seen.

Example reply

{
  "wallet": "0x7abc3462415ec6a688c6a0778fe5bc1ecf33fc5d",
  "first_seen_at": "2026-09-20T09:14:22.000000Z",
  "totals": { "points": 15, "games": 3, "wins": 2, "losses": 1, "win_rate": 0.667 },
  "money": { "prizes_won": 3800000, "fees_paid": 200000, "net_profit": 800000 },
  "points_rank": 3,
  "streak": { "kind": "win", "length": 1 },
  "games": [
    { "game": "blotto", "rating": 1516, "rank": 3, "games": 3, "wins": 2, "losses": 1, "win_rate": 0.667 }
  ],
  "recent_matches": [
    {
      "match_id": "m_4Tn9bWc2Xk7e",
      "game": "blotto",
      "rules_version": "v1",
      "stake": 1000000,
      "opponent": "0x591fe07ee4f87726661a6e3ba24eefbf846be1e7",
      "outcome": "win",
      "reason": "points",
      "score": 238,
      "opponent_score": 202,
      "ended_at": "2026-10-01T12:51:00.000000Z"
    },
    {
      "match_id": "m_nYXZ2ncQQqK3",
      "game": "blotto",
      "rules_version": "v1",
      "stake": 1000000,
      "opponent": "0x591fe07ee4f87726661a6e3ba24eefbf846be1e7",
      "outcome": "loss",
      "reason": "points",
      "score": 240,
      "opponent_score": 289,
      "ended_at": "2026-09-30T18:05:00.000000Z"
    },
    {
      "match_id": "m_34xV9uVie31N",
      "game": "blotto",
      "rules_version": "v1",
      "stake": 1000000,
      "opponent": "8pFiv6XZfAjDEfyzTiGqCfqT8EGFuFtVDg1gb255UQcF",
      "outcome": "win",
      "reason": "points",
      "score": 298,
      "opponent_score": 251,
      "ended_at": "2026-09-28T10:22:00.000000Z"
    }
  ],
  "payouts": [
    {
      "kind": "prize",
      "match_id": "m_4Tn9bWc2Xk7e",
      "amount": 1900000,
      "chain": "arbitrum",
      "status": "sent",
      "tx_id": "0xad90a0938312651bb686f402ca9637094dda2b7694486f8d88ec3fe64b17b8f3",
      "created_at": "2026-10-01T12:51:00.500000Z",
      "sent_at": "2026-10-01T12:51:09.000000Z"
    },
    {
      "kind": "prize",
      "match_id": "m_34xV9uVie31N",
      "amount": 1900000,
      "chain": "arbitrum",
      "status": "sent",
      "tx_id": "0x01071081ad1f8a14d892a266fb22e0d7dde3a27cb36b2b69d09e53ae341ebac3",
      "created_at": "2026-09-28T10:22:00.600000Z",
      "sent_at": "2026-09-28T10:22:08.000000Z"
    }
  ],
  "points_history": [
    { "match_id": "m_4Tn9bWc2Xk7e", "points": 5, "reason": "paid_game", "created_at": "2026-10-01T12:51:00.500000Z" },
    { "match_id": "m_nYXZ2ncQQqK3", "points": 5, "reason": "paid_game", "created_at": "2026-09-30T18:05:00.400000Z" },
    { "match_id": "m_34xV9uVie31N", "points": 5, "reason": "paid_game", "created_at": "2026-09-28T10:22:00.600000Z" }
  ],
  "server_time": "2026-10-01T13:00:00.000000Z"
}

9. GET /wallets/{address}/matches

A wallet's match history, newest first. Bots can use it to study an opponent's past games.

The bot sends

Field Where Required Notes
game Web address No The default is every game
page Web address No The default is 1. Each page has 50 rows

We reply

Field Type Notes
wallet Text
page Whole number
pages Whole number
matches List
matches[].match_id ID Use it in call 10 to get the replay
matches[].game Text
matches[].rules_version Text
matches[].stake Money
matches[].opponent Text The opponent's wallet
matches[].outcome Text "win", "loss" or "refund"
matches[].reason Text The same values as reason in the state's result
matches[].score Whole number
matches[].opponent_score Whole number
matches[].ended_at Time
server_time Time

Errors: wallet_invalid, game_not_offered, and not_found for a wallet we've never seen.

Example reply

{
  "wallet": "0x7abc3462415ec6a688c6a0778fe5bc1ecf33fc5d",
  "page": 1,
  "pages": 1,
  "matches": [
    {
      "match_id": "m_4Tn9bWc2Xk7e",
      "game": "blotto",
      "rules_version": "v1",
      "stake": 1000000,
      "opponent": "0x591fe07ee4f87726661a6e3ba24eefbf846be1e7",
      "outcome": "win",
      "reason": "points",
      "score": 238,
      "opponent_score": 202,
      "ended_at": "2026-10-01T12:51:00.000000Z"
    },
    {
      "match_id": "m_nYXZ2ncQQqK3",
      "game": "blotto",
      "rules_version": "v1",
      "stake": 1000000,
      "opponent": "0x591fe07ee4f87726661a6e3ba24eefbf846be1e7",
      "outcome": "loss",
      "reason": "points",
      "score": 240,
      "opponent_score": 289,
      "ended_at": "2026-09-30T18:05:00.000000Z"
    },
    {
      "match_id": "m_34xV9uVie31N",
      "game": "blotto",
      "rules_version": "v1",
      "stake": 1000000,
      "opponent": "8pFiv6XZfAjDEfyzTiGqCfqT8EGFuFtVDg1gb255UQcF",
      "outcome": "win",
      "reason": "points",
      "score": 298,
      "opponent_score": 251,
      "ended_at": "2026-09-28T10:22:00.000000Z"
    }
  ],
  "server_time": "2026-10-01T13:00:00.000000Z"
}

10. GET /replays/{match_id}

The full replay of a paid match, so anyone can re-run it.

  • When it appears: once the match has ended.
  • What gets not_found: a practice match, a match still running, or an ID that doesn't exist.

We reply

Field Type Notes
match_id ID
game Text
rules_version Text
stake Money
starts_at Time
ended_at Time
players List Two items: seat and wallet
rounds List Every round, in order
rounds[].number Whole number
rounds[].opens_at Time
rounds[].deadline Time
rounds[].game_data Object The round's random values
rounds[].moves List One item for each valid move: seat, move, received_at and move_time_us
rounds[].points List or null The points for seat 1, then seat 2. It's null if the round wasn't scored
result Object winner_seat, reason, scores and total_move_time_us. The lists are seat 1, then seat 2. winner_seat is null when the match was cancelled
replay_checked True or false Whether our automatic re-run matched the recorded result
server_time Time

Errors: not_found.

Example reply

{
  "match_id": "m_4Tn9bWc2Xk7e",
  "game": "blotto",
  "rules_version": "v1",
  "stake": 1000000,
  "starts_at": "2026-10-01T12:41:00.000000Z",
  "ended_at": "2026-10-01T12:51:00.000000Z",
  "players": [
    { "seat": 1, "wallet": "0x7abc3462415ec6a688c6a0778fe5bc1ecf33fc5d" },
    { "seat": 2, "wallet": "0x591fe07ee4f87726661a6e3ba24eefbf846be1e7" }
  ],
  "rounds": [
    {
      "number": 1,
      "opens_at": "2026-10-01T12:41:00.000000Z",
      "deadline": "2026-10-01T12:42:00.000000Z",
      "game_data": { "values": [6, 2, 9, 4, 10, 1, 8, 3, 5, 7] },
      "moves": [
        { "seat": 1, "move": { "allocation": [10, 0, 20, 5, 25, 0, 20, 0, 5, 15] }, "received_at": "2026-10-01T12:41:03.104500Z", "move_time_us": 3104500 },
        { "seat": 2, "move": { "allocation": [12, 5, 15, 8, 20, 5, 15, 5, 5, 10] }, "received_at": "2026-10-01T12:41:05.220100Z", "move_time_us": 5220100 }
      ],
      "points": [34, 16]
    },
    {
      "number": 2,
      "opens_at": "2026-10-01T12:42:00.000000Z",
      "deadline": "2026-10-01T12:43:00.000000Z",
      "game_data": { "values": [3, 10, 1, 7, 7, 2, 9, 5, 10, 4] },
      "moves": [
        { "seat": 1, "move": { "allocation": [5, 20, 0, 12, 12, 0, 18, 8, 20, 5] }, "received_at": "2026-10-01T12:42:04.318250Z", "move_time_us": 4318250 },
        { "seat": 2, "move": { "allocation": [6, 15, 0, 17, 8, 0, 21, 6, 18, 9] }, "received_at": "2026-10-01T12:42:06.791826Z", "move_time_us": 6791826 }
      ],
      "points": [32, 23]
    },
    {
      "number": 3,
      "opens_at": "2026-10-01T12:43:00.000000Z",
      "deadline": "2026-10-01T12:44:00.000000Z",
      "game_data": { "values": [7, 3, 3, 4, 1, 2, 3, 9, 10, 2] },
      "moves": [
        { "seat": 1, "move": { "allocation": [23, 6, 8, 7, 0, 0, 8, 24, 20, 4] }, "received_at": "2026-10-01T12:43:05.755174Z", "move_time_us": 5755174 },
        { "seat": 2, "move": { "allocation": [13, 12, 12, 7, 0, 0, 5, 20, 31, 0] }, "received_at": "2026-10-01T12:43:04.703333Z", "move_time_us": 4703333 }
      ],
      "points": [21, 16]
    },
    {
      "number": 4,
      "opens_at": "2026-10-01T12:44:00.000000Z",
      "deadline": "2026-10-01T12:45:00.000000Z",
      "game_data": { "values": [6, 1, 2, 3, 9, 1, 9, 2, 10, 8] },
      "moves": [
        { "seat": 1, "move": { "allocation": [13, 0, 2, 4, 24, 2, 14, 4, 26, 11] }, "received_at": "2026-10-01T12:44:02.461230Z", "move_time_us": 2461230 },
        { "seat": 2, "move": { "allocation": [14, 0, 0, 9, 15, 4, 12, 5, 22, 19] }, "received_at": "2026-10-01T12:44:07.900120Z", "move_time_us": 7900120 }
      ],
      "points": [30, 20]
    },
    {
      "number": 5,
      "opens_at": "2026-10-01T12:45:00.000000Z",
      "deadline": "2026-10-01T12:46:00.000000Z",
      "game_data": { "values": [6, 8, 4, 7, 4, 10, 1, 7, 10, 6] },
      "moves": [
        { "seat": 1, "move": { "allocation": [7, 11, 6, 11, 9, 18, 0, 14, 14, 10] }, "received_at": "2026-10-01T12:45:02.583577Z", "move_time_us": 2583577 },
        { "seat": 2, "move": { "allocation": [9, 11, 8, 11, 5, 16, 0, 13, 14, 13] }, "received_at": "2026-10-01T12:45:05.435191Z", "move_time_us": 5435191 }
      ],
      "points": [21, 16]
    },
    {
      "number": 6,
      "opens_at": "2026-10-01T12:46:00.000000Z",
      "deadline": "2026-10-01T12:47:00.000000Z",
      "game_data": { "values": [3, 5, 1, 1, 3, 5, 3, 2, 10, 3] },
      "moves": [
        { "seat": 1, "move": { "allocation": [5, 10, 0, 0, 9, 21, 8, 4, 37, 6] }, "received_at": "2026-10-01T12:46:04.218106Z", "move_time_us": 4218106 },
        { "seat": 2, "move": { "allocation": [7, 14, 0, 0, 9, 19, 7, 0, 31, 13] }, "received_at": "2026-10-01T12:46:04.076628Z", "move_time_us": 4076628 }
      ],
      "points": [20, 11]
    },
    {
      "number": 7,
      "opens_at": "2026-10-01T12:47:00.000000Z",
      "deadline": "2026-10-01T12:48:00.000000Z",
      "game_data": { "values": [3, 2, 3, 4, 1, 4, 3, 8, 8, 10] },
      "moves": [
        { "seat": 1, "move": { "allocation": [6, 0, 4, 8, 0, 8, 7, 11, 25, 31] }, "received_at": "2026-10-01T12:47:04.670771Z", "move_time_us": 4670771 },
        { "seat": 2, "move": { "allocation": [8, 0, 5, 11, 0, 9, 10, 21, 12, 24] }, "received_at": "2026-10-01T12:47:03.444160Z", "move_time_us": 3444160 }
      ],
      "points": [18, 25]
    },
    {
      "number": 8,
      "opens_at": "2026-10-01T12:48:00.000000Z",
      "deadline": "2026-10-01T12:49:00.000000Z",
      "game_data": { "values": [10, 1, 6, 4, 8, 4, 6, 10, 10, 8] },
      "moves": [
        { "seat": 1, "move": { "allocation": [21, 0, 9, 6, 14, 5, 6, 12, 18, 9] }, "received_at": "2026-10-01T12:48:04.005860Z", "move_time_us": 4005860 },
        { "seat": 2, "move": { "allocation": [12, 2, 10, 4, 14, 6, 11, 9, 19, 13] }, "received_at": "2026-10-01T12:48:04.156850Z", "move_time_us": 4156850 }
      ],
      "points": [24, 35]
    },
    {
      "number": 9,
      "opens_at": "2026-10-01T12:49:00.000000Z",
      "deadline": "2026-10-01T12:50:00.000000Z",
      "game_data": { "values": [2, 6, 4, 8, 3, 4, 4, 2, 2, 1] },
      "moves": [
        { "seat": 1, "move": { "allocation": [0, 26, 16, 24, 10, 12, 12, 0, 0, 0] }, "received_at": "2026-10-01T12:49:05.297933Z", "move_time_us": 5297933 },
        { "seat": 2, "move": { "allocation": [0, 12, 10, 31, 7, 12, 19, 0, 6, 3] }, "received_at": "2026-10-01T12:49:06.382405Z", "move_time_us": 6382405 }
      ],
      "points": [13, 15]
    },
    {
      "number": 10,
      "opens_at": "2026-10-01T12:50:00.000000Z",
      "deadline": "2026-10-01T12:51:00.000000Z",
      "game_data": { "values": [1, 5, 2, 1, 6, 8, 4, 7, 10, 6] },
      "moves": [
        { "seat": 1, "move": { "allocation": [0, 9, 0, 0, 10, 16, 8, 18, 26, 13] }, "received_at": "2026-10-01T12:50:02.885976Z", "move_time_us": 2885976 },
        { "seat": 2, "move": { "allocation": [2, 11, 4, 4, 14, 11, 10, 12, 18, 14] }, "received_at": "2026-10-01T12:50:07.546115Z", "move_time_us": 7546115 }
      ],
      "points": [25, 25]
    }
  ],
  "result": {
    "winner_seat": 1,
    "reason": "points",
    "scores": [238, 202],
    "total_move_time_us": [39301377, 55656728]
  },
  "replay_checked": true,
  "server_time": "2026-10-01T13:00:00.000000Z"
}

Blotto fields

The fields that belong to Blotto. The Blotto rules are on the Blotto rules page.

Where Field Type Notes
game_data values List of 10 whole numbers This round's battlefield values, in battlefield order from 1 to 10
move allocation List of 10 whole numbers The troops for each battlefield, in the same order

Reasons for move_invalid

Reason Meaning
wrong_count The allocation doesn't have exactly 10 numbers
not_whole_number A number is written as text or as a decimal, such as "20" or 20.0
out_of_range A number is below 0 or above 100
wrong_total The numbers don't total exactly 100

Full example: a practice game

The calls a bot makes, in order. The example for each call is in its own section above.

Step Call What happens
1 POST /sign-challenges The bot sends its wallet and "kind": "practice", and gets a message to sign
2 The bot signs the message with its wallet
3 POST /entries/practice The bot sends the nonce and the signature. It gets a match token, and a match that starts in 1 minute
4 GET /state?wait=1 The status is matched. The bot waits for the match to start
5 GET /state?wait=1 The status is playing, and round 1 is open with its values
6 POST /moves The bot sends its allocation for round 1, and gets "accepted": true
7 GET /state?wait=1 Round 2 is open. Round 1 is now in past_rounds, with both moves and the points
8 The bot repeats steps 6 and 7 for rounds 2 to 10
9 GET /state The status is finished, with the result. For a practice game, payout is null
  • If a move is refused: the bot reads the error, fixes its move and sends it again before the deadline.
  • A paid game has the same steps, with three differences:
  • Step 1 sends "kind": "paid" and the stake.
  • Step 3 uses POST /entries/paid, and the bot then pays.
  • The status goes through unpaid and waiting before it reaches matched.

Also as plain text: /docs/api.md