Skip to main content

ufcalendar — MMA Fight Data API client for Python

UFC, PFL, OKTAGON, BKFC and RIZIN events, fight stats, results, rankings history and judges' scorecards from one Python client.

The MMA Fight Data API by UFCalendar (the UFCalendar Fight API) is a REST API for MMA data: UFC, PFL, OKTAGON, BKFC and RIZIN events, full fight cards, results within minutes, per-fight and round-by-round statistics, complete fighter careers, the only MMA data API with point-in-time UFC rankings history back to 2013, and the only one serving judges' scorecards — every official, every round: UFC back to 1995, PFL to 2018, OKTAGON to 2025 and RIZIN 2019–2022 and from March 2026 — plus the UFCalendar consensus odds line (current, opening and closing on every plan, line movement on Pro). This package is a thin requests wrapper over it — one method per endpoint, cursor pagination handled for you.

pip install ufcalendar

Get a free key at https://www.ufcalendar.com/account/api?trial=1 — the free plan is 1,000 requests a month, no card, never expires. Paid plans from $19/month for 30,000 requests (Pro $49 for 200,000, Business $149 for 1,000,000), hard caps, no overage.

Quickstart in 3 lines

from ufcalendar import FightAPI
api = FightAPI("ufcalendar_...")                       # free-plan key, no card
print(next(api.events(org="ufc", limit=1))["title"])   # the next UFC card

Quickstart

from ufcalendar import FightAPI

api = FightAPI("ufcalendar_...")            # or export UFCAL_API_KEY=...

# Upcoming UFC schedule, soonest first
for ev in api.events(org="ufc", limit=5):
    print(ev["starts_at"], ev["title"], "PPV" if ev["is_ppv"] else "")

# Full card + results of the newest completed UFC event
latest = next(api.events(org="ufc", status="completed", limit=1))
card = api.event(latest["slug"])
for f in card["card"]:
    r = f.get("result")
    if r:
        print(f["fighter_a"]["name"], "vs", f["fighter_b"]["name"], "->", r["method"], f"R{r['round']} {r['time']}")

# Round-by-round statistics for one bout
rounds = api.fight_rounds(card["card"][0]["id"])

# UFC rankings on any date since February 2013 (rank 0 = champion)
board = api.rankings("ufc", date="2016-11-14")
lw = next(d for d in board["divisions"] if d["division"] == "lightweight")
print(board["snapshot_date"], [e["name"] for e in lw["entries"][:5]])

# A fighter's complete multi-promotion career
history = api.fighter_history("islam-makhachev")

What's covered

