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.

Information only, not betting advice. The API never places bets or orders, never asks for a sportsbook or exchange login, and never promises results. Probabilities are estimates. 21+. Gambling problem? Call 1-800-GAMBLER.

Quickstart

  1. The API is in private beta. Join the waitlist to get access.
  2. Log in with your MintSlips account, come back here and create a key under Your API keys. The full key is shown once.
  3. 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

GET/health

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"
}
GET/me

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"
}
GET/board?date=YYYY-MM-DD

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.

response (abbreviated)
{
  "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"]
    },
    …
  ]
}
fieldmeaning
probstated chance that every leg hits (0 to 1)
basismodel (a MintSlips model estimate) or market-implied (from market prices)
fair_centsfair price of a $1 contract, in cents
max_pay_centsmax price: the most, in cents per $1, at which the pick is priced
payout_if_hit_per_dollartotal return per $1 at the fair price if it hits (stake included)
kalshi_payout, books, best_book, best_payoutlisted payouts per $1 where available, else null
tier, bandboard section: banker or boost, and a boost's band
unitsthe pick's stake size in units, as published
legs[]{desc, prob} per leg; absent when locked
GET/picks/{pick_id}

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, … }
}
GET/live

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).

response (abbreviated)
{
  "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",
        …
      },
      …
    }
  ]
}
GET/results?from=YYYY-MM-DD&to=YYYY-MM-DD member+

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.

POST/check

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.

GET/stream pro

Server-sent events. Each event's data is {id, type, created_at, data, disclaimer}. A : heartbeat comment arrives every 25 seconds.

eventdata
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
POSTGETDELETE/webhooks pro

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.

GET/history/export?month=YYYY-MM pro

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.

Python
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)
Node.js
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

tiercalls a dayaccess
Free100all 15 daily picks; live cards delayed 15 minutes
Member1,000full board, live cards, results, MCP
Pro10,000everything 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"}.

statuserrorwhen
400bad_requesta bad date, month, body or price
401unauthorizedmissing, malformed, unknown or revoked key (or session, on the key routes)
403api_betayour account isn't in the beta yet; the body carries a waitlist link
403unverifiedconfirm your email on the site first
403tierthe endpoint needs a higher tier (required_tier, tier)
404not_foundunknown route, pick, key or webhook
409key_limit, webhook_limit3 active keys or 5 active webhooks already
429rate_limited, quota_exceededper-minute or daily limit; wait for Retry-After seconds

Terms of use