Optimize a slate
allocation: a dollar stake for each bet that gets one, and their totalStake. Everything after it explains how the optimizer got there. Bets with no stake are left out of stakes; picks[].reason says why.{
"allocation": {
"kellyMultiplier": 1,
"riskConstraintMet": true,
"totalStake": 65.07,
"stakes": [
{
"pickIndex": 0,
"id": "lad",
"team": "Dodgers",
"betType": "MONEYLINE",
"line": null,
"odds": -145,
"fraction": 0.055356,
"stake": 55.36
},
{
"pickIndex": 1,
"id": "chc",
"team": "Cubs",
"betType": "MONEYLINE",
"line": null,
"odds": 120,
"fraction": 0.009715,
"stake": 9.71
}
]
}
}How it works
Each pick’s price is de-vigged to a fair probability. Your confidence moves it, or your probability replaces it. That probability drives a Kelly-shaped allocation across the whole slate, with each bet capped at the smaller of perBetCap and 1.5 times its single-bet Kelly. A simulation of 100,000 slates measures the risk of each sizing and chooses the share of Kelly the allocation uses.
Request
| Field | Type | Required | Meaning |
|---|---|---|---|
bankroll | number | yes | USD. Greater than 0, at most 100,000,000. |
picks | array | yes | 1 to 50 picks (Free 3, Bettor 12). See below. |
maxTotalExposure | number | no | Most of the bankroll at risk across the slate, above 0 and at most 1. |
perBetCap | number | no | Most of the bankroll on any one bet, 0.01 to 1. Not settable on Free (fixed at 25% of the at-risk amount). |
maxBets | integer | no | Most bets with a stake, 1 to 50. Omit for no maximum. |
useAccountDefaults | boolean | no | Default true. false ignores your saved app settings. |
seed | integer | no | Same seed and same input give an identical result. See Same answer twice. |
Where settings come from
Each setting comes from the request, else your saved KellyIQ app settings (Bettor and up), else the defaults: 100% exposure, 20% per bet, no maximum. perBetCap never exceeds maxTotalExposure; an adjusted value comes with a warning. settingsApplied in the response reports every value and its source, and GET /v1/me shows what an omitted setting resolves to.
Each pick
| Field | Type | Required | Meaning |
|---|---|---|---|
sport | string | yes | A key from GET /v1/sports, e.g. baseball_mlb. |
team | string | yes | Moneyline and spread: the team you are betting. Totals: either team in the game (see totals). |
opponent | string | yes | The other team in the game. |
odds | integer | yes | American odds for your side: the team, or the Over or Under for totals. |
opponentOdds | integer | yes | American odds for the other side of the same market, so the margin can be removed. |
drawOdds | integer | no | Three-way markets (soccer). |
confidence | string | one of | STRONG_FADE, MODERATE_FADE, SLIGHT_FADE, MARKET, SMALL_EDGE, MODERATE_EDGE or STRONG_EDGE: how far your view is from the fair price. |
probability | number | one of | Your own win probability, 0.000001 to 0.999999. Bettor and up. |
betType | string | no | MONEYLINE (default), SPREAD, TOTAL_OVER or TOTAL_UNDER. |
line | number | no | Required for spreads and totals. Spreads are from your side: -1.5 means your team must win by 2 or more. Totals: the total, e.g. 8.5. |
eventId | string | no | Picks sharing an eventId are treated as the same game. |
gameDate | string | no | YYYY-MM-DD. |
id | string | no | Your own label, up to 64 characters, echoed back. |
Confidence or probability
Every pick sends exactly one of confidence or probability, and every pick in a request sends the same one. A pick with both, or a slate that mixes them, is refused with 400 VALIDATION_ERROR and counts against your allowance. For example, the Cubs below send both and the Yankees send a probability while the first pick sent a confidence:
{
"error": "VALIDATION_ERROR",
"message": "The request is invalid; see violations.",
"violations": [
"picks[1]: send exactly one of confidence or probability",
"picks[2]: all picks must use the same input: confidence or probability"
],
"requestId": "req_c7558abd757947bb94fe70d516136622"
}To use your own number for just some bets, convert the rest to probabilities first, for example the fair probability from Remove the vig.
Totals
For TOTAL_OVER and TOTAL_UNDER, team and opponent name the two teams in the game, in either order; betType says which side you are on. odds is the price of your side (the Over for TOTAL_OVER), opponentOdds the price of the other side, and line the total. The third pick in the probability example is an Over 8.5.
Examples
Rate each bet with a confidence
curl https://api.kellyiq.com/v1/optimize \
-H "Authorization: Bearer $KELLYIQ_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"bankroll": 1000,
"picks": [
{
"id": "lad",
"sport": "baseball_mlb",
"team": "Dodgers",
"opponent": "Padres",
"odds": -145,
"opponentOdds": 125,
"confidence": "STRONG_EDGE"
},
{
"id": "chc",
"sport": "baseball_mlb",
"team": "Cubs",
"opponent": "Cardinals",
"odds": 120,
"opponentOdds": -140,
"confidence": "MODERATE_EDGE"
}
]
}'Set limits for this call only
curl https://api.kellyiq.com/v1/optimize \
-H "Authorization: Bearer $KELLYIQ_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"bankroll": 1000,
"maxTotalExposure": 0.5,
"perBetCap": 0.1,
"useAccountDefaults": false,
"seed": 42,
"picks": [
{
"id": "lad",
"sport": "baseball_mlb",
"team": "Dodgers",
"opponent": "Padres",
"odds": -145,
"opponentOdds": 125,
"confidence": "STRONG_EDGE"
},
{
"id": "chc",
"sport": "baseball_mlb",
"team": "Cubs",
"opponent": "Cardinals",
"odds": 120,
"opponentOdds": -140,
"confidence": "MODERATE_EDGE"
}
]
}'{
"allocation": {
"kellyMultiplier": 1,
"riskConstraintMet": true,
"totalStake": 32.54,
"stakes": [
{
"pickIndex": 0,
"id": "lad",
"team": "Dodgers",
"betType": "MONEYLINE",
"line": null,
"odds": -145,
"fraction": 0.027678,
"stake": 27.68
},
{
"pickIndex": 1,
"id": "chc",
"team": "Cubs",
"betType": "MONEYLINE",
"line": null,
"odds": 120,
"fraction": 0.004857,
"stake": 4.86
}
]
},
"settingsApplied": {
"maxTotalExposure": {
"value": 0.5,
"source": "request"
},
"perBetCap": {
"value": 0.1,
"source": "request"
},
"maxBets": {
"value": null,
"source": "default"
}
},
"warnings": []
}Your own probabilities, with a run line and a total
curl https://api.kellyiq.com/v1/optimize \
-H "Authorization: Bearer $KELLYIQ_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"bankroll": 1000,
"picks": [
{
"id": "lad",
"sport": "baseball_mlb",
"team": "Dodgers",
"opponent": "Padres",
"odds": -145,
"opponentOdds": 125,
"probability": 0.62
},
{
"id": "chc-rl",
"sport": "baseball_mlb",
"team": "Cubs",
"opponent": "Cardinals",
"odds": 150,
"opponentOdds": -175,
"betType": "SPREAD",
"line": -1.5,
"probability": 0.42
},
{
"id": "nyy-o",
"sport": "baseball_mlb",
"team": "Yankees",
"opponent": "Red Sox",
"odds": -110,
"opponentOdds": -110,
"betType": "TOTAL_OVER",
"line": 8.5,
"probability": 0.55
}
]
}'{
"allocation": {
"kellyMultiplier": 0.6,
"riskConstraintMet": true,
"totalStake": 95.41,
"stakes": [
{
"pickIndex": 0,
"id": "lad",
"team": "Dodgers",
"betType": "MONEYLINE",
"line": null,
"odds": -145,
"fraction": 0.042448,
"stake": 42.45
},
{
"pickIndex": 1,
"id": "chc-rl",
"team": "Cubs",
"betType": "SPREAD",
"line": -1.5,
"odds": 150,
"fraction": 0.019704,
"stake": 19.7
},
{
"pickIndex": 2,
"id": "nyy-o",
"team": "Yankees",
"betType": "TOTAL_OVER",
"line": 8.5,
"odds": -110,
"fraction": 0.033258,
"stake": 33.26
}
]
}
}Response
The full response to the first example:
{
"allocation": {
"kellyMultiplier": 1,
"riskConstraintMet": true,
"totalStake": 65.07,
"stakes": [
{
"pickIndex": 0,
"id": "lad",
"team": "Dodgers",
"betType": "MONEYLINE",
"line": null,
"odds": -145,
"fraction": 0.055356,
"stake": 55.36
},
{
"pickIndex": 1,
"id": "chc",
"team": "Cubs",
"betType": "MONEYLINE",
"line": null,
"odds": 120,
"fraction": 0.009715,
"stake": 9.71
}
]
},
"bankroll": 1000,
"atRisk": 1000,
"settingsApplied": {
"maxTotalExposure": {
"value": 1,
"source": "default"
},
"perBetCap": {
"value": 0.2,
"source": "default"
},
"maxBets": {
"value": null,
"source": "default"
}
},
"warnings": [],
"picks": [
{
"index": 0,
"id": "lad",
"team": "Dodgers",
"opponent": "Padres",
"betType": "MONEYLINE",
"line": null,
"odds": -145,
"eventId": null,
"included": true,
"reason": null,
"probability": {
"market": 0.571116,
"yours": 0.614004
},
"expectedValue": 0.037456,
"sizing": {
"full": {
"fraction": 0.055356,
"stake": 55.36
},
"half": {
"fraction": 0.027678,
"stake": 27.68
},
"quarter": {
"fraction": 0.013839,
"stake": 13.84
},
"fixed": {
"fraction": 0.850708,
"stake": 850.71
},
"riskAdjusted": {
"fraction": 0.055356,
"stake": 55.36
}
}
},
{
"index": 1,
"id": "chc",
"team": "Cubs",
"opponent": "Cardinals",
"betType": "MONEYLINE",
"line": null,
"odds": 120,
"eventId": null,
"included": true,
"reason": null,
"probability": {
"market": 0.437956,
"yours": 0.459854
},
"expectedValue": 0.011679,
"sizing": {
"full": {
"fraction": 0.009715,
"stake": 9.71
},
"half": {
"fraction": 0.004857,
"stake": 4.86
},
"quarter": {
"fraction": 0.002429,
"stake": 2.43
},
"fixed": {
"fraction": 0.149292,
"stake": 149.29
},
"riskAdjusted": {
"fraction": 0.009715,
"stake": 9.71
}
}
}
],
"risk": {
"full": {
"independent": {
"trials": 100000,
"meanProfit": 1.96,
"stdDev": 46.77,
"probLoss": 0.38836,
"var5": -65.07,
"cvar5": -65.07
},
"correlated": {
"trials": 100000,
"meanProfit": -1.48,
"stdDev": 47.89,
"probLoss": 0.42941,
"var5": -65.07,
"cvar5": -65.07
}
},
"half": {
"independent": {
"trials": 100000,
"meanProfit": 1.19,
"stdDev": 23.36,
"probLoss": 0.38437,
"var5": -32.54,
"cvar5": -32.54
},
"correlated": {
"trials": 100000,
"meanProfit": -0.86,
"stdDev": 23.95,
"probLoss": 0.43221,
"var5": -32.54,
"cvar5": -32.54
}
},
"quarter": {
"independent": {
"trials": 100000,
"meanProfit": 0.56,
"stdDev": 11.69,
"probLoss": 0.38569,
"var5": -16.27,
"cvar5": -16.27
},
"correlated": {
"trials": 100000,
"meanProfit": -0.35,
"stdDev": 11.98,
"probLoss": 0.42795,
"var5": -16.27,
"cvar5": -16.27
}
},
"fixed": {
"independent": {
"trials": 100000,
"meanProfit": 34,
"stdDev": 719.11,
"probLoss": 0.38545,
"var5": -1000,
"cvar5": -1000
},
"correlated": {
"trials": 100000,
"meanProfit": -24.51,
"stdDev": 736.33,
"probLoss": 0.42977,
"var5": -1000,
"cvar5": -1000
}
},
"riskAdjusted": {
"independent": {
"trials": 100000,
"meanProfit": 2.4,
"stdDev": 46.73,
"probLoss": 0.38378,
"var5": -65.07,
"cvar5": -65.07
},
"correlated": {
"trials": 100000,
"meanProfit": -1.54,
"stdDev": 47.88,
"probLoss": 0.42938,
"var5": -65.07,
"cvar5": -65.07
}
}
},
"seed": 5331169363815191,
"apiVersion": "1.0",
"engineVersion": "d4c05f6",
"requestId": "req_4e0feecfd37049a9a7adce0cc98c2c3b",
"explanation": "(plain-English summary, omitted here)"
}| Field | Meaning |
|---|---|
allocation | The result. stakes: one row per bet with a stake (pickIndex, id, team, betType, line, odds, fraction of the bankroll, stake in USD). totalStake: their sum. kellyMultiplier: the share of full Kelly used. riskConstraintMet: whether that share keeps risk within bounds. |
atRisk | bankroll × maxTotalExposure: the amount being sized. |
settingsApplied | Each setting's value and source: request, account, default or tier. |
warnings | Adjustments made to your input, in words. |
picks | One per pick, in your order. included (has a stake) and reason when not (NO_EDGE, MAX_BETS, NOT_SELECTED, NOT_PRICEABLE); probability.market (fair) and .yours (used); expectedValue per 1 staked; sizing under each strategy. |
risk | Simulated results for each sizing (riskAdjusted is the allocation): meanProfit, stdDev, probLoss, var5 (5th percentile), cvar5 (average of the worst 5%). correlated is null below Sharp. |
explanation | Plain-English summary for display. Do not parse it; the wording changes. |
seed | The seed used. Resend it with the same input to reproduce this result. |
apiVersion, engineVersion, requestId | What produced the result. Keep them with the seed. |
Errors
| Status | error | When |
|---|---|---|
| 400 | VALIDATION_ERROR | Bad or missing fields, or mixed inputs: every problem is listed. An unknown sport is reported on its own, after the other fields pass. |
| 403 | UPGRADE_REQUIRED | Too many picks for your plan, or probability on Free. |
| 422 | SLATE_NOT_OPTIMIZABLE | Valid, but nothing could be sized. |
| 429 | DAILY_LIMIT_EXCEEDED | Today's allowance is used up. |
| 429 | BURST_LIMIT_EXCEEDED | More than 10 a minute, or another optimize is still running. |
| 503 | SERVICE_BUSY | Server capacity or a temporary outage. Not counted. |
Plus the errors every endpoint can return; see Errors.