How do I get UFC data into Google Sheets?
Add the UFCalendar script to your spreadsheet (Extensions > Apps Script, paste, save), set your MMA Fight Data API key from the new UFCalendar menu, then type a formula such as =MMA_EVENTS("ufc", "upcoming") or =MMA_CARD("ufc-dwcs-93-2026-09-22"). Eight functions return schedules, full cards with results, fighter profiles and careers, rankings on any date, fight stats, Record Book leaderboards and the consensus odds line for the UFC, PFL, OKTAGON, BKFC and RIZIN. The free plan's 100 requests a month cover a personal sheet.
Free plan: 100 requests a month, no card. Paid plans from $19/mo.
Endpoints used
| Method & path | What it returns | Plan |
|---|---|---|
GET /v1/events | MMA schedule + results API: full fight cards, venues, broadcasts, and a card-change diff log for late reshuffles. | All plans |
GET /v1/events/{slug} | One event with its full fight card (both corners, results when completed), venue and event-level broadcasts. | All plans |
GET /v1/fighters/{slug} | Fighter bio, records, career statistics per scope, Power Index and CC-licensed images. | All plans |
GET /v1/fighters/{slug}/history | Roster search, bios, records · and complete multi-promotion career timelines, merged and deduplicated. | All plans |
GET /v1/rankings/{org}/{division}?date= | One division's current board, or the board on any past date. | All plans |
GET /v1/fights/{id}/stats | Per-fight totals for both corners: strikes by target and position, takedowns, control time · plus UFCalendar fantasy points per corner and data-quality flags. | All plans |
GET /v1/events/{slug}/odds | The consensus line for every bout on a card in one call, in card order, unpriced bouts included. | All plans |
GET /v1/stats/leaders | One Record Book leaderboard per promotion: career, single-fight, round, division and event boards across 48 metrics. | All plans |
The code
The Google Sheets block lists every function with an example. curl shows the request behind =MMA_CARD. The Python block pushes the next card into a sheet on a schedule with gspread, and the JavaScript block is one Apps Script custom function for anyone who wants a single formula without the add-on.
Every block runs as shown with your API key in the UFCAL_API_KEY environment variable. Python uses pip install ufcalendar and JavaScript or TypeScript uses npm install @ufcalendar/sdk; any HTTP client works too.
=MMA_EVENTS("ufc", "upcoming") upcoming UFC cards, soonest first
=MMA_EVENTS("", "completed", 50) the last 50 cards of every promotion
=MMA_CARD("ufc-dwcs-93-2026-09-22") a full card, headliner first, with results
=MMA_CARD(C2) the card of the event slug in cell C2
=MMA_FIGHTER("jon-jones") bio, records and career stats
=MMA_FIGHTER_HISTORY("jon-jones") every bout, every promotion, newest first
=MMA_RANKINGS("ufc", "lightweight") today's board ("C" = champion)
=MMA_RANKINGS("ufc", "bantamweight", "2019-03-01") the board as it stood on that day
=MMA_FIGHT_STATS(84176) both corners, whole fight
=MMA_FIGHT_STATS(84176, 2) round 2 only
=MMA_ODDS("ufc-fight-night-2026-08-29") consensus line per bout, information only
=MMA_STAT_LEADERS("knockouts", "ufc", 10) a Record Book leaderboard# The request behind =MMA_CARD("ufc-dwcs-93-2026-09-22")
curl "https://api.ufcalendar.com/v1/events/ufc-dwcs-93-2026-09-22" \
-H "Authorization: Bearer $UFCAL_API_KEY"# pip install ufcalendar gspread
# Push the next UFC card into a sheet on a schedule (cron, GitHub Actions...).
# gspread needs a Google service account with the sheet shared to it.
import gspread
from ufcalendar import FightAPI
api = FightAPI() # reads UFCAL_API_KEY from the environment
ws = gspread.service_account().open("MMA data").sheet1
nxt = next(api.events("ufc", limit=1))
rows = [["Bout", "Weight class", "Section", "Winner", "Method", "Round", "Time"]]
for f in api.event(nxt["slug"])["card"]:
if f["status"] == "cancelled":
continue
r = f["result"] or {}
a, b = f["fighter_a"], f["fighter_b"]
winner = next((c["name"] for c in (a, b) if c["id"] == r.get("winner_fighter_id")), "")
rows.append([f"{a['name']} vs {b['name']}", f["weight_class"], f["card_section"],
winner, r.get("method"), r.get("round"), r.get("time")])
ws.clear()
ws.update(rows, "A1")
print(f"{nxt['title']}: {len(rows) - 1} bouts")// Google Apps Script (JavaScript): Extensions > Apps Script, paste, save.
// Project Settings > Script properties: add UFCAL_API_KEY = your key.
// Then in any cell: =NEXT_CARD("ufc")
/**
* The next card of a promotion, one row per bout.
* @param {string} org ufc, pfl, oktagon, bkfc or rizin
* @customfunction
*/
function NEXT_CARD(org) {
const key = PropertiesService.getScriptProperties().getProperty("UFCAL_API_KEY");
const get = (path) => JSON.parse(UrlFetchApp.fetch("https://api.ufcalendar.com/v1" + path, {
headers: { Authorization: "Bearer " + key },
}).getContentText());
const next = get(`/events?org=${org || "ufc"}&limit=1`).data[0];
if (!next) return "No upcoming card";
const rows = [["Bout", "Weight class", "Section", "Winner", "Method"]];
for (const f of get(`/events/${next.slug}`).data.card) {
if (f.status === "cancelled") continue;
const r = f.result || {};
const winner = [f.fighter_a, f.fighter_b].find((c) => c.id === r.winner_fighter_id);
rows.push([`${f.fighter_a.name} vs ${f.fighter_b.name}`, f.weight_class || "",
f.card_section || "", winner ? winner.name : "", r.method || ""]);
}
return rows;
}Sample response
The documented payload, filled with real values from the same bout. … marks a value left out.
{
"data": {
"id": 2556,
"slug": "ufc-dwcs-93-2026-09-22",
"title": "Dana White's Contender Series 93",
"org": "ufc",
"sport": "mma",
"starts_at": "2026-09-22T23:00:00.000Z",
"tz": "America/Los_Angeles",
"status": "completed",
"venue": { "name": "Meta Apex", "city": "Las Vegas", "country_code": "US", … },
"card": [
{
"id": 84176,
"ordering": 1,
"card_section": "main",
"weight_class": "Middleweight",
"is_title": false,
"is_main": true,
"scheduled_rounds": 3,
"status": "completed",
"fighter_a": { "id": 25314, "slug": "norbert-novenyi-jr", "name": "Norbert Novenyi Jr.", … },
"fighter_b": { "id": 33707, "slug": "theo-haig", "name": "Theo Haig", … },
"result": {
"winner_fighter_id": 25314,
"method": "Decision - Unanimous",
"method_normalized": "decision_unanimous",
"round": 3,
"time": "05:00",
"time_seconds": 300,
…
},
"referee": "Herb Dean",
…
},
…
],
…
},
"meta": {}
}Notes and pitfalls
- Install: open the spreadsheet, choose Extensions > Apps Script, replace the sample code with the add-on script, save and reload the sheet. A UFCalendar menu appears: Set API key keeps the key in your Google account's private script storage, never in a cell, and Insert sample dashboard adds five tabs that chain the schedule, the next card, its headliner, a rankings board and a leaderboard.
- Answers are cached: 2 minutes for a live card, 10 for an upcoming one and 6 hours once it is over. Sheets keeps a formula's last answer until its inputs change, so use UFCalendar > Refresh data for fresh numbers. A table costs one request (MMA_FIGHT_STATS two), and MMA_EVENTS reads at most five pages of 100.
- An error reads as one cell: #NO_KEY means no key is set, #QUOTA names the day your monthly quota resets, #RATE_LIMIT asks you to wait a minute and #NOT_FOUND passes on the API's own message. Slugs come from the Slug column of MMA_EVENTS, and a pasted ufcalendar.com link works too.
- Custom functions run with the spreadsheet owner's key, so on a shared sheet every collaborator's recalculation counts against the owner's quota. MMA_ODDS returns one consensus line per bout, the mean across the sportsbooks we track with no book named, and ends with a note: information only, not betting advice.
Related pages
More code guides
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