KenoSpots Keno API

Four free JSON APIs: live keno and Quick Draw results across 21 US states, DC, and Canada, structured paytables and game metadata, and a simulator that runs draws against any real state paytable. No API key required to get started.

KenoSpots aggregates official state and provincial lottery feeds into one clean, predictable JSON API. Whether you are building an app, a dashboard, or an AI agent that needs structured keno data, the results endpoint gives you the latest draws in a single request and the simulator endpoint answers "what would happen if I played these numbers" with exact odds and a quotable summary.

Quickstart

Fetch the latest Massachusetts Keno draws with a single GET request. No authentication, no headers required.

# Latest Massachusetts Keno results
curl https://app.kenospots.com/api/results/mass-keno

The response is a JSON object with a draws array, newest draw first. Read on for the full field reference and the complete list of supported games.

Endpoint

There is one endpoint. Swap the {slug} for any supported game (see Game slugs).

GET https://app.kenospots.com/api/results/{slug}

Base URL

https://app.kenospots.com

OpenAPI specification

Prefer a machine-readable contract? The full endpoint, schema, and examples are published as an OpenAPI 3.0 spec. Import it into Postman, Swagger UI, or your code generator of choice, or point an AI agent straight at it. AI assistants can also start from our llms.txt (or the fuller llms-full.txt) for a plain-text map of the site and API.

https://kenospots.com/api/openapi.json

Path parameters

ParameterTypeDescription
slug string The game identifier, for example mass-keno or ohio-keno. Required. See the full catalog below.

Example requests

# Ohio Keno
curl https://app.kenospots.com/api/results/ohio-keno

# New York Quick Draw
curl https://app.kenospots.com/api/results/ny-quick-draw

# OLG Daily Keno (Ontario, field of 1-70)
curl https://app.kenospots.com/api/results/daily-keno

# JavaScript (fetch)
const res = await fetch('https://app.kenospots.com/api/results/mass-keno');
const { draws } = await res.json();
console.log(draws[0].winningNumbers);

# Python (requests)
import requests
draws = requests.get('https://app.kenospots.com/api/results/mass-keno').json()['draws']
print(draws[0]['winningNumbers'])

Response format

Every request returns 200 OK with a JSON body containing a draws array ordered newest first. An unknown slug returns 404. Here is a trimmed example response:

{
  "draws": [
    {
      "drawNumber": 3023269,
      "drawDate": "2026-06-24",
      "drawTime": "2026-06-24T17:12:00-04:00",
      "timezone": "America/New_York",
      "winningNumbers": [75, 65, 62, 31, 52, 27, 66, 12, 7, 30, 46, 10, 58, 2, 76, 32, 29, 45, 61, 59],
      "ballsDrawn": 20,
      "bonus": 1,
      "multiplier": null,
      "multiplierType": "booster",
      "extraNumbers": [],
      "bullseyeNumber": null,
      "jackpotAmount": null,
      "source": "Massachusetts Lottery feed",
      "kenospotsUpdatedAt": "2026-06-24T21:13:04.512Z",
      "freshnessStatus": "live",
      "expectedUpdateFrequency": "every few minutes",
      "isOfficial": false,
      "disclaimer": "KenoSpots is an independent informational site. Verify prizes with the official lottery."
    }
    // ... more draws, newest first
  ]
}

Field reference

Each object in the draws array describes one draw. Optional fields are populated only for games that report them and are null or empty otherwise.

FieldTypeDescription
drawNumberintegerThe official sequential game number for the draw. Useful as a stable identifier and for ordering.
drawDatestringThe draw date in YYYY-MM-DD format, in the game's local timezone.
drawTimestring | nullThe draw time as an ISO 8601 timestamp with the game's local UTC offset, derived from the draw date and timezone. Useful for games that draw every few minutes, where the date alone is not enough to order draws. null when no usable draw date is available.
timezonestringThe IANA timezone the draw date and time are expressed in, for example America/New_York.
winningNumbersinteger[]The drawn numbers, in draw order. Standard keno draws 20 numbers from 1-80; some Canadian games draw from a field of 1-70.
ballsDrawnintegerHow many numbers were drawn (typically 20).
bonusintegerThe bonus or multiplier value applied to the draw, where the game supports it (often 1 when no enhanced multiplier is in play).
multiplierinteger | nullThe active multiplier for games that draw one separately from bonus. null when not applicable.
multiplierTypestring | nullThe name of the multiplier feature for the game, for example booster.
extraNumbersinteger[]Any supplementary numbers some games draw in addition to the main 20. Empty for most games.
bullseyeNumberinteger | nullThe Bullseye number for games that offer a Bullseye add-on. null otherwise.
jackpotAmountnumber | nullThe reported jackpot amount where the game publishes one. null otherwise.
sourcestringHuman-readable name of the upstream feed this result was sourced from, for example Massachusetts Lottery feed.
kenospotsUpdatedAtstring | nullISO 8601 timestamp (UTC) of when KenoSpots last fetched and cached this result.
freshnessStatusstringHow frequently this feed publishes new draws. One of live (draws every few minutes), hourly (refreshes roughly once an hour), or daily (publishes a batch each day).
expectedUpdateFrequencystringHuman-readable cadence at which this feed updates, for example every few minutes.
isOfficialbooleanAlways false. KenoSpots is an independent informational resource, not the official source of results.
disclaimerstringStandard disclaimer reminding consumers to verify prizes with the official lottery.