Method Endpoint
plans() GET /v1/plans — plans (Free first), quotas, free-plan terms, MCP endpoint (no key required)
events(org, status, from_date, to_date, order, is_title_card, is_ppv, include=["headline"]) GET /v1/events (paginated; headline = each card's main event and its result)
event(slug, include=["eta", "odds"]) / event_changes(slug) GET /v1/events/{slug} / …/changes (eta = per-bout estimated start; odds = each bout's latest consensus line)
event_watch(slug, country) GET /v1/events/{slug}/watch — how to watch one event, per country: the rights deals for its series merged with the event's own listings
changes(org, since, kind) GET /v1/changes — the card-change feed across every event, newest first (paginated; default last 90 days)
fight(id, include=["odds"]) / fight_stats(id) / fight_rounds(id) GET /v1/fights/{id} / …/stats / …/rounds (fight_stats lines carry fantasy — UFCalendar fantasy points with a per-category breakdown; last_meta["quality"] = data-quality flags)
fight_odds(id) / event_odds(slug) GET /v1/fights/{id}/odds / GET /v1/events/{slug}/odds — the UFCalendar consensus line: current, opening, closing (settled bouts), movement and sources (how many sportsbooks backed each point), plus props (method of victory per corner, total rounds over/under, goes the distance). Information only, not betting advice
fight_odds_history(id, from_date, to_date) GET /v1/fights/{id}/odds/history — every consensus point, oldest first (paginated; Pro plans and up)
find_fights(org, title_only, method, division, fighter, winner, from_date, to_date, main_events_only, order) GET /v1/fights/search — completed bouts, filtered, newest first (at least one narrowing filter; 25 a page, 10 pages deep)
fight_scorecards(id) GET /v1/fights/{id}/scorecards — judges, rounds, totals, deductions
judges(q, org, min_fights) / judge(id) / judge_scorecards(id) GET /v1/judges / …/{id} / …/{id}/scorecards (last_meta["league"] = baseline rates; each card row flags lone_dissent / split and lists colleagues)
split_decisions(org, from_date, to_date) GET /v1/scorecards/splits — split and majority decisions, newest first, with every judge's card and the dissenters named (paginated)
referees(q, org) / referee(slug) / referee_fights(slug) GET /v1/referees / …/{slug} / …/{slug}/fights — referees across the launch promotions: bouts, finish rate, method mix, average fight time; the bouts each one worked (paginated)
coverage(org) GET /v1/coverage — what the database holds per promotion per year: events, settled bouts, and the share with stats, round stats, commission scorecards and a consensus odds line
fighters(q, org, country) / fighter(slug, include=["bonuses", "credentials"]) GET /v1/fighters / …/{slug} (always carries next_fight / last_fight; bonuses = the UFC bonus ledger; credentials = grappling and wrestling pedigree, gyms and coaches, each row sourced and confidence-graded)
fighter_history / fighter_stats / fighter_rankings / fighter_power_index GET /v1/fighters/{slug}/…
rankings(org, date) / division_rankings(org, division) / champions() GET /v1/rankings/… / /v1/champions (entries carry movement, is_new, country_code; last_meta names the previous/next snapshot)
power_index(org, view, division, days, limit) / predictions_upcoming(event) GET /v1/power-index/{org} (view: current · movers · peaks) / /v1/predictions/upcoming
matchmaker(org, division, limit) / whos_next(fighter, limit) GET /v1/matchmaker/{org} / …/next/{fighter} — UFCalendar's matchmaker: the fights worth making (scored 0–100, per division or across the roster; not bookings), and one fighter's best next opponents with the win probability for each and the bout already booked
leaderboard(org, metric, division, country, population, limit) / record_book(org, division, country, population, scope, top) GET /v1/stats/leaders / …/record-book — the Record Book: one leaderboard (48 metrics; ties flagged, sample size on every row), or every board's top rows grouped by category
org_division(org, division) GET /v1/orgs/{org}/divisions/{division} — one weight class: rankings board, upcoming bouts, latest results, roster by recency
year_stats(year, org) GET /v1/stats/years/{year} — one calendar year in numbers for one promotion (or every covered one): methods, divisions, fastest finishes, upsets, Power Index climbers, busiest fighters, countries, judges
compare(a, b) GET /v1/compare — two fighters side by side: bios, career stats, strike mix, streaks, previous meetings, common opponents, booked bout, model prediction (UFC), Power Index
event_storylines(slug) GET /v1/events/{slug}/storylines — the talking points of one card: title fights, eliminators, closest bout, rematches, streaks, debuts, returns, ranked fighters, nations
event_pickem(slug) GET /v1/events/{slug}/pickem — how the UFCalendar community is picking each bout on one card (picks per corner, total, percentage); crowd sentiment, not a market and not a forecast
broadcast_rights(org, country, series) / venue(id) / search(q) / usage() misc (series="dwcs"/"rtufc" = a UFC sub-series grid)
venues(q, country) / venue_events(id, status, from_date, to_date, order) GET /v1/venues / …/{id}/events — venue search, and every covered event at one venue, newest first (paginated)
articles(q, tag, locale) / article(slug, locale) GET /v1/articles / …/{slug} — UFCalendar's own editorial archive, newest first, in any of the 13 site languages (paginated), and one article's full Markdown body
create_webhook_endpoint(url, events) … POST /v1/webhook-endpoints (Pro+; kinds event.announced, fight.result, card.changed, event.completed, odds.moved)
calendar_ics_url(org) GET /v1/calendar/{org}.ics

Full reference: https://api.ufcalendar.com/docs · OpenAPI 3.1: https://api.ufcalendar.com/openapi.json · Also on npm (TypeScript client), RapidAPI Hub, Postman and the hosted MCP server

Consensus odds

o = api.fight_odds(83379)
print(o["consensus"]["a"]["american"], o["consensus"]["b"]["american"], o["consensus"]["sources"])
print(o["movement"]["delta_points_a"], o["closing"])   # closing = last line before the start (settled bouts)

for point in api.fight_odds_history(83379):               # Pro plans and up
    print(point["recorded_at"], point["a"]["american"], point["b"]["american"])

One anonymised UFCalendar consensus line per corner across the sportsbooks we track; book identities are never exposed. Information only, not betting advice.

Webhooks instead of polling (Pro and up)

ep = api.create_webhook_endpoint("https://example.com/hooks/ufcal", ["fight.result", "card.changed"])
print(ep["secret"])   # shown once; verify X-UFCalendar-Signature with it

odds.moved fires when the consensus line on an upcoming bout moves 5+ implied-probability points on corner a, or the favourite flips, measured against the last line delivered, so a slow drift arrives once. Information only, not betting advice.

Errors and rate limits

Every error raises FightAPIError with .status, .code, .message, .request_id. After each call api.last_rate_limit holds the X-RateLimit-* headers.

Rate-limited reads are waited out for you. When a GET hits the per-minute limit (429 rate_limited, Retry-After of 60 s or less; the free plan allows 10 requests a minute) the client sleeps for Retry-After plus a little jitter and tries again, up to max_retries times (default 3), so a script that walks a few pages does not die mid-run. A 429'd request does not count against your month.

api = FightAPI("ufcalendar_...", max_retries=5)   # wait out more per-minute 429s
api = FightAPI("ufcalendar_...", max_retries=0)   # never wait: raise FightAPIError on the first 429

The monthly quota (429 quota_exceeded), a 429 with a long or missing Retry-After, and every non-GET request (webhook endpoint create / delete / rotate) raise FightAPIError at once.

Notes

  • Odds are the UFCalendar consensus line only (no book identities). Information only, not betting advice.
  • Fighter images are Wikimedia Commons / Creative Commons files: display the license and artist fields as a credit.
  • Not affiliated with UFC, Zuffa, TKO or any promotion. Terms: https://www.ufcalendar.com/developers/terms

MIT licensed.

Metadata

Release files for ufcalendar 0.9.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ufcalendar 0.9.0
File Size Uploaded Provenance Actions
ufcalendar-0.9.0.tar.gz 22.6 kB Without provenance Details for ufcalendar-0.9.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for ufcalendar 0.9.0
File Interpreter ABI Platform Actions
ufcalendar-0.9.0-py3-none-any.whl Python 3 none any Details for ufcalendar-0.9.0-py3-none-any.whl

Total release size: 40.2 kB

Release files / ufcalendar-0.9.0.tar.gz

Download URL ufcalendar-0.9.0.tar.gz
Size 22.6 kB
Tags Source
SHA-256 checksum
How to use checksums
afd12a7143169540d1927d63d78fe612f2b4b76526903268cab97bc5490b7411
BLAKE2b-256 checksum
How to use checksums
25ee40935ea0c44b7ca4134a87cc273a59baf83c39c84b40602e99c36256acbf
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.9 {"installer":{"name":"uv","version":"0.11.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / ufcalendar-0.9.0-py3-none-any.whl

Download URL ufcalendar-0.9.0-py3-none-any.whl
Size 17.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
fb68b87691740aece73ef0d4ac8e96283e9e33e708cdf4511ff5eeee7ea8d4d7
BLAKE2b-256 checksum
How to use checksums
2c3ea2cd7c4eb985febc81ddd6ee37e1c9b3b12731b37de2edace95c864b2948
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.9 {"installer":{"name":"uv","version":"0.11.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for ufcalendar 0.9.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page