$ api--v1

HTTP/JSON API for live football odds, surebet detection, and movement signals across our bookmaker network. Designed for B2B integrators building their own betting products on top of our data.

$overview

Base URL: https://api.freeoddportal.space

All endpoints return application/json. Public endpoints are unauthenticated (browse the catalogue, see surebets); paid endpoints require either a B2B API key or a consumer Supabase JWT.

Network coverage: multiple international and regional bookmakers, kept anonymous to protect our scraping operation. Specific source identities are surfaced only to authenticated paying customers (in the response under the books key) — they need them to place the bet. Markets covered: 1x2, over/under, asian handicap, BTS, double chance, plus deep markets (corners, cards, correct score, half-time variants).

$auth

Two parallel auth paths — the API key path is preferred for B2B and server-to-server; the bearer path is used by our consumer web app.

api key (B2B)

Pass your key in the X-API-Key header. Each key is bound to a single site domain (registered when you request the key). Browser requests must come from that origin; server-to-server calls have no Origin header and pass through.

$ curlcurl
curl "https://api.freeoddportal.space/matches/237/odds"
  -H "X-API-Key: op_live_xuQXhqq8IvDRp0bpzE_G8ZFS3Vea4SAT"

bearer jwt (consumer)

For consumer apps using Supabase Auth, pass the user's access token. Subscription state is enforced server-side — the user must be in their trial window or have a paid period remaining.

$ curlcurl
curl "https://api.freeoddportal.space/me"
  -H "Authorization: Bearer eyJhbGc…"

$rate limits

  • API keys: configurable per key, default 60 requests/minute. Higher tiers available on request.
  • Bearer (consumer): unenforced for read endpoints in v1; will ship a soft cap before public launch.
  • Billing endpoints (deposit, extend): 5 requests per hour per user.

Exceeded limits return 429 Too Many Requests with a Retry-After header in seconds.

$endpoints

GET/bookmakers[public]

Lists active bookmakers with last_scrape_at for freshness badges.

GET/matches[public]

Filterable upcoming match list.

paramtypenotes
league_idintfilter to one league
sport_idint1 = football
statusstringupcoming | live | finished | cancelled
from_time / to_timeISO8601kickoff window
min_booksintonly matches with ≥N book coverage
limit / offsetintpagination, default 50
GET/matches/{id}[public]

Match metadata only (teams, kickoff, status, league, scores).

GET/matches/{id}/markets[public]

Distinct market_type values for a match with outcome / book / handicap-line counts. Use this to know which tabs to render.

GET/matches/{id}/odds[paid]

Wide-format pivot of all live odds for a match. Pivoted by (market_type, outcome, handicap) → books{slug: {value, prev, direction, last_seen_at, scraped_at}}.

paramtypenotes
marketscsvfilter to specific market types (e.g. corners_over_under,btts). Default: all.
GET/matches/{id}/movement[paid]

Historical odds for a single (market, outcome, handicap).

paramtypenotes
market_typestringrequired, e.g. '1x2'
outcomestringrequired, e.g. 'home'
handicapfloatif applicable
bookmaker_slugstringfilter to one book
limitintdefault 500, max 5000
GET/signals/surebets[public]

Cross-book arbitrage opportunities for 1x2. Rare — when they appear, the window is short. Free for v1; alerts on new ones will be a paid feature.

paramtypenotes
marketstring1x2 only currently
min_roifloatfilter to ≥X% ROI
limitintdefault 50, max 200

$errors

  • 400 — bad input (e.g. invalid markets= regex).
  • 401 — missing or invalid auth credentials.
  • 402 — authenticated but no active subscription. Detail includes balance and required amount.
  • 403— API key presented from an origin that doesn't match its bound site_domain.
  • 404 — match or resource not found.
  • 429 — rate limit exceeded. Backoff per Retry-After.
  • 5xx — backend error. Contact us if persistent.

$examples

python

surebets.pypython
import requests

res = requests.get(
  "https://api.freeoddportal.space/signals/surebets",
  params={"min_roi": 1.0},
  headers={"X-API-Key": "op_live_…"},
)
for sb in res.json()["surebets"]:
    print(sb["match_id"], sb["roi_pct"])

javascript / node

odds.mjsjavascript
const res = await fetch(
  "https://api.freeoddportal.space/matches/237/odds",
  {
    headers: {
      "X-API-Key": "op_live_…",
      "Accept": "application/json",
    }
  }
);
const data = await res.json();
console.log(data.odds);

sample response

GET /matches/237/oddsjson
{
  "match": {
    "id": 237,
    "home_team": "Arsenal",
    "away_team": "Liverpool"
  },
  "odds": [
    { "market_type": "1x2", "outcome": "home",
      "handicap": null, "books": {
        "book_a": { "value": 1.85, "direction": "up" },
        "book_b": { "value": 1.83, "direction": "flat" }
      } }
  ]
}

$get a key

B2B keys are issued by hand for v1. Send us:

  • your contact email,
  • the site domain you'll use the key from,
  • expected request rate (we'll tune the per-key limit).

Email keys@freeoddportal.space with those details. Or, if you just want the consumer plan, start with a $5 deposit and use the bearer JWT path.