Game slugs

The following games are available today. Pass the slug as the {slug} path parameter. We add states and games regularly, so check back or email us to request one.

United States · 1-80 field

MassachusettsKenomass-keno
OhioKenoohio-keno
MichiganClub Kenomichigan-club-keno
CaliforniaHot Spotcalifornia-hot-spot
New YorkQuick Drawny-quick-draw
New JerseyQuick Drawnew-jersey-quick-draw
PennsylvaniaKenopennsylvania-keno
VirginiaKenovirginia-keno
MarylandKenomaryland-keno
KentuckyKenokentucky-keno
GeorgiaKenogeorgia-keno
OregonKenooregon-keno
ConnecticutKenoconnecticut-keno
KansasKenokansas-keno
Rhode IslandKenorhode-island-keno
New HampshireKeno 603new-hampshire-keno
MissouriClub Kenomissouri-keno
DelawareKenodelaware-keno
Washington DCKenodc-keno
WyomingKenowyoming-keno

Canada

British ColumbiaBC Keno · 1-80bc-keno
OntarioOLG Daily Keno · 1-70daily-keno
Atlantic CanadaKeno Atlantic · 1-70keno-atlantic

Paytable endpoint

Returns a game's published payout table as structured JSON: every spot count, every catch level, the prize at your bet, the exact odds of each catch, and the return to the player. This is the direct answer to "what are the payouts for Ohio keno" without running a simulation to read a static table.

GET https://kenospots.com/api/paytables

Query parameters

ParameterTypeDescription
gamestringState code (OH), game name (ohio), results-API slug (ohio-keno) or paytable key. Omit it entirely to get the index of every supported game.
spotsintegerReturn only this spot count instead of all of them.
betnumberScale every prize to this stake. Default 1. Odds and the return are unaffected.

Example requests

# Every game that has a paytable
curl "https://kenospots.com/api/paytables"

# What are the payouts for Ohio keno?
curl "https://kenospots.com/api/paytables?game=OH"

# Just the 5-spot, at a $5 bet
curl "https://kenospots.com/api/paytables?game=ohio&spots=5&bet=5"

# The one field to quote
curl -s "https://kenospots.com/api/paytables?game=OH" | jq -r .plainLanguageSummary

Response

Trimmed to a single spot count for length. A full call returns one entry in spots per spot count the game prices.

