Quick reference for the Cymba API. All endpoints live under /api/v1/.
All API requests require a Bearer token. Generate keys from the dashboard.
Authorization: Bearer cy_live_your_api_key_here
Rate limits and spin quotas are enforced per key. The response includes X-RateLimit-Limit and X-RateLimit-Remaining headers.
Resolve a spin. Pass a stored config ID or an inline config object.
{
"config": "my-config-id", // string (stored ID) or object (inline config)
"lines": 20, // integer, min: 1 — number of active paylines
"client_seed": "optional" // string, auto-generated if omitted
}
{
"result": {
"round_id": "01JQ5K8X...", // unique time-ordered identifier
"spins": [
{
"type": "base",
"reel_positions": [
["A", "wild", "K"],
["K", "A", "A"],
["J", "wild", "K"]
],
"wins": [{
"type": "payline",
"line": 1,
"payline": [0, 0, 0, 0, 0],
"win_symbol_id": "A",
"matching_symbols": 4,
"win_multiplier": 25,
"has_wild": true
}],
"active_reel_count": 5, // only present for dynamic reel count games
"reel_heights": [3,5,4,2,6], // per-reel visible symbols (dynamic reels)
"ways_count": 360, // ways-to-win (dynamic reels/reel count)
"symbol_features": { ... } // wild/mystery/stacked features applied
}
],
"summary": {
"total_multiplier": 25,
"win_capped": false,
"breakdown": { // multiplier by source
"payline": 25,
"scatter": 0
}, // hold_respin, free_spins: present when > 0
"cascade_count": 0, // number of cascades in this spin
"hold_respin_count": 0, // number of hold respins
"free_spin_count": 0, // number of free spins played
"free_spins_retriggered": 0 // free spins won during free spins
},
"result_hash": "d4e5f6..." // HMAC-SHA256 tamper-evident hash
},
"provably_fair": {
"server_seed_hash": "a3f1c8...",
"client_seed": "b7c29e...",
"nonce": 42
}
}
The type field determines which extra fields appear on each win line.
Ways win
{
"type": "ways",
"win_symbol_id": "K",
"matching_symbols": 4,
"win_multiplier": 10,
"has_wild": false,
"ways_count": 12,
"reel_counts": [2, 1, 3, 2]
}
Cluster win
{
"type": "cluster",
"win_symbol_id": "A",
"matching_symbols": 7,
"win_multiplier": 15,
"has_wild": true,
"cluster_size": 7,
"cluster_positions": [[0,0],[0,1],[1,0],[1,1],[1,2],[2,1],[2,2]]
}
Entries with "type": "cascade" appear in the spins[] array when cascading reels trigger. Summary includes cascade_count.
{
"type": "cascade",
"iteration": 1,
"reel_positions_before": [["A","wild","K"], ...], // grid before cascade removal
"reel_positions": [["A","K","Q"], ...], // grid after cascade fill
"wins": [{ ...win object... }], // wins from this iteration
"positions_removed": [[0,2],[],[1],[],[]], // per-reel row indices removed
"iteration_multiplier": 2 // cascade progression multiplier
}
Entries with "type": "hold_respin" appear in the spins[] array when the feature triggers. Summary includes hold_respin_count; the multiplier contribution is at summary.breakdown.hold_respin.
{
"type": "hold_respin",
"respin_index": 1,
"reel_positions": [["bonus","K","Q"], ...], // grid after respin
"wins": [], // no payline evaluation during respins
"locked_positions": [[0],[],[1,2],[],[]], // per-reel locked row indices
"locked_count": 3, // total locked trigger symbols
"new_triggers_landed": true, // whether new triggers appeared
"respins_remaining": 3 // respins left (resets on new triggers)
}
Entries with "type": "free_spin" appear in the spins[] array when scatter symbols award free spins. The entire sequence is resolved in a single API call. Summary includes free_spins_won, free_spin_count, and free_spins_retriggered; the multiplier contribution is at summary.breakdown.free_spins.
{
"type": "free_spin",
"spin_index": 0,
"spins_remaining": 9,
"reel_positions": [["A","K","wild"], ...],
"wins": [...win objects...], // flat array, same shape as base spin
"win_total": 20.0 // this free spin's total multiplier
}
// Cascades within free spins include parent_type + parent_spin_index:
{
"type": "cascade",
"parent_type": "free_spin",
"parent_spin_index": 0,
"iteration": 1,
// ... same fields as any cascade entry
}
Control which direction paylines are evaluated using the win_evaluation.direction config field.
"win_evaluation": {
"direction": "ltr" // "ltr" (default), "rtl", or "both"
}
"ltr" — left-to-right only (default). "rtl" — right-to-left only. "both" — evaluates paylines in both directions with full-line deduplication to prevent double-counting.
RTL wins use negative line numbers (e.g. -1, -2) to distinguish them from LTR wins in the response.
Present on the base spin entry at spins[0].symbol_features when any symbol transformations occurred. Each sub-field is only included if non-empty.
"symbol_features": {
"expanded_reels": { // reel index → rows filled with wild
"2": [0, 1, 2]
},
"sticky_positions": { // wilds persisted from prior free spin
"1": [1],
"3": [0, 2]
},
"stacked_positions": { // multi-row stacked symbols
"0": [0, 1]
},
"mystery_resolutions": { // [reel][row] → resolved symbol
"0": { "0": "A", "2": "A" },
"3": { "1": "A" }
},
"mystery_uniform_symbol": "A", // only if uniform resolve mode
"wild_multipliers": { // [reel][row] => assigned multiplier value
"1": { "0": 3, "2": 2 }
}
}
Wilds can carry random multiplier values that multiply wins they participate in. Configure by adding multiplier_values to any symbol with role: "wild".
// Symbol config
{
"id": "wild",
"role": "wild",
"multiplier_values": [2, 3, 5], // possible multiplier values
"multiplier_weights": [50, 30, 20] // optional — uniform if omitted
}
Payline mode: when multiple multiplier wilds appear in a winning line, the product of their multipliers is applied to the win.
Ways mode: each wild contributes its multiplier value instead of 1 to the effective ways calculation, amplifying the total ways count.
Present on the base spin entry at spins[0].reel_heights and spins[0].ways_count when the game config uses dynamic reels.
"reel_heights": [3, 5, 4, 2, 6, 3], // visible rows per reel this spin
"ways_count": 2160 // product of reel heights (3×5×4×2×6×3)
Enable variable number of reels per spin via reels.dynamic_reel_count. When active, each spin entry includes active_reel_count.
"reels": {
"dynamic_reel_count": {
"enabled": true,
"min_reels": 3, // minimum active reels
"max_reels": 6, // maximum active reels
"trigger": "per_spin", // "per_spin" or "on_cascade_win"
"reel_count_weights": { // optional — uniform if omitted
"3": 10, "4": 25, "5": 40, "6": 25
}
}
}
"per_spin" — reel count is randomized on every spin. "on_cascade_win" — reel count can increase when a cascade win occurs.
Validate a game config JSON object without resolving a spin.
// Request
{ "config": { ... your game config object ... } }
// Success → 200
{ "valid": true }
// Failure → 422
{ "valid": false, "error": "Invalid symbol frequency..." }
Get the observed RTP for a stored game config, calculated from all logged spins.
{
"config_id": "my-config-id",
"total_spins": 12847,
"rtp": 95.6582
}
Returns "rtp": null if no spins have been logged yet for this config.
Rotate the server seed for provably fair verification. Reveals the previous server seed so clients can verify past spins, then generates a new seed and resets the nonce.
{
"previous_server_seed": "e8d4a1...", // verify: sha256(this) === old server_seed_hash
"new_server_seed_hash": "7b2f9c..." // committed hash for upcoming spins
}
Verify a previous spin by reproducing it from its provably fair seeds. Returns the same result the spin would have produced, plus the server seed hash for comparison.
// Request
{
"server_seed": "e8d4a1...", // revealed after seed rotation
"client_seed": "b7c29e...", // from the original spin
"nonce": 42,
"config": "my-config-id",
"lines": 20
}
// Response
{
"server_seed_hash": "3e1c7d...", // compare with committed hash
"result": { ... same as /spin result ... },
"verified": true
}
Verification flow: (1) save the server_seed_hash and result_hash from each spin, (2) call POST /seed/rotate to reveal the server seed, (3) call POST /verify with the revealed seed to reproduce the spin, (4) confirm sha256(server_seed) === server_seed_hash, (5) confirm the result_hash matches by recomputing it with the canonical encoding below (a plain json_encode will not match — PHP doesn't guarantee key order and drops trailing zeros on floats).
// PHP — canonical encoding for result_hash verification
function canonicalize($value) {
if (!is_array($value)) return $value;
foreach ($value as $k => $v) $value[$k] = canonicalize($v);
if (!array_is_list($value)) ksort($value); // recursive key-sort, lists untouched
return $value;
}
$payload = $result;
unset($payload['result_hash']); // strip the hash field
$canonical = json_encode(
canonicalize($payload),
JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRESERVE_ZERO_FRACTION
);
$expected = hash_hmac('sha256', $canonical, $server_seed);
$verified = hash_equals($expected, $result['result_hash']);
Every spin response includes fields for tamper-evident audit trails.
"round_id": "01JQ5K8X..." // unique time-ordered identifier for each game round
"result_hash": "d4e5f6..." // HMAC-SHA256 tamper-evident hash
round_id is a unique, time-ordered identifier assigned to each game round — useful for logging, dispute resolution, and correlating spins across systems.
result_hash is an HMAC-SHA256 hash computed with the server seed over the canonically-encoded result payload (excluding the hash itself). Store it alongside your own records to detect any post-hoc tampering. See the verification flow above for the exact canonical encoding (recursive key-sort + JSON_PRESERVE_ZERO_FRACTION) — a naive json_encode will not match.
Cymba returns multipliers, not monetary amounts. The engine does not handle money — that is the client's responsibility.
To compute a payout from the engine's result:
// Per-line win
line_payout = win_multiplier × bet_per_line × denomination
// Total payout
total_payout = total_multiplier × bet_per_line × denomination