API Reference

Base URL: https://api.kirkova.sports/v1 (or http://localhost:8000/v1 in local dev). All responses are JSON; timestamps are ISO-8601 UTC.

Two kinds of endpoints

Public endpoints (/public/*) need no key, are IP rate-limited, and power this website. Authenticated endpoints are everything else — they need a bearer JWT, obtained by exchanging an API key, and the scopes on that key determine what you can reach.

Authentication

If you have an API key (from your account page), exchange it for a short-lived JWT and send that JWT as a bearer token:

curl -X POST https://api.kirkova.sports/v1/auth/token \
  -H "Content-Type: application/json" \
  -d '{"api_key": "sk_live_..."}'
# => {"access_token": "eyJ...", "token_type": "bearer", "expires_in": 900}

curl https://api.kirkova.sports/v1/games/upcoming?sport=nba \
  -H "Authorization: Bearer eyJ..."

Tokens expire quickly (see expires_in, in seconds) — request a fresh one when they do rather than caching long-term.

Scopes and tiers

freepredictions:read, models:readpropredictions:read, models:read, props:read

Upgrading via the Pricingpage flips your tier and updates your existing API key's scopes in place — no new key needed. A JWT obtained before the upgrade keeps its old scopes until you exchange it again.

Rate limits & errors

Public endpoints allow 30 requests/minute per IP. Authenticated endpoints carryX-RateLimit-Remainingresponse headers. Exceeding a limit returns 429 with a Retry-After header. Errors are always {"detail": "message"}: 401 missing/expired token, 403 missing scope, 404 not found, 422 validation error, 429 rate limited.

Public endpoints

GET/public/sportsList supported sports.
GET/public/games/upcomingUpcoming games (sport, hours params).
GET/public/predictions/game/{game_id}Game winner probability.
GET/public/models/activeActive model versions and metrics.

Games & predictions (requires predictions:read)

GET/games/upcomingUpcoming games with full team/venue detail.
GET/games/{game_id}Single game detail.
GET/predictions/game/{game_id}Game winner probability.

Player props (Pro — requires props:read)

GET/predictions/props/{game_id}All player prop predictions for a game.
GET/predictions/player/{player_id}Props for one player (needs ?game_id=).

Models

GET/sportsList supported sports.
GET/models/activeActive model versions and metrics (models:read).

Account

POST/account/signupCreate an account; auto-provisions a free API key.
POST/account/loginEmail/password sign-in (returns an MFA challenge if 2FA is on).
POST/account/oauth/googleSign in with a Google ID token.
POST/account/2fa/login-verifyComplete sign-in with a TOTP or recovery code.
GET/account/meEmail, tier, masked API key, recent request log.
POST/account/api-key/rotateRevoke the current key and issue a new one.
POST/account/verify-email/requestResend the email verification link.
GET/account/verify-email/confirmConfirm an email verification link.
POST/account/2fa/setupStart TOTP 2FA setup — returns a QR code.
POST/account/2fa/enableConfirm 2FA with a code; returns recovery codes.
POST/account/2fa/disableTurn off 2FA (needs a valid code).

Example: full flow with an API key

import httpx

api_key = "sk_live_..."
base = "https://api.kirkova.sports/v1"

token = httpx.post(f"{base}/auth/token", json={"api_key": api_key}).json()["access_token"]
headers = {"Authorization": f"Bearer {token}"}

games = httpx.get(f"{base}/games/upcoming", params={"sport": "nba"}, headers=headers).json()
pred = httpx.get(f"{base}/predictions/game/{games[0]['id']}", headers=headers).json()
print(pred["home_win_probability"])