{
  "ok": true,
  "game": {
    "code": "ohio",
    "name": "Ohio KENO",
    "type": "lottery",
    "jurisdiction": "Ohio",
    "operator": "Ohio Lottery",
    "field": 80,
    "ballsDrawn": 20,
    "maxSpots": 10,
    "spotCountsPriced": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
    "drawFrequency": "Every few minutes throughout the day",
    "drawSchedule": "Every few minutes throughout the day. Each game draws 20 numbers from a pool of 80.",
    "timezone": "America/New_York",
    "officialUrl": "https://www.ohiolottery.com/games/draw-games/keno"
  },
  "bet": 1,
  "howToPlay": "Pick between 1 and 10 numbers from 1 to 80. 20 numbers are drawn, and the prize depends on how many of your picks come up and how many you picked.",
  "spots": [
    {
      "spots": 5,
      "payouts": {
        "3": 2,
        "4": 18,
        "5": 410
      },
      "payoutsPerDollar": {
        "3": 2,
        "4": 18,
        "5": 410
      },
      "topPrize": {
        "catch": 5,
        "prize": 410,
        "oneIn": 1550.5686274509826
      },
      "lowestPayingCatch": 3,
      "odds": [
        {
          "catch": 0,
          "prize": 0,
          "isWinner": false,
          "probability": 0.22718420819686644,
          "oneIn": 4.401714397038768
        },
        {
          "catch": 1,
          "prize": 0,
          "isWinner": false,
          "probability": 0.4056860860658329,
          "oneIn": 2.4649600623417105
        },
        {
          "catch": 2,
          "prize": 0,
          "isWinner": false,
          "probability": 0.27045739071055525,
          "oneIn": 3.697440093512566
        },
        {
          "catch": 3,
          "prize": 2,
          "isWinner": true,
          "probability": 0.08393505228948267,
          "oneIn": 11.913973634651601
        },
        {
          "catch": 4,
          "prize": 18,
          "isWinner": true,
          "probability": 0.01209233804170513,
          "oneIn": 82.69699346405228
        },
        {
          "catch": 5,
          "prize": 410,
          "isWinner": true,
          "probability": 0.000644924695557606,
          "oneIn": 1550.5686274509826
        }
      ],
      "rtp": 0.6499513145082763,
      "houseEdge": 0.35004868549172374,
      "chanceOfAnyPrize": 0.0966723150267454,
      "expectedValuePerGame": -0.35004868549172374
    }
  ],
  "bonusFeature": {
    "name": "Booster",
    "extraCostRatio": 1,
    "multipliers": [1, 2, 3, 4, 5, 10],
    "averageMultiplier": 1.96,
    "maxSpots": null,
    "note": "Prizes above do NOT include this add-on. It costs extra and multiplies a winning prize."
  },
  "bullseyeFeature": null,
  "provenance": {
    "source": "Ohio Lottery official published paytable",
    "officialUrl": "https://www.ohiolottery.com/games/draw-games/keno",
    "verified": "2026-08-22",
    "note": "...",
    "disclaimer": "Figures are transcribed from the operating lottery's published paytable and rechecked on a schedule. Always verify against the official source before playing. KenoSpots is operated by BoostOps and is not affiliated with any lottery."
  },
  "links": {
    "guide": "https://kenospots.com/states/ohio-keno/",
    "results": "https://kenospots.com/states/ohio-keno/results/",
    "paytableApi": "https://kenospots.com/api/paytables?game=ohio",
    "resultsApi": "https://app.kenospots.com/api/results/ohio-keno",
    "dataFeed": "https://kenospots.com/data/oh-results.json"
  },
  "plainLanguageSummary": "Ohio KENO pays $410 for hitting 5 of 5 on a $1 bet, which happens about once in 1,551 games. A 5-spot pays $410 for hitting all 5, odds of 1 in 1,551, and starts paying at 3 matches. Its return is 65.0% of every $1 staked. Across the spot counts it prices, the return to the player runs from 65.0% to 65.0%, so the game keeps roughly 35.0% to 35.0% of everything wagered over the long run. Draws run every few minutes throughout the day. The game is run by the Ohio Lottery. Prizes are per game and vary by bet; always check the current official paytable before playing.",
  "meta": {
    "attribution": "Powered by KenoSpots (https://kenospots.com/)"
  }
}

Key response fields

FieldTypeDescription
plainLanguageSummarystringA paragraph written to be quoted directly. Names the game and operator, the headline top prize with its odds, a mid-range example, and the actual return rather than the jackpot alone.
spots[]object[]One per spot count: payouts at your bet, payoutsPerDollar, topPrize, lowestPayingCatch, odds, rtp, houseEdge and chanceOfAnyPrize.
spots[].odds[]object[]Per catch level: the exact hypergeometric probability, its oneIn reciprocal, and the prize. Computed with exact integer arithmetic.
bonusFeatureobject | nullMultiplier add-ons such as Keno Bonus. The prizes above never include these; the add-on costs extra and multiplies a winning prize.
provenanceobjectThe operator whose published paytable this came from, the official URL, and the date the figures were last verified.
game.drawFrequencystringHow often the game draws. See the games endpoint for the full schedule and links.

Games endpoint

Metadata for every game the site covers, so a client can answer "how often does Ohio keno draw" or "where can I see Ohio results" without scraping a page.

GET https://kenospots.com/api/games

Optional ?game=OH narrows the list to one, accepting the same codes as the other endpoints.

# Every game: name, schedule, timezone, official site, results links
curl "https://kenospots.com/api/games"

