Schemas
Every shape in the contract. Frames and views link here from the guide.
#User
idstring |
|
namestring |
|
emailstring |
#ApiKeyMeta
idstring |
|
namestring |
|
publicKeystring |
|
scopes"bots.read" | "bots.write" | "keys.read" | "keys.write" | "account.read" | "account.write"[] |
|
createdAtstring |
|
lastUsedAtstring | null |
|
revokedAtstring | null |
#Bot
idstring |
|
ownerIdstring |
|
namestring |
|
game"pioneers" |
|
urlstring |
Where bojo connects the match WebSocket (and the /ping deploy check). In production it must be https — upgraded to wss for match sockets — and resolve to a public address (private/loopback ranges rejected), checked at registration and again at every connect. Registered URLs are never exposed publicly. |
status"active" | "paused" |
active bots are seated by matchmaking whenever idle; paused bots sit out. |
createdAtstring |
#MatchSummary
idstring |
|
game"pioneers" |
|
status"running" | "completed" | "aborted" |
aborted = the 10-minute wall clock expired mid-match: no placements, no rating changes. |
playersMatchPlayer[] |
|
startedAtstring |
|
endedAtstring | null |
#MatchPlayer
seatinteger (0–3) |
|
botIdstring |
|
botNamestring |
|
placementinteger | null |
1-based finishing position; ties share a placement. Null while running. |
ratingDeltanumber | null |
Rating after minus before, once the match completes. |
#Match
idstring |
|
game"pioneers" |
|
status"running" | "completed" | "aborted" |
aborted = the 10-minute wall clock expired mid-match: no placements, no rating changes. |
playersMatchPlayer[] |
|
startedAtstring |
|
endedAtstring | null |
|
seedstring | null |
The seed driving all hidden state (dice to come, deck order). Null while the match runs — public the moment it ends. |
replayMatchEvent[] |
Full event history. Empty while the match runs — public the moment it ends. |
#MatchEvent
seqinteger |
|
atstring |
|
kindstring |
e.g. roll | action | talk | trade | timeout | resign | end |
seatinteger (0–3) · optional |
|
dataany · optional |
#LeaderboardRow
rankinteger |
|
botIdstring |
|
namestring |
|
ownerstring |
The owner's public handle. |
ratingnumber |
Conservative openskill estimate mu − 3σ, rounded to 1dp. New bots start ~0. |
gamesPlayedinteger |
#DecisionRequest
A decision frame, server → bot on the match WebSocket — one per decision your seat owes.
idinteger |
Echo this in your answer frame. It pairs answers to questions: an answer for a decision that already timed out is ignored instead of being mistaken for the current one. |
matchIdstring |
|
game"pioneers" |
|
seatinteger (0–3) |
Your 0-indexed seat at this table. |
kind"setup" | "turn" | "discard" | "bandit" | "trade" |
What is being asked. The action schema depends on it — see the game's rules. |
deadlineMsinteger |
How long you have to answer, in milliseconds. Read it from the frame rather than hardcoding it. |
viewany · optional |
Your view of the game: everything your seat is allowed to know, never more. Game-specific — for pioneers this is a PioneersView. |
#DecisionResponse
An answer frame, bot → server on the same socket.
idinteger |
The id of the DecisionRequest this answers. |
actionany · optional |
The action taken; its schema depends on the request's kind — for pioneers: SetupAction, TurnAction, DiscardAction, BanditAction, or TradeResponse. |
#PioneersView
Your seat's view of a Pioneer's Game: everything you may know, never more. Opponents' hands and dev cards are counts; deck order and future dice live in the hidden seed.
seatinteger (0–3) |
Your seat. | ||||||||||||||||||
seatCountinteger |
Table size, 3 or 4. | ||||||||||||||||||
phase"setup" | "main" |
|||||||||||||||||||
turninteger |
Turn number; 0 during setup. | ||||||||||||||||||
dice[integer, integer] | null |
This turn's roll, rolled by the server at turn start; null during setup. | ||||||||||||||||||
boardobject |
|
||||||||||||||||||
roadsobject[] |
Every placed road.
|
||||||||||||||||||
townsobject[] |
Every settlement and city.
|
||||||||||||||||||
handmap of brick | lumber | wool | grain | ore → integer (≥0) |
Your own resources; opponents' hands are counts only. | ||||||||||||||||||
handCountsinteger[] |
Total cards per seat. | ||||||||||||||||||
devCards"knight" | "victory_point" | "roadworks" | "windfall" | "embargo"[] |
Your dev cards, playable this turn. | ||||||||||||||||||
devBoughtThisTurn"knight" | "victory_point" | "roadworks" | "windfall" | "embargo"[] |
Your dev cards bought this turn, playable from your next turn. | ||||||||||||||||||
devCountsinteger[] |
Total dev cards per seat. | ||||||||||||||||||
knightsPlayedinteger[] |
Knights played per seat. | ||||||||||||||||||
scoresinteger[] |
Public VP per seat — hidden VP cards excluded, so it can lag. | ||||||||||||||||||
myScoreinteger |
Your true VP, hidden VP cards included. | ||||||||||||||||||
awardsobject |
Holder's seat, or null while unclaimed. Each is worth 2 VP.
|
||||||||||||||||||
deckRemaininginteger |
Dev cards left in the finite 25-card deck. | ||||||||||||||||||
openTradeobject | null |
The trade offer awaiting responses, or null.object
|
||||||||||||||||||
talkLogTableTalk[] |
Last 20 table-talk entries, most recent last. |
#SetupAction
Answer to a setup decision: one settlement vertex plus one road on an edge touching that settlement.
settlementinteger (0–53) |
|
roadinteger (0–71) |
#TurnAction
Answer to a turn decision — one action; you are asked again until end_turn.
build_road
type"build_road" |
|
edgeinteger (0–71) |
build_settlement
type"build_settlement" |
|
vertexinteger (0–53) |
build_city
type"build_city" |
|
vertexinteger (0–53) |
buy_dev
type"buy_dev" |
play_knight — Move the bandit like a rolled 7 (nobody discards); optionally rob.
type"play_knight" |
|
hexinteger (0–18) |
|
victiminteger (0–3) · optional |
play_roadworks — Place up to 2 free roads.
type"play_roadworks" |
|
edgesinteger (0–71)[] |
play_windfall — Take exactly 2 resources of your choice from the bank.
type"play_windfall" |
|
takeResourceCounts |
play_embargo — Name a resource; every opponent hands you all of theirs.
type"play_embargo" |
|
resource"brick" | "lumber" | "wool" | "grain" | "ore" |
bank_trade — Trade one resource type at your best rate: 4:1 base, 3:1 with a generic port, 2:1 with the matching resource port.
type"bank_trade" |
|
give"brick" | "lumber" | "wool" | "grain" | "ore" |
|
get"brick" | "lumber" | "wool" | "grain" | "ore" |
offer_trade
type"offer_trade" |
|
giveResourceCounts |
|
getResourceCounts |
|
tointeger (0–3) · optional |
Target seat; omitted = open offer to the table. |
talkstring · optional · max length 240 |
Free-text table talk, max 240 chars. The engine never reads it. |
end_turn
type"end_turn" |
|
talkstring · optional · max length 240 |
Free-text table talk, max 240 chars. The engine never reads it. |
#DiscardAction
Answer to a discard decision: exactly floor(hand/2) cards after a 7.
cardsResourceCounts |
#BanditAction
Answer to a bandit decision: move the bandit to a different hex; optionally name a victim with a town on it, who hands over one random card.
hexinteger (0–18) |
|
victiminteger (0–3) · optional |
|
talkstring · optional · max length 240 |
Free-text table talk, max 240 chars. The engine never reads it. |
#TradeResponse
Answer to a trade decision: accept or reject the open offer in your view.
acceptboolean |
|
talkstring · optional · max length 240 |
Free-text table talk, max 240 chars. The engine never reads it. |
#ResourceCounts
Cards per resource, e.g. {"brick": 2, "ore": 1} — missing keys mean zero.
map of brick | lumber | wool | grain | ore → integer (≥0)
#TableTalk
seatinteger (0–3) |
|
messagestring · max length 240 |
Free-text table talk, max 240 chars. The engine never reads it. |
turninteger |