EN
Play demos

API · v1

API documentation

The operators’ API and the wallet protocol, version 1.

Overview

Callisto Minis are short multiplayer games: two to four players at a table, each stakes, the game decides, and the pot less the configured rake goes to the winners. You launch your players into a game; the provider calls your wallet for every stake and every payout. The integration is two pieces:

  • The operators’ API, which you call: launch a player, list your games, read rounds.
  • Your wallet, which we call: balance, bet, win, refund.

Environments

Operators’ APIOpenAPI
Sandboxhttps://sandbox-api.callistominis.com/v1/v1/openapi.json
Productionhttps://api.callistominis.com/v1/v1/openapi.json

A sandbox key works in the sandbox only. You move to production with a new key.

Signing

Every call, both ways, carries three headers:

X-Minis-Key:       key_id
X-Minis-Timestamp: unix seconds, within 5 minutes of the receiver’s clock
X-Minis-Signature: hex_lowercase(HMAC_SHA256(secret, timestamp + "." + payload))

payload is the raw body exactly as sent. A request without a body signs its request line, for example GET /v1/games or GET /v1/players/p-1/rounds?limit=10. Our calls to your wallet are signed with your key, so you check them with the same secret. Keep the secret on your server only; you can hold two active keys, so you rotate by asking for a second one and dropping the first.

import { createHmac } from 'node:crypto'

const timestamp = Math.floor(Date.now() / 1000).toString()
const body = JSON.stringify(payload)            // exactly the bytes you send
const signature = createHmac('sha256', secret)
  .update(timestamp + '.' + body)
  .digest('hex')

headers['X-Minis-Key'] = keyId
headers['X-Minis-Timestamp'] = timestamp
headers['X-Minis-Signature'] = signature

Operators’ API

Answers are { "data": ... }.

RouteWhat it does
GET /v1/pingWho the signature says you are: operator_code, environment.
GET /v1/gamesThe games your players can open now: game_id, name, type, min_seats, max_seats, real_money.
POST /v1/sessionsOpen a game for a player; answers the game_url.
POST /v1/games/{game_id}/demoA demo with coins and bots, no wallet. Body { "language": "en" } or empty.
GET /v1/rounds/{round_id}One round with the seats of your players: session_id, player_alias, stake, rake, payout. Other operators’ players at a shared table are not shown.
GET /v1/players/{player_id}/roundsA player’s rounds, newest first, by your own id for the player; offset, limit (50). Answers { items, total }.

Game ids: snake, sharks, bombs, seabattle.

Launching a player

POST /v1/sessions
{
  "game_id": "snake",
  "player_id": "p-1",
  "player_name": "Ann",
  "currency": "EUR",
  "token": "<your token for this player>",
  "language": "en",
  "exit_url": "https://casino.example/lobby",
  "cashier_url": "https://casino.example/cashier"
}
{
  "data": {
    "session_id": "6f1c…",
    "game_url": "https://snake.callistominis.com/?session=…",
    "expires_at": "2026-10-08T22:00:00Z"
  }
}

Open game_url in the player’s frame. The token you pass comes back to your wallet with every call of this session, so you know which player and session a bet belongs to.

Your wallet

The provider calls POST {wallet_url}/{call} with JSON in snake_case, signed with your key. Check the signature and the timestamp before anything else, and refuse a bad one with 401 and an error. Every call carries session_id, token, player_id and currency.

CallDoes
balanceNothing; answers the balance.
betTakes amount.
winPays amount, which may be 0.
refundGives back the bet bet_transaction_id.

Money calls add:

transaction_idThe provider’s id of this movement, a UUID. The same id twice is the same movement.
round_idThe round it belongs to.
game_idThe game.
amountDecimal, two places, never negative.
bet_transaction_idOn win and refund: the bet it closes.
POST {wallet_url}/bet
{
  "session_id": "6f1c…",
  "token": "<your token>",
  "player_id": "p-1",
  "currency": "EUR",
  "transaction_id": "0b7e…",
  "round_id": "snake-…",
  "game_id": "snake",
  "amount": 5.00
}

Wallet answers

  • Done: 2xx with the balance and your id for the movement.
  • Refused: 4xx with error and message. The provider understands insufficient_funds and invalid_token; any other code is a refusal too.
  • Anything else (5xx, no JSON, no answer within 5 seconds) is unknown: you may or may not have done it.
200 { "balance": 995.00, "transaction_id": "<your id for it>" }
402 { "error": "insufficient_funds", "message": "Not enough money" }

Wallet rules

  1. Idempotent by transaction_id. A call you have already done is answered as done, with the same answer, and moves nothing. The provider repeats calls whose answer it did not get.
  2. A refund of a bet you never received succeeds and moves nothing. It happens when your answer to a bet was lost.
  3. Never refuse a win or a refund short of a broken request. A refused credit goes to manual handling; an unknown one is retried, 5 seconds at first, doubling, at most an hour apart, until it gets through.
  4. A bet with no answer is given up. After 5 seconds the round does not start; after two minutes the provider sends a refund for that bet, in case you did take it.
  5. Stakes of a round are all or none. When another player’s bet is refused, yours is refunded.

Money in a round

Every player’s stake is taken before the round starts. When the game ends, each player gets a win, of 0 when they lost; a cancelled round gives each stake back as a refund. The rake is a percentage of the pot configured for your operator, and a payout never exceeds the pot less the rake. In the shared pool your players may win money staked by another operator’s players; who owes whom is settled monthly on your invoice.

Errors

Every refusal of the operators’ API is { error, message, retryable, fields? }. Retry only when retryable is true.

StatuserrorWhen
401unauthorizedSignature missing or wrong, a revoked key, a suspended operator; retryable only for the clock.
404not_foundAn unknown game, player or round, or not yours.
409currency_not_enabled, game_not_available, … or conflictA business refusal.
422validation_failed, with fieldsThe request is malformed.
503unavailableA service behind did not answer; retryable.

Self-test and going live

The integration kit has a self-test that signs like we do and checks your wallet: signature and clock refusals, balance, bet, win, refund, repeats, a refund of a bet you never received, insufficient funds, a foreign token, and that each answer comes within 5 seconds. It moves a few units of the test player’s money and leaves the balance where it found it.

node selftest/wallet-selftest.mjs --wallet-url https://wallet.example/minis \
  --key-id <key_id> --secret <secret> \
  --player <a test player> --token <a valid token of that player> --currency EUR
  1. Every self-test line is PASS.
  2. On the sandbox, launch two players of yours and play a round in two browsers; your wallet sees two bets and two wins, and GET /v1/rounds/{round_id} agrees.
  3. Stop your wallet during a bet: the round does not start, and the bet is refunded within two minutes.
  4. We move you to production with a new key.

Bring the minis to your players

Tell us about your casino and how you want to connect. We will send sandbox keys and the integration kit.

Contact us sales@callistoplatform.com