# How often does Ohio keno draw?
curl -s "https://kenospots.com/api/games?game=OH" | jq -r .games[0].drawSchedule
{
  "ok": true,
  "count": 1,
  "generatedAt": "2026-08-24T01:11:57.808Z",
  "fieldNotes": {
    "drawFrequency": "How often the game draws, from the published FAQ on its results page.",
    "feedUpdateCadence": "How often the KenoSpots data feed refreshes. Not the same as drawFrequency: New York draws every 4 minutes but publishes in a daily batch.",
    "observedDrawInterval": "Median gap between recently published draws, where the feed carries real times. A cross-check, not the schedule."
  },
  "games": [
    {
      "code": "ohio",
      "name": "Ohio KENO",
      "jurisdiction": "Ohio",
      "operator": "Ohio Lottery",
      "gameName": "Keno",
      "field": 80,
      "ballsDrawn": 20,
      "maxSpots": 10,
      "drawFrequency": "Every few minutes throughout the day",
      "drawSchedule": "Every few minutes throughout the day. Each game draws 20 numbers from a pool of 80.",
      "drawScheduleSource": "published FAQ on the results page",
      "timezone": "America/New_York",
      "officialUrl": "https://www.ohiolottery.com/games/draw-games/keno",
      "hasPaytable": true,
      "feedUpdateCadence": "live",
      "observedDrawInterval": {
        "minutes": 4,
        "basis": "median gap between 640 recently published draws",
        "sampleSize": 640
      },
      "links": {
        "guide": "https://kenospots.com/states/ohio-keno/",
        "results": "https://kenospots.com/states/ohio-keno/results/",
        "paytableApi": "https://kenospots.com/api/paytables?game=ohio",
        "resultsApi": "https://app.kenospots.com/api/results/ohio-keno",
        "dataFeed": "https://kenospots.com/data/oh-results.json"
      }
    }
  ]
}

Read drawFrequency, not feedUpdateCadence, to answer how often a game draws. They are different things and they disagree on purpose. New York Quick Draw draws every 4 minutes, but the New York data reaches us in a single daily batch, so its feedUpdateCadence is daily while its drawFrequency is every 4 minutes. The schedule comes from the published FAQ on each results page, which is the same answer the site shows the public.

Simulator endpoint

The results endpoint tells you what was drawn. The simulator tells you what would happen if you played. It scores one or more sets of numbers against a real state paytable and returns the outcome of every draw, the exact hypergeometric odds of each catch level, observed hit rates against expectation, and a plain-language summary you can quote directly.

It is stateless. There is no authentication, CORS is open to any origin, and responses are never cached.

GET https://kenospots.com/api/simulate

Query parameters

ParameterTypeDescription
numbersstringYour picks as a comma-separated list, for example 7,14,23,55,68. Repeat the parameter to compare several sets: numbers=1,2,3,4,5&numbers=10,20,30,40,50. Either this, sets or spots is required.
setsstringSeveral sets in one parameter, separated by ; or |, for example sets=1,2,3,4,5;10,20,30,40,50. Equivalent to repeating numbers.
spotsintegerPick this many numbers at random instead of naming them. Repeat it for several random sets, including different sizes: spots=5&spots=8.
drawsintegerHow many draws to score against. Default 1 simulated, 100 historical. Maximum 10000.
modestringsimulated (default) generates draws. historical scores against the most recent real published draws for the game. See Historical mode.
gamestringWhich paytable to score against. Accepts a state code (OH, MA, NY), a results-API slug (ohio-keno), or a paytable key. Defaults to a generic casino paytable. Required for mode=historical. Call ?games=1 for the full list.
betnumberWager per draw, per set. Default 1. Paytable values are per dollar staked and scale with this.
seedstringAny string. Makes a simulated run fully reproducible, including any auto-picked numbers. Ignored for the draws in historical mode, which are already fixed.
gamesanyPresence of this parameter returns every supported game with its field size, spot range, aliases and whether historical mode is available, and runs no simulation.

At most 20 sets may be compared in one call.

Comparing several number sets

Every set is scored against the same draws. That is the point of passing several: scoring each against its own separate draw sequence would be comparing two unrelated experiments, and any difference between them would mean nothing.

The response always returns a sets array, one entry per set, each with its own paytable, odds, results, hit rates and summary. When there are two or more, a comparison object ranks them and reports the combined position.

