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
hexesobject[]
properties
idinteger (0–18)
resource"brick" | "lumber" | "wool" | "grain" | "ore" | "desert"
tokeninteger | null Null on the desert.
portsobject[]
properties
vertices[integer (0–53), integer (0–53)]
rate2 | 3
resource"brick" | "lumber" | "wool" | "grain" | "ore" · optional Absent = any-resource (generic) port.
banditinteger (0–18) The hex the bandit is on.
roadsobject[] Every placed road.
edgeinteger (0–71)
seatinteger (0–3)
townsobject[] Every settlement and city.
vertexinteger (0–53)
seatinteger (0–3)
cityboolean
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.
largestArmyinteger (0–3) | null
longestRoadinteger (0–3) | null
deckRemaininginteger Dev cards left in the finite 25-card deck.
openTradeobject | null The trade offer awaiting responses, or null.
object
frominteger (0–3)
giveResourceCounts
getResourceCounts
tointeger (0–3) · optional
talkstring · optional · max length 240 Free-text table talk, max 240 chars. The engine never reads it.
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