MintSlips API
Read-only sports betting data for your code and your AI agent: every MintSlips pick with its chance to hit, fair payout and max price, live cards, a price check, and graded results.
- Quickstart
- Your API keys
- Authentication
- Endpoints
- MCP setup
- Webhooks and signatures
- Rate limits and tiers
- Errors
- Terms of use
Quickstart
- The API is in private beta. Join the waitlist to get access.
- Log in with your MintSlips account, come back here and create a key under Your API keys. The full key is shown once.
- Call the board:
curl https://api.mintslips.com/api/v1/board \
-H "Authorization: Bearer ms_live_…"
Base URL: https://api.mintslips.com/api/v1. Every
response is JSON (except /stream and /history/export) and
carries schema_version, as_of (the data's own time when it has
one) and disclaimer.
Your API keys
Keys belong to your MintSlips account. You can hold up to 3 active keys.
Authentication
Send your key as a bearer token on every request:
Authorization: Bearer ms_live_…
A key is ms_live_ followed by 32 letters and digits. MintSlips stores only a
hash of it, so a lost key can't be shown again: revoke it and create another. Keep keys
out of client-side code and public repositories.
Your tier is worked out on every request from your account (see tiers), so an upgrade or a lapsed membership applies to existing keys at once.
Endpoints
Service check. No key needed; 10 calls a minute per address.
{
"schema_version": 1,
"as_of": "2026-10-11T12:00:00.000Z",
"disclaimer": "Information only, not betting advice. 21+. Gambling problem? 1-800-GAMBLER.",
"status": "ok",
"service": "mintslips-public"
}
Your account, tier and today's usage.
{
"schema_version": 1,
"as_of": "2026-10-11T12:00:00.000Z",
"disclaimer": "Information only, not betting advice. 21+. Gambling problem? 1-800-GAMBLER.",
"email": "you@example.com",
"tier": "member",
"usage": { "day": "2026-10-11", "calls": 12, "daily_limit": 1000 },
"limits": { "per_minute": 60, "per_day": 1000 },
"key_id": "key_3f9c0a1b2c3d4e5f6a7b8c9d"
}
Today's board: the day's 15 picks, every leg, at every tier (all picks are free with an
account since 2026-10-12; members pay for live cards and higher limits). A past date (member+) answers from the graded
results, marked "source": "results"; a day with nothing graded returns an
empty list.
{
"schema_version": 1,
"as_of": "2026-10-11T12:00:00.000Z",
"disclaimer": "Information only, not betting advice. 21+. Gambling problem? 1-800-GAMBLER.",
"day": "2026-10-11",
"state": "board",
"source": "live",
"picks": [
{
"pick_id": "20261011-F1",
"tier": "banker",
"sport": "soccer",
"n_legs": 3,
"first_start": "2026-10-12T22:00:00Z",
"event_date": "2026-10-12",
"prob": 0.6032,
"fair_cents": 60.3,
"max_pay_cents": 62.3,
"payout_if_hit_per_dollar": 1.66,
"kalshi_payout": 1.57,
"units": 1.0,
"basis": "market-implied",
"provisional": true,
"books": null,
"best_book": null,
"best_payout": null,
"locked": false,
"filler": true,
"legs": [
{ "desc": "Gremio Novorizontino to win (plays Mon Oct 12)", "prob": 0.905 },
{ "desc": "Al Hilal to win (plays Fri Oct 16)", "prob": 0.86 },
{ "desc": "Junior to win (plays Wed Oct 14)", "prob": 0.775 }
],
"games": ["GRN @ PON", "RIY @ HIL", "JUN @ PER"]
},
…
]
}
| field | meaning |
|---|---|
prob | stated chance that every leg hits (0 to 1) |
basis | model (a MintSlips model estimate) or market-implied (from market prices) |
fair_cents | fair price of a $1 contract, in cents |
max_pay_cents | max price: the most, in cents per $1, at which the pick is priced |
payout_if_hit_per_dollar | total return per $1 at the fair price if it hits (stake included) |
kalshi_payout, books, best_book, best_payout | listed payouts per $1 where available, else null |
tier, band | board section: banker or boost, and a boost's band |
units | the pick's stake size in units, as published |
legs[] | {desc, prob} per leg; absent when locked |
One pick from today's board, or (member+) from the graded results.
{
"schema_version": 1,
"as_of": "2026-10-11T12:00:00.000Z",
"disclaimer": "Information only, not betting advice. 21+. Gambling problem? 1-800-GAMBLER.",
"day": "2026-10-11",
"source": "live",
"pick": { "pick_id": "20261011-F1", "tier": "banker", "prob": 0.6032, … }
}
In-game live cards: one card per game, each with the score, the game state and the card's
current market when it has one. Members and Pro get the latest cards; free keys get a
snapshot delayed 15 minutes ("delayed": true).
{
"schema_version": 1,
"as_of": "2026-10-12T01:31:45Z",
"disclaimer": "Information only, not betting advice. 21+. Gambling problem? 1-800-GAMBLER.",
"delayed": false,
"cards": [
{
"sport": "nfl",
"game_id": "2026_05_BAL_ATL",
"home": "ATL",
"away": "BAL",
"status": "live",
"state_line": "Q2 2:24 · BAL 6, ATL 14",
"score": { "away": "BAL", "home": "ATL", "away_score": 6, "home_score": 14, "clock": "Q2 2:24", "down": null },
"checked_at": "2026-10-12T01:31:45Z",
"bet": {
"label": "Derrick Henry under 1 touchdowns",
"side": "no",
"ask": 0.59,
"fair": 0.6767,
"ev_label": "+EV 7.0¢ per contract (model estimate)",
"caveat": "model estimate, not an edge: on past games picks like this ran about 5-8¢ per contract below the model's EV",
…
},
…
}
]
}
Graded picks by day (hit, miss or void), newest day first, at most 31 days per call. Without dates you get the last 7 days.
{
"schema_version": 1,
"as_of": "2026-10-11T12:05:00Z",
"disclaimer": "Information only, not betting advice. 21+. Gambling problem? 1-800-GAMBLER.",
"from": "2026-10-05",
"to": "2026-10-11",
"days": [
{
"day": "2026-10-11",
"picks": [
{
"pick_id": "20261011-SOCCER1",
"tier": "banker",
"sport": "soccer",
"n_legs": 3,
"first_start": "2026-10-11T12:30:00Z",
"prob": 0.9079,
"fair_cents": 90.8,
"max_pay_cents": 92.8,
"payout_if_hit_per_dollar": 1.1,
"provisional": true,
"basis": "market-implied",
"settled": { "hit": true },
"legs": [ { "desc": "Lecce–Bologna Under 5.5 goals (regulation)", "prob": 0.965 }, … ]
}
]
}
]
}
A void pick has "void": true and "settled": null; it is neither a
hit nor a miss. The result above is illustrative.
Compare the price you're offered with a pick's max price. Send exactly one of
american or cents.
curl https://api.mintslips.com/api/v1/check \
-H "Authorization: Bearer ms_live_…" \
-H "content-type: application/json" \
-d '{"pick_id": "20261011-F1", "offered": {"american": -150}}'
{
"schema_version": 1,
"as_of": "2026-10-11T12:00:00.000Z",
"disclaimer": "Information only, not betting advice. 21+. Gambling problem? 1-800-GAMBLER.",
"pick_id": "20261011-F1",
"within_max_price": true,
"label": "within max price",
"max_price": { "cents": 62.3, "american": -165, "implied_prob": 0.623 },
"offered": { "cents": 60, "american": -150, "decimal": 1.6667 },
"offered_implied_prob": 0.6,
"fair_cents": 60.3,
"fair_payout": 1.6584,
"offered_payout": 1.6667,
"difference_vs_fair": 0.0083
}
Payouts are total return per $1 if the pick hits. difference_vs_fair is
offered_payout − fair_payout. The same pick at {"cents": 64}
returns "within_max_price": false, "label": "above max price".
A label describes the price, not whether to bet.
Server-sent events. Each event's data is {id, type, created_at, data,
disclaimer}. A : heartbeat comment arrives every 25 seconds.
| event | data |
|---|---|
pick.created | {day, pick}: a pick new to the board |
live.updated | {sport, game_id, card}: a card's status, score or state changed |
live.bet_changed | {sport, game_id, card}: a card's market changed |
result.graded | {day, pick}: a pick was graded (hit, miss or void) |
curl -N https://api.mintslips.com/api/v1/stream \
-H "Authorization: Bearer ms_live_…"
id: evt_5d0c9b8a7f6e5d4c3b2a1908
event: pick.created
data: {"id":"evt_5d0c9b8a7f6e5d4c3b2a1908","type":"pick.created","created_at":"2026-10-11T15:00:04.000Z","data":{"day":"2026-10-11","pick":{…}},"disclaimer":"…"}
: heartbeat
Register an https URL to receive the same events as the stream. The signing secret is
shown once. Up to 5 active webhooks per account; events defaults to all
four.
curl https://api.mintslips.com/api/v1/webhooks \
-H "Authorization: Bearer ms_live_…" \
-H "content-type: application/json" \
-d '{"url": "https://example.com/mintslips", "events": ["pick.created", "result.graded"]}'
{
"schema_version": 1,
"as_of": "2026-10-11T12:00:00.000Z",
"disclaimer": "Information only, not betting advice. 21+. Gambling problem? 1-800-GAMBLER.",
"id": "wh_8e7d6c5b4a39281706f5e4d3",
"url": "https://example.com/mintslips",
"events": ["pick.created", "result.graded"],
"secret": "whsec_…",
"created_at": "2026-10-11T12:00:00.000Z"
}
GET /webhooks returns {"webhooks": [{id, url, events, created_at,
disabled_at, fail_count}]}. DELETE /webhooks/{id} returns
{"ok": true, "id"}. Failed deliveries retry after 1 minute, 5 minutes, 30
minutes, 2 hours and 12 hours.
One month as NDJSON (application/x-ndjson): a meta line, then
one line per pick ("type": "pick" for today's board, "type":
"result" for graded picks).
{"type":"meta","schema_version":1,"month":"2026-10","as_of":"2026-10-11T12:00:00.000Z","disclaimer":"…"}
{"type":"result","day":"2026-10-01","pick_id":"…","prob":…,"settled":{"hit":false},…}
MCP setup
The MCP server is at https://api.mintslips.com/mcp
(Streamable HTTP). Tools: get_board(date?), get_pick(pick_id),
get_live_cards(), check_price(pick_id, american_odds? | price_cents?),
get_results(from, to), explain_pick(pick_id). Tool calls count
toward the same daily limit as REST calls.
Claude Code
claude mcp add --transport http mintslips https://api.mintslips.com/mcp \
--header "Authorization: Bearer ms_live_…"
Claude Desktop
Claude Desktop connects through mcp-remote (needs Node.js). Add this to
claude_desktop_config.json and restart the app:
{
"mcpServers": {
"mintslips": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://api.mintslips.com/mcp",
"--header", "Authorization:${AUTH_HEADER}"],
"env": { "AUTH_HEADER": "Bearer ms_live_…" }
}
}
}
Claude.ai and ChatGPT
Add a custom connector (in Claude.ai: Settings, Connectors; in ChatGPT: the apps and
connectors settings) with the URL
https://api.mintslips.com/mcp, then sign in with your
MintSlips account when asked. No key to paste: the sign-in grants the connector your
account's tier.
Any other agent
Any MCP client that speaks Streamable HTTP and can send an
Authorization: Bearer ms_live_… header works.
Webhooks and signatures
Each delivery is a POST of the event JSON with the header
MintSlips-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256(secret, t + "." + raw body)>
Verify it against the raw request body (before any JSON parsing), reject old timestamps, and compare in constant time.
import hashlib, hmac, time
def verify(secret: str, header: str, body: bytes, tolerance: int = 300) -> bool:
parts = dict(p.split("=", 1) for p in header.split(",") if "=" in p)
t, sig = parts.get("t", ""), parts.get("v1", "")
if not t.isdigit() or abs(time.time() - int(t)) > tolerance:
return False
expected = hmac.new(secret.encode(), t.encode() + b"." + body,
hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, sig)
import crypto from "node:crypto";
export function verify(secret, header, rawBody, tolerance = 300) {
const parts = Object.fromEntries(header.split(",").map((p) => {
const i = p.indexOf("=");
return [p.slice(0, i), p.slice(i + 1)];
}));
const t = parts.t || "", sig = parts.v1 || "";
if (!/^\d+$/.test(t) || Math.abs(Date.now() / 1000 - Number(t)) > tolerance) return false;
const expected = crypto.createHmac("sha256", secret)
.update(`${t}.${rawBody}`).digest("hex");
return sig.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}
Answer with any 2xx within 10 seconds. After 5 failed events in a row the webhook is disabled; create it again to resume.
Rate limits and tiers
| tier | calls a day | access |
|---|---|---|
| Free | 100 | all 15 daily picks; live cards delayed 15 minutes |
| Member | 1,000 | full board, live cards, results, MCP |
| Pro | 10,000 | everything in Member, plus /stream, webhooks and history export |
Every key is also limited to 60 calls a minute. Daily counts reset at 00:00 UTC.
/health and the key routes are limited to 10 calls a minute per address.
During the beta, accepted accounts get Pro. Paid tiers are launching soon; nothing is on
sale yet.
Errors
Errors are JSON: {"error": "<code>", "message": "…", "disclaimer", "as_of"}.
| status | error | when |
|---|---|---|
| 400 | bad_request | a bad date, month, body or price |
| 401 | unauthorized | missing, malformed, unknown or revoked key (or session, on the key routes) |
| 403 | api_beta | your account isn't in the beta yet; the body carries a waitlist link |
| 403 | unverified | confirm your email on the site first |
| 403 | tier | the endpoint needs a higher tier (required_tier, tier) |
| 404 | not_found | unknown route, pick, key or webhook |
| 409 | key_limit, webhook_limit | 3 active keys or 5 active webhooks already |
| 429 | rate_limited, quota_exceeded | per-minute or daily limit; wait for Retry-After seconds |
Terms of use
- Information only, not betting advice. Nothing here tells you to place a bet.
- Probabilities, fair prices and max prices are estimates. Any pick can lose. Past results do not predict future ones.
- MintSlips is not a sportsbook and does not place bets. The API never takes or stores sportsbook or exchange credentials.
- For people 21 and over, where sports wagering is lawful. Gambling problem? Call 1-800-GAMBLER.
- Use of the API is covered by the terms and privacy policy. Redistribution needs a commercial agreement: mintslips1@gmail.com.