{
  "comparison": {
    "setCount": 2,
    "allSameSpotCount": true,
    "rankedBy": "net",
    "ranking": [
      {
        "rank": 1,
        "id": "set1",
        "numbers": [1, 2, 3, 4, 5],
        "spots": 5,
        "net": 226,
        "achievedRtp": 1.226,
        "totalWon": 1226,
        "bestCatch": 5,
        "winRate": 0.101,
        "theoreticalRtp": 0.6499513145082763
      },
      {
        "rank": 2,
        "id": "set2",
        "numbers": [10, 20, 30, 40, 50],
        "spots": 5,
        "net": -302,
        "achievedRtp": 0.698,
        "totalWon": 698,
        "bestCatch": 5,
        "winRate": 0.081,
        "theoreticalRtp": 0.6499513145082763
      }
    ],
    "combined": {
      "totalStaked": 2000,
      "totalWon": 1924,
      "net": -76,
      "achievedRtp": 0.962
    },
    "bestSet": "set1",
    "worstSet": "set2",
    "note": "All 2 sets pick 5 numbers, so they have mathematically identical odds and an identical theoretical return of 65.0%. No set of numbers is luckier than another. The gap between best and worst here is variance over 1,000 draws and nothing else, and it would reshuffle on the next run.",
    "summary": "set1 (1, 2, 3, 4, 5) came out ahead with $226, and set2 (10, 20, 30, 40, 50) did worst with -$302. Across all 2 sets the combined result was -$76 on $2,000 staked, a return of 96.2%. All 2 sets pick 5 numbers, so they have mathematically identical odds and an identical theoretical return of 65.0%. No set of numbers is luckier than another. The gap between best and worst here is variance over 1,000 draws and nothing else, and it would reshuffle on the next run."
  },
  "summary": "set1 (1, 2, 3, 4, 5) came out ahead with $226, and set2 (10, 20, 30, 40, 50) did worst with -$302. Across all 2 sets the combined result was -$76 on $2,000 staked, a return of 96.2%. All 2 sets pick 5 numbers, so they have mathematically identical odds and an identical theoretical return of 65.0%. No set of numbers is luckier than another. The gap between best and worst here is variance over 1,000 draws and nothing else, and it would reshuffle on the next run. These are simulated draws, not real ones."
}

Read the comparison note before reporting a winner. In keno every set of the same size has mathematically identical odds. No combination of numbers is luckier than another, so a gap between two 5-number sets over any number of draws is variance and nothing else. Sets of different sizes do genuinely differ, and the allSameSpotCount flag tells you which case you are looking at.

Hit rates

Every set carries a hitRates array, one entry per catch level, giving the observed count and rate alongside the exact expected rate. This is what answers "how often would these numbers have hit 4 of 5 over 1000 draws".

Each entry also carries an atLeast block, the cumulative "this catch level or better" figure. That distinction matters: "how often did it hit 4 of 5" almost always means four or more, and the exact-only rate answers a different question.

When draws is 1000 or more, hitRateMode is true and the per-set summaries lead with observed against expected rates rather than the running balance.

# How often would these hit 4 or better over 1,000 draws?
curl "https://kenospots.com/api/simulate?numbers=7,14,23,55,68&draws=1000&game=OH"

# then read: sets[0].hitRates[4].atLeast
{ "count": 15, "observedRate": 0.015, "expectedRate": 0.0128, "expectedCount": 12.8 }

Historical mode

mode=historical scores your sets against the most recent real published draws for a game instead of generated ones. It reads the same public draw feeds the results pages use, so it adds no load to the results API.

Available for 24 games. Call ?games=1 and check the historical flag on each. How far back the feed reaches varies by game, from a couple of dozen draws to several thousand. The response reports draws.available, the draws.dateRange actually covered, and a draws.shortfall message when fewer draws exist than you asked for, so a short feed is never silently passed off as a full sample.

# How would these numbers have done against the last 500 real Ohio draws?
curl "https://kenospots.com/api/simulate?numbers=7,14,23,55,68&mode=historical&game=OH&draws=500"

# Three sets against the same 1,000 real New York draws
curl "https://kenospots.com/api/simulate?numbers=1,2,3,4,5&numbers=10,20,30,40,50&numbers=7,14,23,55,68&mode=historical&game=NY&draws=1000"

Real past draws are still just a sample. A set that did well over the last 1,000 draws is not more likely to do well in the next one, and no run of past results changes the odds of any future draw.

Example requests

# One set, 100 simulated draws, real Ohio paytable
curl "https://kenospots.com/api/simulate?numbers=7,14,23,55,68&draws=100&game=OH"

# Two sets compared over the same 1,000 draws
curl "https://kenospots.com/api/simulate?numbers=1,2,3,4,5&numbers=10,20,30,40,50&draws=1000&game=OH"

# Same thing in one parameter
curl "https://kenospots.com/api/simulate?sets=1,2,3,4,5;10,20,30,40,50&draws=1000&game=OH"

# Is a 5-spot or an 8-spot the better bet? Different sizes genuinely differ
curl "https://kenospots.com/api/simulate?spots=5&spots=8&draws=10000&game=MA"

# Against real published draws instead of generated ones
curl "https://kenospots.com/api/simulate?numbers=7,14,23,55,68&mode=historical&game=OH&draws=500"

