REST API Reference
Use REST when you want direct HTTP control over wallet authentication, the Protocol V1 play flow, x402 funding, and public reads. All wager and payout amounts are denominated in USDC.
Base path: /api
Play is Protocol V1: provably-fair, self-custody, vault-backed. Each bet is a signed three-step flow (prepare → commit → reveal) authorized by a browser session key, and wagers are drawn from a vault-backed arcade balance. There are no single-call play endpoints. MCP (/api/mcp) is the recommended surface for LLM agents and wraps the same flow; this reference is for direct HTTP integrators.
Quickstart
- Request a wallet challenge with
GET /api/wallet/challenge?wallet=<wallet-address>. - Sign the returned
messagewith that wallet; exchange it withPOST /api/wallet/sessionfor a bearer token. - Send
Authorization: Bearer <wallet-session-token>on caller-owned routes. - Set up Protocol V1 once: build a setup transaction (
POST /api/protocol/v1/player/setup-transaction) that initializes player state and authorizes your Ed25519 session key, then sign + send it with your wallet. - Fund your arcade balance: deposit by signing the setup/deposit transaction, or pay per top-up via x402 (
POST /api/protocol/v1/player/x402-deposit). - Play:
prepare→ sign the returnedbetPayloadHashwith the session key →commit→ sign the reveal message →reveal. The reveal response carries the settled outcome. - Cash out via the two-step withdrawal (
request→ keeperfinalize); read public activity/rankings/receipts as needed.
The same $TOKEN works as the bearer for MCP at /api/mcp. See MCP for tool-call payloads, which are the canonical play surface for agents.
Identity
Arcade Cafe uses the authenticated Solana wallet address as the account key. The API does not issue or expose a separate playerId. Public activity and rankings use short public aliases so raw wallet addresses are not exposed there.
Caller-owned routes require:
Authorization: Bearer <wallet-session-token>x402 funding (POST /api/protocol/v1/player/x402-deposit) also requires an x402 payment payload whose amount matches the requested top-up:
payment-signature: <x402-payment-payload>REST submissions are recorded with source api. Source is server-owned metadata, not a client parameter.
Errors
All failed requests use the coded envelope below. See src/content/docs/errors.md for the full code table and per-code recovery actions.
{
"error": { "code": "GAME_INPUT_REJECTED", "message": "Bet exceeds balance.", "details": {} }
}402 responses also include x402 negotiation fields (accepts, x402Version) at the top level so the caller can resubmit with a valid payment payload.
Retries And Idempotency
The Protocol V1 play flow is replay-safe by construction: a bet is bound to a betNonce/betPayloadHash, so re-sending the same commit or reveal resolves the same bet rather than creating a new wager. After a network error, re-GET the game session (GET /api/protocol/v1/sweeper/session, or replay the same reveal) to learn the settled state before acting.
x402 funding (POST /api/protocol/v1/player/x402-deposit) credits the arcade balance per accepted payment — always mint a fresh x402 payload on retry, as facilitators reject duplicates.
GAME_INPUT_REJECTED, 401, and 403 are terminal for the given input or token. Do not auto-retry; fix inputs or re-authenticate.
Pending Protocol V1 bets are recoverable. After a successful commit, clients should persist the raw client seed with POST /api/protocol/v1/dice/recovery-seed. If the browser refreshes before reveal, read GET /api/protocol/v1/dice/session; when it returns pendingBet.status = "awaiting_reveal", fetch the recovery seed and reveal the original bet instead of creating a new wager.
Wallet Authentication
GET/api/wallet/challenge
Creates a short-lived message for the wallet to sign.
Auth: none
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
wallet | query | string | Yes | Solana public key for the wallet creating the session. |
Response:
{
"walletAddress": "8x...abc",
"message": "https://example.com wallet login\n\nWallet: 8x...abc\nNonce: ...",
"nonce": "d827...",
"expiresAt": "2026-05-25T12:00:00.000Z"
}POST/api/wallet/session
Exchanges the signed challenge for a bearer token.
Auth: none
Body:
{
"walletAddress": "8x...abc",
"message": "<challenge-message>",
"signature": "<base64-ed25519-signature>"
}Response:
{
"token": "<wallet-session-token>",
"walletAddress": "8x...abc",
"expiresAt": "2026-05-26T12:00:00.000Z"
}Shared Response Shapes
Dice session:
{
"balance": 1,
"nonce": 0,
"serverSeedHash": "b7f4...9c21",
"recentRolls": []
}Sweeper session:
{
"balance": 1,
"nonce": 0,
"serverSeedHash": "d3c5...7f10",
"activeTicket": null,
"recentTickets": []
}Sweeper ticket:
{
"id": "ticket-id",
"status": "running",
"mode": "easy",
"betAmount": 0.01,
"clientSeed": "tower-seed",
"nonce": 0,
"rowsWon": 0,
"payout": 0,
"choices": [],
"serverSeedHash": "d3c5...7f10",
"createdAt": "2026-05-25T12:00:00.000Z"
}Finished Sweeper tickets also include revealed per-turn resultColumns and endedAt.
> In Protocol V1, serverSeedHash carries the operator RNG chain head (the > commitment for the hash-chain seed revealed on-chain), not a legacy HMAC server > seed. Per-bet randomness is verified from the on-chain RngReveal plus the > player client-seed commitment — see the Fairness guide.
House position:
{
"shares": 0,
"principal": 0,
"currentValue": 0,
"profit": 0,
"ownership": 0
}Payment object on paid responses:
{
"payment": {
"id": "payment-id",
"amount": 0.01
}
}Player Setup And Funding
Protocol V1 play requires one-time player setup plus a funded, vault-backed arcade balance. All transaction-building routes return an unsigned base64 transaction for the caller's wallet to sign and send; POST /api/protocol/v1/player/sync then reconciles the off-chain mirror from on-chain state.
| Route | Auth | Purpose |
|---|---|---|
GET /api/protocol/v1/player/status | bearer | On-chain player status (arcade balance, exposure, nonces). |
POST /api/protocol/v1/player/setup-transaction | bearer | Build a tx that initializes player state, authorizes a session key, and (optionally) deposits. Body: { amount, sessionPubkey }. |
POST /api/protocol/v1/player/x402-deposit | bearer + x402 | Credit the caller's vault-backed balance from an x402 payment matching amount. |
POST /api/protocol/v1/player/sync | bearer | Reconcile the off-chain mirror after a confirmed transaction. |
POST /api/protocol/v1/player/revoke-session-key-transaction | bearer | Build a tx revoking a session key. Body: { sessionPubkey }. |
Dice (Protocol V1)
Provably-fair Dice as a signed three-step flow. The caller holds an Ed25519 session key (authorized during setup) and signs each step client-side.
| Step | Route | Body |
|---|---|---|
| 1. Prepare | POST /api/protocol/v1/dice/prepare | `{ commitment, sessionPubkey, direction: "over"\ |
| 2. Commit | POST /api/protocol/v1/dice/commit | { betPayload, betPayloadHash, diceParams, sessionSignature } |
| 3. Reveal | POST /api/protocol/v1/dice/reveal | { betPayloadHash, clientSeedRaw, sessionSignature } |
commitmentis the SHA-256 commitment of your raw client seed; reveal it only in step 3.preparereturns the canonicalbetPayload+betPayloadHash+diceParams; sign the hash with your session key and pass it tocommit.commitreturns the operator ack (withackHash+ operator signature); sign the reveal message and callreveal.revealreturns the settled outcome, updated player state, and the bet-log entry used for fairness verification.
Sweeper (Protocol V1)
Running, multi-turn ticket. Stake is reserved while the ticket is open; each turn (choice or cashout) is the same prepare → commit → reveal flow.
| Route | Body |
|---|---|
GET /api/protocol/v1/sweeper/session | — (reads active ticket + session) |
POST /api/protocol/v1/sweeper/ticket | { mode, sessionPubkey, wager } |
POST /api/protocol/v1/sweeper/choice/prepare | { ticketId, commitment, sessionPubkey, row, choiceColumn } |
POST /api/protocol/v1/sweeper/cashout/prepare | { ticketId, commitment, sessionPubkey } |
POST /api/protocol/v1/sweeper/commit | { betPayload, betPayloadHash, sweeperParams, sessionSignature } |
POST /api/protocol/v1/sweeper/reveal | { betPayloadHash, clientSeedRaw, sessionSignature } |
A wallet can have only one active Sweeper ticket at a time. A losing choice or a board-clear settles the ticket; otherwise cashout settles it at the current payout.
House
GET/api/house
Reads public on-chain house state. NAV is vault_usdc − total_locked (LP equity net of player liabilities); sharePrice is NAV per LP token; openExposure is the committed active exposure. Values are read live from the Protocol V1 vault.
Auth: none
Response:
{
"house": {
"totalValue": 1000,
"baseBankroll": 0,
"gameNet": 12,
"volume": 0,
"apr": 0,
"maxExposure": 400,
"utilization": 0.1,
"totalStaked": 1000,
"sharePrice": 1.02,
"activeReserve": 600,
"idleReserve": 400,
"deployableToYield": 400,
"maxBet": 10,
"maxPayout": 100,
"openExposure": 40,
"strategy": "cash"
}
}GET/api/house/position
Reads the caller wallet's house position.
Auth: bearer wallet session
Response:
{
"position": {
"shares": 0,
"principal": 0,
"currentValue": 0,
"profit": 0,
"ownership": 0
}
}LP deposits/redemptions run through the Solana vault directly. Player funding is the arcade-balance flow (setup-transaction / x402-deposit); cash-out is the two-step withdrawal (request → keeper finalize).
Activity, Rankings, and Receipts
GET/api/activity
Reads recent public play activity.
Auth: none
| Parameter | In | Type | Default | Description |
|---|---|---|---|---|
game | query | all, dice, or sweeper | all | Filters activity by game. |
limit | query | number | 20 | Number of latest plays/events to return. |
Response:
{
"generatedAt": "2026-05-25T12:00:00.000Z",
"game": "all",
"latestPlays": [],
"latestEvents": [],
"topWagers": [],
"topActors": [],
"totals": {
"plays": 0,
"wagered": 0,
"paidOut": 0,
"houseNet": 0
},
"bankroll": {
"totalValue": 1000
}
}GET/api/activity/stream
Streams public activity with Server-Sent Events.
Auth: none
| Parameter | In | Type | Default | Description |
|---|---|---|---|---|
game | query | all, dice, or sweeper | all | Filters streamed play events by game. |
Events:
event: snapshot
data: { "type": "snapshot", "snapshot": { ... } }
event: play
data: { "type": "play", "play": { ... } }GET/api/rankings
Reads public rankings derived from settled receipts.
Auth: none
| Parameter | In | Type | Default | Description |
|---|---|---|---|---|
game | query | all, dice, or sweeper | all | Filters by game. |
source | query | all, api, or mcp | all | Filters by submission source. |
metric | query | wagered, profit, roi, plays, or winRate | profit | Sort metric. |
Response:
{
"generatedAt": "2026-05-25T12:00:00.000Z",
"game": "all",
"source": "all",
"metric": "profit",
"actors": [],
"totals": {
"plays": 0,
"wagered": 0,
"paidOut": 0,
"houseNet": 0
}
}GET/api/stats
Reads bucketed public stats.
Auth: none
| Parameter | In | Type | Default | Description |
|---|---|---|---|---|
game | query | all, dice, or sweeper | all | Filters by game. |
period | query | day, month, or year | day | Bucket period. |
Response:
{
"generatedAt": "2026-05-25T12:00:00.000Z",
"game": "all",
"period": "day",
"totals": {
"plays": 0,
"wagered": 0,
"paidOut": 0,
"houseNet": 0
},
"bankroll": {
"totalValue": 1000
},
"buckets": []
}GET/api/receipts/:id
Reads one settled play receipt by id.
Auth: none
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Receipt id returned from activity or stored after settlement. |
Response:
{
"receipt": {
"id": "play-id",
"receiptId": "play-id",
"game": "dice",
"gameId": "dice:protocol-v1",
"player": "P-1a2b3c",
"actorAlias": "P-1a2b3c",
"source": "api",
"betAmount": 0.01,
"payout": 0.0198,
"profit": 0.0098,
"status": "won",
"input": {},
"result": {},
"proof": {},
"anchor": {
"status": "pending_commit",
"commitId": "0",
"commitSignature": null,
"commitTransactionUrl": null,
"commitAccount": null,
"commitExplorerUrl": null,
"betLogEntryHash": "abc...",
"betLogRoot": null,
"betLogIndex": null,
"betLogProof": [],
"entryCount": null,
"stateRoot": null
},
"verifierPayload": {},
"createdAt": "2026-05-25T12:00:00.000Z"
}
}Use the receipt proof and verifierPayload fields with the fairness guide or standalone verifier. Arcade Cafe publishes proof material instead of hosting a verification endpoint. anchor.status is pending_commit until a Protocol V1 commit posts the batch root on Solana. Once anchored, commitTransactionUrl, commitAccount, betLogRoot, and betLogProof connect the individual receipt to the on-chain commit.
GET/api/protocol/v1/journal/manifest/latest
Reads the latest signed hash manifest over the public Protocol V1 DA journal. The manifest is not a substitute for on-chain commits; it is a tamper-evident checkpoint for the interval between commits.
Auth: none
Response:
{
"manifest": {
"version": 1,
"eventCount": 42,
"eventRoot": "abc...",
"firstEventId": "ack:...",
"lastEventId": "bet-log:...",
"previousManifestHash": "def...",
"manifestHash": "123...",
"operatorPublicKey": "456...",
"signature": "...",
"createdAt": "2026-06-01T12:00:00.000Z"
}
}GET/api/protocol/v1/dice/recovery-seed
Fetches the caller-owned raw client seed for a pending Dice reveal. This is only available after the operator ack has fixed the bet inputs.
Auth: wallet session bearer
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
betPayloadHash | query | string | Yes | Pending Dice bet payload hash. |
POST/api/protocol/v1/dice/recovery-seed
Stores the caller-owned raw client seed after a successful Dice commit, so the same browser session can recover and reveal after refresh or interrupted network responses.
Auth: wallet session bearer
{
"betPayloadHash": "abc...",
"clientSeedRaw": "32-byte-hex"
}