UFC Live Stats API: round, clock and in-fight stats over WebSocket
The UFCalendar UFC Live Stats API streams what is happening in the cage as it happens: the round and a running clock, unofficial significant-strike, takedown, knockdown and control-time totals for both corners, round-by-round lines, the referee, and a timestamped play-by-play timeline (round starts, knockdowns, takedowns, submission attempts). One WebSocket, one JSON document, pushed within 1–2 seconds of every change. Pro plans and up.
Free 1-day trial · 100 requests · no credit card. Paid plans from $19/mo for 30,000 requests, cancel anytime.
A socket, not a poll
developers.topicApi.ufcLive.edge1Body
The clock is yours to tick
Every document carries clock_sec and clock_observed_at, and the clock is derived from the official round_start timestamp rather than a lagging counter. Add the wall time since the observation and you have a second-accurate round clock in your own UI, whether the last frame arrived one second ago or forty. Stats are flagged official: false until the promotion finalizes them.
Endpoints for this job
Method and path, linked to the reference. The same key covers every endpoint in the API.
| Method & path | What it returns |
|---|---|
GET /v1/events/{slug}/live | Real-time fight-night stream: round, clock, unofficial in-fight stats and a play-by-play timeline, pushed over WebSocket as they change. |
GET /v1/events/{slug} | One event with its full fight card (both corners, results when completed), venue and event-level broadcasts. |
GET /v1/fights/{id}/stats | Per-fight totals for both corners: strikes by target and position, takedowns, control time. |
POST /v1/webhook-endpoints | Signed webhooks for fight results, card changes and event completion · no polling needed. |
Quickstart in curl, Python and JavaScript
Complete requests against the live API. Any HTTP client works, or `pip install ufcalendar` for a typed Python client (source on GitHub); every response is plain JSON in a {"data": ..., "meta": ...} envelope.
$ curl "https://api.ufcalendar.com/v1/events/ufc-2026-09-20/live" \
-H "Authorization: Bearer $UFCAL_KEY"
{
"data": {
"event": { "slug": "ufc-2026-09-20", "status": "live" },
"current": {
"phase": "round", "round": 2, "possible_rounds": 5,
"clock_sec": 67, "official": false,
"stats": { "a": { "sig_strikes_landed": 41, "takedowns_landed": 0,
"control_time_sec": 1, ... },
"b": { "sig_strikes_landed": 20, "takedowns_landed": 1, ... } },
"timeline": [ { "t": "…", "type": "takedown", "round": 2,
"clock": "2:57", "side": "b" }, ... ]
},
"last": { "winner": "a", "method": "KO/TKO", "round": 1, "time": "4:56" }
},
"meta": { "live": true, "websocket": "wss://live.ufcalendar.com/v1" }
}
# Or hold one socket open and let every change come to you:
$ wscat -c "wss://live.ufcalendar.com/v1?key=$UFCAL_KEY"
> {"action":"subscribe","event":"ufc-2026-09-20"}
< {"type":"snapshot","data":{...}}
< {"type":"update","data":{...}} # every few seconds while a round runs
< {"type":"fight.final","data":{...}} # the moment a bout endsimport json, websocket # pip install websocket-client
ws = websocket.create_connection(f"wss://live.ufcalendar.com/v1?key={UFCAL_KEY}")
ws.send(json.dumps({"action": "subscribe", "event": "ufc-2026-09-20"}))
while True:
frame = json.loads(ws.recv())
if frame["type"] == "pong" or not frame.get("data"):
continue
cur = frame["data"]["current"]
if cur and cur["phase"] == "round":
a, b = cur["stats"]["a"], cur["stats"]["b"]
print(f"R{cur['round']} {cur['clock_sec']}s sig {a['sig_strikes_landed']}-{b['sig_strikes_landed']}")
if frame["type"] == "fight.final":
print("FINAL:", frame["data"]["last"])const ws = new WebSocket(`wss://live.ufcalendar.com/v1?key=${process.env.UFCAL_KEY}`);
ws.onopen = () => ws.send(JSON.stringify({ action: "subscribe", event: "ufc-2026-09-20" }));
ws.onmessage = (e) => {
const { type, data } = JSON.parse(e.data);
if (!data?.current) return;
const { phase, round, clock_sec, stats, timeline } = data.current;
if (phase === "round")
console.log(`R${round} ${clock_sec}s sig ${stats.a.sig_strikes_landed}-${stats.b.sig_strikes_landed}`);
if (type === "fight.final") console.log("FINAL", data.last);
// timeline[] carries round_start / knockdown / takedown / submission_attempt … with a clock
};Questions
- Which promotions have live stats?
- UFC only in this first version — including numbered cards, Fight Nights and the live-streamed prelims. The source is the promotion's own real-time feed, and no other promotion publishes one. Other orgs still get results within minutes through the normal API and webhooks.
- How live is live?
- The promotion's feed changes every 5–10 seconds during a round. We poll it at 1–2 seconds and publish only when the content changes, so you see a new frame within about two seconds of the source. Between frames, interpolate the clock from clock_observed_at.
- What is in the document?
- The full card with statuses and results, the bout in progress (phase: prefight, round, break or over; round; running clock; both corners' totals across 22 stat fields; per-round lines; referee; the action timeline) and the last result. It is the same document the website's live panel renders.
- Which plan do I need, and how many connections?
- Pro and up. Pro keys may hold 2 concurrent connections, Business 5, Enterprise 25. Messages are not metered and do not count toward your monthly request quota; the REST snapshot does count as a normal request.
UFC API guides by use case
One page per job — schedules, results, per-round statistics and rankings history — each with its own endpoints, sample responses and code.
UFCalendar is an independent publication, not affiliated with UFC/Zuffa/TKO, PFL, OKTAGON MMA, BKFC, RIZIN FF or any promotion. Organization names are used to describe the data. Terms of service · Refund Policy