# Reproducible simulated run
curl "https://kenospots.com/api/simulate?spots=6&draws=50&seed=hello"

# List every supported game code
curl "https://kenospots.com/api/simulate?games=1"

# JavaScript (fetch)
const url = 'https://kenospots.com/api/simulate?numbers=1,2,3,4,5&numbers=10,20,30,40,50&draws=1000&game=OH';
const sim = await (await fetch(url)).json();
console.log(sim.summary);
for (const s of sim.sets) console.log(s.id, s.numbers, s.results.achievedRtp);

# Python (requests)
import requests
sim = requests.get('https://kenospots.com/api/simulate', params=[
    ('numbers', '1,2,3,4,5'), ('numbers', '10,20,30,40,50'),
    ('draws', 1000), ('game', 'OH')]).json()
print(sim['summary'])
print(sim['sets'][0]['hitRates'][3]['atLeast'])

Response

Every successful call returns 200 OK. Per-draw detail sits at draws.detail and is included when draws is 100 or fewer; above that it is null, draws.detailTruncated is true, and the per-set hitRates and results still cover every draw. Errors return 400 with a machine-readable error code and a message naming the parameter at fault.

{
  "ok": true,
  "mode": "simulated",
  "hitRateMode": true,
  "game": {
    "code": "ohio",
    "name": "Ohio KENO",
    "type": "lottery",
    "field": 80,
    "ballsDrawn": 20,
    "maxSpots": 10,
    "note": "...",
    "paytableSource": "https://kenospots.com/paytables/",
    "historicalAvailable": true
  },
  "draws": {
    "count": 1000,
    "source": "generated",
    "generator": "sfc32 seeded PRNG (deterministic)",
    "seed": "docs",
    "reproducible": true,
    "allSetsScoredAgainstTheSameDraws": true,
    "detail": null,
    "detailTruncated": true,
    "detailLimit": 100
  },
  "setCount": 1,
  "sets": [
    {
      "id": "set1",
      "numbers": [7, 14, 23, 55, 68],
      "spots": 5,
      "paytable": {
        "perDollar": {
          "3": 2,
          "4": 18,
          "5": 410
        },
        "atThisBet": {
          "3": 2,
          "4": 18,
          "5": 410
        }
      },
      "odds": [
        {
          "catch": 0,
          "probability": 0.22718420819686644,
          "oneIn": 4.401714397038768,
          "payout": 0,
          "isWinner": false
        },
        {
          "catch": 1,
          "probability": 0.4056860860658329,
          "oneIn": 2.4649600623417105,
          "payout": 0,
          "isWinner": false
        },
        {
          "catch": 2,
          "probability": 0.27045739071055525,
          "oneIn": 3.697440093512566,
          "payout": 0,
          "isWinner": false
        },
        {
          "catch": 3,
          "probability": 0.08393505228948267,
          "oneIn": 11.913973634651601,
          "payout": 2,
          "isWinner": true
        },
        {
          "catch": 4,
          "probability": 0.01209233804170513,
          "oneIn": 82.69699346405228,
          "payout": 18,
          "isWinner": true
        },
        {
          "catch": 5,
          "probability": 0.000644924695557606,
          "oneIn": 1550.5686274509826,
          "payout": 410,
          "isWinner": true
        }
      ],
      "theoretical": {
        "rtp": 0.6499513145082763,
        "houseEdge": 0.35004868549172374,
        "expectedValuePerDraw": -0.35004868549172374,
        "expectedValueTotal": -350.04868549172375,
        "chanceOfAnyPrize": 0.0966723150267454
      },
      "results": {
        "totalStaked": 1000,
        "totalWon": 426,
        "net": -574,
        "achievedRtp": 0.426,
        "winningDraws": 93,
        "winRate": 0.093,
        "bestCatch": 4,
        "bestCatchOnDraw": 13
      },
      "hitRates": [
        {
          "catch": 0,
          "count": 219,
          "observedRate": 0.219,
          "expectedRate": 0.22718420819686644,
          "expectedCount": 227.18420819686645,
          "ratioToExpected": 0.9639754529514903,
          "oneInObserved": 4.566210045662101,
          "oneInExpected": 4.401714397038768,
          "atLeast": {
            "count": 1000,
            "observedRate": 1,
            "expectedRate": 1,
            "expectedCount": 1000
          },
          "payout": 0,
          "isWinner": false
        },
        {
          "catch": 1,
          "count": 402,
          "observedRate": 0.402,
          "expectedRate": 0.4056860860658329,
          "expectedCount": 405.6860860658329,
          "ratioToExpected": 0.9909139450613677,
          "oneInObserved": 2.487562189054726,
          "oneInExpected": 2.4649600623417105,
          "atLeast": {
            "count": 781,
            "observedRate": 0.781,
            "expectedRate": 0.7728157918031335,
            "expectedCount": 772.8157918031335
          },
          "payout": 0,
          "isWinner": false
        },
        {
          "catch": 2,
          "count": 286,
          "observedRate": 0.286,
          "expectedRate": 0.27045739071055525,
          "expectedCount": 270.4573907105553,
          "ratioToExpected": 1.0574678667445938,
          "oneInObserved": 3.4965034965034967,
          "oneInExpected": 3.697440093512566,
          "atLeast": {
            "count": 379,
            "observedRate": 0.379,
            "expectedRate": 0.36712970573730064,
            "expectedCount": 367.12970573730064
          },
          "payout": 0,
          "isWinner": false
        },
        {
          "catch": 3,
          "count": 78,
          "observedRate": 0.078,
          "expectedRate": 0.08393505228948267,
          "expectedCount": 83.93505228948267,
          "ratioToExpected": 0.9292899435028249,
          "oneInObserved": 12.820512820512821,
          "oneInExpected": 11.913973634651601,
          "atLeast": {
            "count": 93,
            "observedRate": 0.093,
            "expectedRate": 0.0966723150267454,
            "expectedCount": 96.67231502674541
          },
          "payout": 2,
          "isWinner": true
        },
        {
          "catch": 4,
          "count": 15,
          "observedRate": 0.015,
          "expectedRate": 0.01209233804170513,
          "expectedCount": 12.09233804170513,
          "ratioToExpected": 1.2404549019607842,
          "oneInObserved": 66.66666666666667,
          "oneInExpected": 82.69699346405228,
          "atLeast": {
            "count": 15,
            "observedRate": 0.015,
            "expectedRate": 0.012737262737262736,
            "expectedCount": 12.737262737262736
          },
          "payout": 18,
          "isWinner": true
        },
        {
          "catch": 5,
          "count": 0,
          "observedRate": 0,
          "expectedRate": 0.000644924695557606,
          "expectedCount": 0.644924695557606,
          "ratioToExpected": 0,
          "oneInObserved": null,
          "oneInExpected": 1550.5686274509826,
          "atLeast": {
            "count": 0,
            "observedRate": 0,
            "expectedRate": 0.000644924695557606,
            "expectedCount": 0.644924695557606
          },
          "payout": 410,
          "isWinner": true
        }
      ],
      "summary": "Over 1,000 simulated draws of Ohio KENO, the 5 numbers 7, 14, 23, 55, 68 at $1 a draw: 4 or better landed 15 times (1.5%, expected 1.3%); 3 or better landed 93 times (9.3%, expected 9.7%). Overall it paid something on 93 draws and finished -$574 against $1,000 staked. The theoretical return on this paytable is 65.0%, so the game keeps about 35.0% long term."
    }
  ],
  "comparison": null,
  "summary": "Over 1,000 simulated draws of Ohio KENO, the 5 numbers 7, 14, 23, 55, 68 at $1 a draw: 4 or better landed 15 times (1.5%, expected 1.3%); 3 or better landed 93 times (9.3%, expected 9.7%). Overall it paid something on 93 draws and finished -$574 against $1,000 staked. The theoretical return on this paytable is 65.0%, so the game keeps about 35.0% long term. These are simulated draws, not real ones.",
  "disclaimer": "Simulated results from a random number generator. These are not real draws and have no bearing on any actual lottery outcome. Past or simulated results do not change the odds of any future draw. KenoSpots is operated by BoostOps and is not affiliated with any lottery.",
  "meta": {
    "hitRateThreshold": 1000,
    "note": "Per-draw detail is omitted above 100 draws. sets[].hitRates and sets[].results cover every draw.",
    "attribution": "Powered by KenoSpots (https://kenospots.com/)"
  }
}

