ufcalendar — Python client for the UFCalendar Fight API
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 UFC rankings API with point-in-time 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 from March 2026. This package is a thin requests wrapper over it — one method per endpoint, cursor pagination handled for you.
pip install ufcalendar
Get a key (free 1-day trial, 100 requests, no card) at https://www.ufcalendar.com/account/api?trial=1. 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
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["fights"]:
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["fights"][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, quotas, trial 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"]) / event_changes(slug) |
GET /v1/events/{slug} / …/changes (eta = per-bout estimated start) |
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) |
event_live(slug) |
GET /v1/events/{slug}/live — real-time LiveState on fight night (Pro plans and up); the same document streams over wss://live.ufcalendar.com/v1?key=… |
live_stream(slug) |
the WebSocket itself — subscribe to wss://live.ufcalendar.com/v1 and iterate every frame (Pro plans and up) |
fight(id) / fight_stats(id) / fight_rounds(id) |
GET /v1/fights/{id} / …/stats / …/rounds |
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) |
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+) |
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 and Postman
Live stream (UFC fight nights, Pro plans and up)
The UFC live API: a WebSocket that pushes the fight-night document the moment it
changes — card order and statuses, the bout in progress (round, running clock,
unofficial in-fight stats, per-round splits, a timestamped action timeline) and the
last result. It is the streaming half of event_live(), and it is the UFC live
stats API you want instead of polling.
The socket ships as an optional extra so a REST-only install stays requests-thin:
pip install 'ufcalendar[live]'
from ufcalendar import FightAPI
api = FightAPI() # UFCAL_API_KEY
for frame in api.live_stream("ufc-331", until_final=True):
# frame["type"]: "snapshot" | "update" | "fight.final" | "event.completed"
state = frame["data"]
if not state:
continue # nothing streaming yet
cur = state["current"] or {}
print(frame["type"], "R", cur.get("round"), cur.get("clock_sec"))
until_final=True stops after the first fight.final frame; the default runs until
the socket ends. A dropped connection is re-subscribed once automatically.
Runnable ticker: examples/live_ticker.py — ~40 lines that
print R2 3:41 · Oliveira 41 vs Makhachev 37 sig. strikes as the round unfolds.
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
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.
Notes
- No betting odds are served, by design.
- Fighter
imagesare Wikimedia Commons / Creative Commons files: display thelicenseandartistfields as a credit. - Not affiliated with UFC, Zuffa, TKO or any promotion. Terms: https://www.ufcalendar.com/developers/terms
MIT licensed.
Release files for ufcalendar 0.6.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| ufcalendar-0.6.0.tar.gz | 20.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| ufcalendar-0.6.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 36.7 kB
Release files / ufcalendar-0.6.0.tar.gz
| Download URL | ufcalendar-0.6.0.tar.gz |
|---|---|
| Size | 20.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6fa8cb5a57bf8b3424d14c86367da85deb0b7e876425542b62b7a9e5e90b2539
|
|
BLAKE2b-256 checksum How to use checksums |
dca13343bd171b9082f4816f109ec485dab8e753e99533a1e4ddfee762fbaf0b
|
| 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.6.0-py3-none-any.whl
| Download URL | ufcalendar-0.6.0-py3-none-any.whl |
|---|---|
| Size | 15.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
444dac956bb3734c1c9c8bfeedf012ca1a5c006bb03ad55a8905d28e28120153
|
|
BLAKE2b-256 checksum How to use checksums |
085f08bed516cc36e92a803bf47a8d2584fef7fe26ea8d1d1066c4264df7b282
|
| 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}
|