Dice, exactly
Full dice notation for bots, chat servers and game nights: 4d6kh3, 2d20kh1+5, exploding dice, rerolls, Fudge dice. Every roll shows its work, and every expression has its odds worked out to the last fraction.
Fair dice, shown working
Every die goes through its modifiers in one order: reroll, then explode, then keep or drop, then count successes. An exploding die adds a new die to the pool, which can be rerolled, dropped or explode again like any other. The answer lists every die as it landed, which ones were kept, and what each term adds up to.
Without a seed, dice come from PHP's random_int, the system's secure generator, so nobody can steer a roll. With seed, the same seed and expression give the same roll every time, which makes a roll shareable as a link. Seeded rolls are reproducible on purpose, so do not use them where fairness matters.
The notation
Terms are dice or whole numbers, joined by + and -. Modifiers stick to their dice, in any order. Upper or lower case, it does not matter.
| Write | Example | Means |
|---|---|---|
NdM | 3d6 | N dice with M sides. N is 1 to 100 and can be left out (d20 is 1d20); M is 2 to 1000. |
d% | 2d% | Percentile dice, the same as d100. |
NdF | 4dF | Fudge dice, as in Fate: two minus faces, two blank, two plus, so each one is -1, 0 or +1. |
+ - | 1d20+5 | Add or take away dice and constants. Up to 10 terms. |
khN, kN | 4d6kh3 | Keep the highest N. k3 is kh3. |
klN | 2d20kl1 | Keep the lowest N. |
dlN, dN | 4d6dl1 | Drop the lowest N. d1 is dl1. |
dhN | 5d6dh2 | Drop the highest N. |
! | 3d6! | Explode: a die on its highest face adds another die, which can explode too. |
rN, r<N | 2d6r1 | Reroll once when the die matches: r1, r<3, r>=5. ro means the same. |
rrN | 4d6rr1 | Reroll until it stops matching. |
>=N, >N, <=N, <N, =N | 10d6>=5 | Count successes instead of adding up: every kept die that matches counts 1. |
In a URL query a literal + arrives as a space, so a space between terms reads as + and ?expr=4d6kh3+2 just works. To be exact, send %2B. And d% is d%25 in a URL, or simply write d100.
crit is natural_20 or natural_1 when the roll is a single d20 that keeps one die: 1d20+5, or 2d20kh1 for advantage (roll a second d20 and use the higher) and 2d20kl1 for disadvantage. Otherwise it is null.
200 characters, 10 terms, 100 dice in all. Explosions may add up to 100 more dice per die and 1,000 per term; even the likeliest explosion chains 100 times about once in three trillion. rr gives up after 20 tries and picks one of the other faces directly, which is the same thing, only faster.
4d6kh3 rolls a D&D ability score (four dice, the best three). 2d20kh1 is advantage. 4dF is a Fate roll. 10d10>=8 counts hits in a dice pool.
Endpoints
| Method | Path | What you get |
|---|---|---|
| GET | /v1/dice/roll | A roll: every die, what was kept and dropped, each term's subtotal, the total, and the crit. Takes seed, times and format=text. |
| GET | /v1/dice/odds | Every total the expression can roll, with its probability, percent, exact fraction and the chance of rolling at least or at most it. Mean, standard deviation, min, max, the most likely total. Takes format=text. |
| GET | /v1/dice/roll/image | The roll as a picture: d6s with pips, other dice as their shapes, dropped dice crossed out, the total big. SVG or PNG. Takes seed. |
| GET | /v1/dice/odds/image | The odds as a bar chart, SVG or PNG. |
Parameters
| Name | Example | Does |
|---|---|---|
expr | 4d6kh3%2B2 | The dice, in the notation above. Default 1d20. |
seed | gamenight | Rolls only. 1 to 64 letters, digits, - or _. The same seed rolls the same dice, and the answer carries a permalink. |
times | 6 | /roll only, 1 to 20. Rolls that many times; data becomes a list and meta.totals lists the totals. |
format | text | text for one plain line per roll, on /roll and /odds. On the image endpoints, svg (default) or png. |
Calls
$ curl "https://api.tasta.space/v1/dice/roll?expr=4d6kh3%2B2"
$ curl "https://api.tasta.space/v1/dice/roll?expr=1d20+5&format=text"
$ curl "https://api.tasta.space/v1/dice/roll?expr=4d6kh3×=6"
$ curl "https://api.tasta.space/v1/dice/odds?expr=2d20kh1&format=text"
A roll looks like this
With seed=gamenight, so it is the same on every call. As text: 4d6kh3+2 = 16 (5, 6, [2], 3). Dropped dice are in brackets, a die that exploded has a !, a counted success a *. Terms with those modifiers also carry exploded (extra dice), rerolled (the faces rerolled away) and successes.
{
"data": {
"expr": "4d6kh3+2",
"total": 16,
"terms": [
{
"dice": "4d6kh3",
"rolls": [5, 6, 2, 3],
"kept": [5, 6, 3],
"dropped": [2],
"subtotal": 14
},
{
"constant": 2
}
],
"crit": null,
"seed": "gamenight"
},
"meta": {
"permalink": "https://api.tasta.space/v1/dice/roll?expr=4d6kh3%2B2&seed=gamenight",
"image": "https://api.tasta.space/v1/dice/roll/image?expr=4d6kh3%2B2&seed=gamenight",
"odds": "https://api.tasta.space/v1/dice/odds?expr=4d6kh3%2B2"
}
}
The odds look like this
at_least is the chance of rolling that total or more (handy against a target number), at_most that total or less. mode is the likeliest total; when several tie, the lowest, and mode_ties says how many share it.
{
"data": {
"expr": "2d4",
"method": "exact",
"samples": null,
"floor": 2,
"ceiling": 8,
"open_ended": false,
"unlisted": 0,
"min": 2,
"max": 8,
"mean": 5,
"sd": 1.581139,
"mode": 5,
"mode_percent": 25,
"mode_ties": 1,
"distribution": [
{"total": 2, "probability": 0.0625, "percent": 6.25, "fraction": "1/16", "at_least": 1, "at_most": 0.0625},
{"total": 3, "probability": 0.125, "percent": 12.5, "fraction": "1/8", "at_least": 0.9375, "at_most": 0.1875},
{"total": 4, "probability": 0.1875, "percent": 18.75, "fraction": "3/16", "at_least": 0.8125, "at_most": 0.375},
{"total": 5, "probability": 0.25, "percent": 25, "fraction": "1/4", "at_least": 0.625, "at_most": 0.625},
{"total": 6, "probability": 0.1875, "percent": 18.75, "fraction": "3/16", "at_least": 0.375, "at_most": 0.8125},
{"total": 7, "probability": 0.125, "percent": 12.5, "fraction": "1/8", "at_least": 0.1875, "at_most": 0.9375},
{"total": 8, "probability": 0.0625, "percent": 6.25, "fraction": "1/16", "at_least": 0.0625, "at_most": 1}
]
}
}
How the odds are worked out
Sums are exact. Each term's distribution is built die by die and the terms are combined, so 2d6 gives 7 exactly 1/6. Fractions are given while the count of outcomes fits in 253; past that, probabilities are decimals to ten significant digits.
Keep and drop are exact too, by going through every sorted hand of dice once, weighted by the number of ways it can land: 4d6kh3 averages 15869/1296, about 12.24, and 10d10kh3 takes 92,378 hands instead of ten billion rolls.
Exploding dice are followed until a longer chain is less likely than one in a trillion; unlisted says how much probability that leaves out.
Floor and ceiling are the lowest and highest totals possible at all, rerolls, keeps and signs included. Explosions that can keep adding to the total leave one of them null and make open_ended true; 1d6!kh1 still tops out at 6. min and max are the lowest and highest totals in the list.
Anything bigger, and exploding dice that are also kept or dropped, is rolled a few thousand times instead: method says simulated and samples how many rolls. The seed comes from the expression, so the same question always gets the same answer. Past 1000 different totals, the list comes as buckets of bucket_size totals.
Errors of its own
On top of the hub's errors. Notation mistakes say what to fix, and sometimes have opinions.
| Status | Code | When |
|---|---|---|
| 422 | invalid_parameter | Notation that does not parse or breaks a limit (d1: "A d1 is a marble."), a times out of range, a bad seed or format |
Try it
Where the facts come from
No dataset here: every roll and every probability is computed on the spot. The few game facts on this page are checked against: the Fate Core SRD for Fudge dice, the D&D 5e Basic Rules on advantage and disadvantage, and Aarhus University's physics colloquium on D&D dice for rolling four d6 and keeping three.
Dice notation has no single standard. This one follows what most dice bots and virtual tabletops share; where they differ, r rerolls once here and rr keeps going.