Key response fields

FieldTypeDescription
summarystringA plain-language paragraph describing the whole run, written to be quoted directly. With several sets this is the comparison narrative, including the caveat about equal-sized sets.
sets[]object[]One entry per number set, always present even for a single set. Each carries its own numbers, paytable, odds, theoretical, results, hitRates and summary.
sets[].hitRates[]object[]Per catch level: observed count and observedRate, the exact expectedRate, ratioToExpected, and an atLeast block for the cumulative "this or better" figure.
sets[].odds[]object[]Exact hypergeometric probability, the oneIn reciprocal, and the payout at your bet. Computed with exact integer arithmetic, not sampled from the run.
comparisonobject | nullnull for a single set. Otherwise a ranking by net, the combined position across all sets, allSameSpotCount, and a note explaining what the ranking does and does not mean.
drawsobjectcount, source, generator, and the per-draw detail. In historical mode also available, dateRange, shortfall and the feed it read.
hitRateModebooleantrue when draws is 1000 or more, signalling that hit rates rather than the running balance are the meaningful output.
modestringsimulated or historical.

How the numbers are generated

In simulated mode without a seed, draws come from crypto.getRandomValues. With one, they come from a seeded sfc32 generator so the run reproduces exactly. Both paths use rejection sampling, so every ball is equally likely; a plain modulo would quietly bias the low numbers. In historical mode the draws are whatever the lottery actually published.

