What it is
A /api/dice endpoint for game developers. Pass a dice expression (3d6, 2d20+5, 1d100-10) and get back each die, the modifier and the total. Most dice rollers call Math.random() on a server and ask players to trust it. This one publishes a proof with every roll.
A roll and its proof
This is what a player can check. The three values in the steps are all a verifier needs, and the replay button recomputes the roll right here, without calling Provable.
{
"clientSeed": "player_42_attack_roll",
"nonce": 0,
"serverHash": "10d765b471c142b74f45f838f0443d4b72a28946ad8d5fdf4c3586d77380499a",
"outcome": {
"notation": "4d6",
"rolls": [6, 1, 5, 5],
"modifier": 0,
"total": 17
}
}- Before the roll, the server commits to a seedPublish this hash before the roll, so the seed can't be swapped afterwards.
serverHash = sha256(serverSeed) = 10d765b471c142b74f45f838f0443d4b72a28946ad8d5fdf4c3586d77380499a - The roll is a pure function of both seeds
HMAC-SHA256(serverSeed, "player_42_attack_roll:0:0") → 4d6 = [6, 1, 5, 5], total 17 - After the roll, the seed is revealed and anyone can replay it
serverSeed = 5709b54366035ad56ac3bc4789fd53324dc3fea3df2ebbc554f536aea927aaef
For rolls made through the API, players paste the clientSeed and serverHash into /verify, which checks them against the stored record.
Try it live
curl "https://api.provable.io/api/dice?clientSeed=player_42_attack_roll¬ation=4d6"Two d20s plus a +5 modifier (the total is the sum of both dice):
curl "https://api.provable.io/api/dice?clientSeed=player_42_round_3¬ation=2d20%2B5"Supported notation
- NdM: N dice of M sides.
1d20,3d6,2d100. - NdM+K / NdM-K: flat modifier added to the total.
1d20+5,2d6-1. - Bounds:
1 ≤ count ≤ 100,2 ≤ sides ≤ 1,000,000. Enough for percentile dice, exotic polyhedra and stat blocks.
Integration snippet
// Roll 3d6 for a D&D stat block
const url = new URL("https://api.provable.io/api/dice");
url.searchParams.set("clientSeed", `char_${characterId}_str`);
url.searchParams.set("notation", "3d6");
const res = await fetch(url, {
headers: { "x-api-key": process.env.PROVABLE_KEY }
});
const { outcome, serverHash } = await res.json();
// outcome.rolls = [4, 6, 3]
// outcome.total = 13
console.log(outcome.rolls, "→", outcome.total);
// Persist serverHash with the character so anyone can re-verify later.How each die is drawn
Each die reads 4 bytes of the HMAC stream as an unsigned 32-bit number u and takes the high 32 bits of u × sides, plus one. The few values of u that would favour low faces are redrawn, so there is no modulo bias. The modifier is added to the total only, so each die your UI shows matches the verified result. The long version, with code, is in Dice rolls & coin flips.
Where it fits
- Online TTRPG and VTT platforms and Discord bots, anywhere players need to trust the dice.
- Craps and sic bo games that show players the hash before the throw.
- "Roll to enter" giveaways on community platforms.
- Streamer overlays where chat watches the roll and can check it afterwards.
Related
FAQ
What dice notation does the Provable.io dice roller API support?
Standard NdM notation with optional flat modifier: 1d20, 3d6, 2d100, 1d20+5, 2d6-1, 4d6. Pass the expression as the notation query parameter on /api/dice.
What are the bounds for count and sides?
1 ≤ count ≤ 100 and 2 ≤ sides ≤ 1,000,000. That's enough for percentile dice, exotic polyhedra, and full stat blocks in a single call.
How do players verify a roll later?
Show the player the clientSeed, the serverHash, and the published roll. They paste the trio into /verify, which recomputes the HMAC stream, applies the dice mapping, and confirms (or refutes) the published result.
Why publish the serverHash before the player rolls?
Publishing the hash before the player picks their clientSeed eliminates the "but you knew my seed when you generated yours" attack. The server is committed to a seed it can't later swap.