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 (preparecommitreveal) 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

  1. Request a wallet challenge with GET /api/wallet/challenge?wallet=<wallet-address>.
  2. Sign the returned message with that wallet; exchange it with POST /api/wallet/session for a bearer token.
  3. Send Authorization: Bearer <wallet-session-token> on caller-owned routes.
  4. 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.
  5. 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).
  6. Play: prepare → sign the returned betPayloadHash with the session key → commit → sign the reveal message → reveal. The reveal response carries the settled outcome.
  7. Cash out via the two-step withdrawal (request → keeper finalize); 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

ParameterInTypeRequiredDescription
walletquerystringYesSolana 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.

RouteAuthPurpose
GET /api/protocol/v1/player/statusbearerOn-chain player status (arcade balance, exposure, nonces).
POST /api/protocol/v1/player/setup-transactionbearerBuild a tx that initializes player state, authorizes a session key, and (optionally) deposits. Body: { amount, sessionPubkey }.
POST /api/protocol/v1/player/x402-depositbearer + x402Credit the caller's vault-backed balance from an x402 payment matching amount.
POST /api/protocol/v1/player/syncbearerReconcile the off-chain mirror after a confirmed transaction.
POST /api/protocol/v1/player/revoke-session-key-transactionbearerBuild 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.

StepRouteBody
1. PreparePOST /api/protocol/v1/dice/prepare`{ commitment, sessionPubkey, direction: "over"\
2. CommitPOST /api/protocol/v1/dice/commit{ betPayload, betPayloadHash, diceParams, sessionSignature }
3. RevealPOST /api/protocol/v1/dice/reveal{ betPayloadHash, clientSeedRaw, sessionSignature }
  • commitment is the SHA-256 commitment of your raw client seed; reveal it only in step 3.
  • prepare returns the canonical betPayload + betPayloadHash + diceParams; sign the hash with your session key and pass it to commit.
  • commit returns the operator ack (with ackHash + operator signature); sign the reveal message and call reveal.
  • reveal returns 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.

RouteBody
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

ParameterInTypeDefaultDescription
gamequeryall, dice, or sweeperallFilters activity by game.
limitquerynumber20Number 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

ParameterInTypeDefaultDescription
gamequeryall, dice, or sweeperallFilters 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

ParameterInTypeDefaultDescription
gamequeryall, dice, or sweeperallFilters by game.
sourcequeryall, api, or mcpallFilters by submission source.
metricquerywagered, profit, roi, plays, or winRateprofitSort 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

ParameterInTypeDefaultDescription
gamequeryall, dice, or sweeperallFilters by game.
periodqueryday, month, or yeardayBucket 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

ParameterInTypeRequiredDescription
idpathstringYesReceipt 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

ParameterInTypeRequiredDescription
betPayloadHashquerystringYesPending 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"
}