Simulated draws are not real draws. They have no bearing on any actual lottery outcome, and no sequence of past, real or simulated results changes the odds of a future draw. Every response carries this in a disclaimer field.

Rate limits

Rate limits keep the service fast and reliable for everyone. Keyless access needs nothing at all; an API key raises the ceiling to its tier's limits:

TierLimitAuth
Free (keyless)60 requests / minute per IPNone required
Free registered120 / minute, 10,000 / day per keyAPI key
Developer300 / minute, 50,000 / day per keyAPI key
Pro1,000 / minute, 200,000 / day per keyAPI key

Draw data refreshes roughly every 60 seconds, so polling faster than once a minute per game offers no fresher data. Please cache responses where you can. The limits are burst tolerant: short spikes well above the sustained rate are absorbed before they engage, so a human, a results page, or an AI assistant making a handful of quick calls will never see one. Clients that exceed a limit receive an HTTP 429 response with a Retry-After header; back off for that many seconds and retry. If you need a higher ceiling, see API tiers.

Send your key on any endpoint as an X-Api-Key header, or as Authorization: Bearer ks_live_.... Sending no key means anonymous access at the keyless limits. Sending a key that is unknown or revoked returns 401 invalid_api_key rather than silently falling back to anonymous, so a typo in a production key surfaces immediately instead of as mystery rate limiting. If our key lookup itself fails, the request is served anonymously with an X-Api-Key-Status: lookup_failed header; an outage on our side never punishes a valid key.

Powered by KenoSpots

The API is free for personal use, AI assistants, and light integrations, and we just ask one thing in return: visible attribution. If you publish or display data from this API, credit KenoSpots with a link back to kenospots.com. Every JSON response carries source and license fields so the provenance travels with the data.

Required attribution

Display a clear "Powered by KenoSpots" credit with a link, for example:

<a href="https://kenospots.com/">Keno data powered by KenoSpots</a>

Attribution helps us keep the free tier free. Always verify winning tickets against the official lottery; KenoSpots is an independent informational resource and is not affiliated with any lottery commission.

API terms of use

Plain-language terms for the free tier. Using the API means agreeing to them; they exist so the API can stay free, fast, and key-free for everyone.

API tiers

Keyless access covers the latest draws for every supported game and is all most integrations ever need. Keys add higher limits and, on the results endpoints, date-range access to the full archive. Billing is handled personally by email while the program is young; keys are issued by hand, usually same-day.

TierPriceWhat you get
Free (keyless)$0Latest ~100 draws per game, all games, 60 requests/minute per IP. No key, no signup. This tier is permanent.
Free registered$0A named key with 120/minute, 10,000/day, and a 30-day history window via the date-range parameters. Email api@kenospots.com with a line about what you are building.
Developer$29/month300/minute, 50,000/day, and the full draw archive via date-range queries (multi-year history on most games), up to 1,000 rows per request.
Pro$99/month1,000/minute, 200,000/day, full archive at up to 5,000 rows per request for bulk pulls. Webhook push on new draws is the next capability shipping to this tier.
CommercialCustomNegotiated limits, redistribution licensing, and support commitments. Tell us what you need.

Date-range queries (keyed tiers)

With a key, every /api/results/<game> endpoint on app.kenospots.com accepts ?days=N, or ?from=YYYY-MM-DD&to=YYYY-MM-DD, plus ?limit=N. Ranges deeper than your tier's history window are clamped, and the response echoes the resolved window in a window field so you always know what you got. Without a valid key these parameters are ignored and the standard recent window is served.

Get a key or ask a question: email api@kenospots.com and tell us what you are building. Card/Stripe self-serve billing is planned; today invoicing is by email.

Contact

Questions, slug requests, bug reports, or partnership ideas, we want to hear from you. Reach the API team at api@kenospots.com.

Browse Live Results Odds Calculator Email the API Team