@ufcalendar/sdk
v0.9.0
Published
MMA Fight Data API client for TypeScript — UFC, PFL, OKTAGON, BKFC, RIZIN events, fight stats, results, rankings history, judges' scorecards and consensus odds.
Maintainers
Readme
@ufcalendar/sdk — MMA Fight Data API client for TypeScript
UFC, PFL, OKTAGON, BKFC and RIZIN events, fight stats, results, rankings history and judges' scorecards from one typed 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 — plus the UFCalendar consensus odds line (current, opening and closing on every plan, line movement on Pro). This package is a dependency-free fetch wrapper over it: one method per endpoint, cursor pagination handled for you, ESM + CJS, Node 18+, browsers and edge runtimes.
npm install @ufcalendar/sdkGet 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
import { FightAPI } from '@ufcalendar/sdk';
const api = new FightAPI('ufcalendar_...'); // free-plan key, no card
for await (const ev of api.events({ org: 'ufc', limit: 1 })) console.log(ev.title); // the next UFC cardQuickstart
import { FightAPI } from '@ufcalendar/sdk';
const api = new FightAPI(process.env.UFCALENDAR_API_KEY); // or new FightAPI({ apiKey })
// Upcoming UFC schedule, soonest first
for await (const ev of api.events({ org: 'ufc', limit: 5 })) {
console.log(ev.starts_at, ev.title, ev.is_ppv ? 'PPV' : '');
}
// Full card + results of the newest completed UFC event
const [latest] = await Array.fromAsync(api.events({ org: 'ufc', status: 'completed', limit: 1 }));
const card = await api.event(latest!.slug);
for (const f of card.card) {
if (f.result) {
console.log(f.fighter_a?.name, 'vs', f.fighter_b?.name, '->', f.result.method, `R${f.result.round}`);
}
}
// The judges' scorecards for one bout — the official commission record
const cards = await api.fightScorecards(card.card[0]!.id);
// UFC rankings on any date since February 2013 (rank 0 = champion)
const board = await api.rankings('ufc', { date: '2016-11-14' });
// A fighter's complete multi-promotion career
const history = await api.fighterHistory('islam-makhachev');List endpoints are async generators that follow meta.pagination.next_cursor for you; pass limit to stop after N rows. Everything else returns the envelope's data. The last response's meta and rate-limit headers stay on the client (api.lastMeta, api.lastRateLimit).
What's covered
| Method | Endpoint |
|---|---|
| plans() | GET /v1/plans — plans (Free first), quotas, free-plan terms, MCP endpoint (no key required) |
| orgs() / org(slug) | GET /v1/orgs / …/{slug} |
| events({ org, status, from, to, order, isTitleCard, isPpv, include: ['headline'], limit }) | GET /v1/events (paginated; headline = each card's main event and its result) |
| event(slug, { include: ['eta', 'odds'] }) / eventChanges(slug) | GET /v1/events/{slug} / …/changes (eta = per-bout estimated start; odds = each bout's latest consensus line) |
| eventWatch(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, limit }) | GET /v1/changes — the card-change feed across every event, newest first (paginated; default last 90 days) |
| fight(id, { include: ['odds'] }) / fightStats(id) / fightRounds(id) | GET /v1/fights/{id} / …/stats / …/rounds (fightStats lines carry fantasy — UFCalendar fantasy points with a per-category breakdown; lastMeta.quality = data-quality flags) |
| fightOdds(id) / eventOdds(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 |
| fightOddsHistory(id, { from, to }) | GET /v1/fights/{id}/odds/history — every consensus point, oldest first (async generator; Pro plans and up) |
| findFights({ org, titleOnly, method, division, fighter, winner, from, to, mainEventsOnly, order, limit }) | GET /v1/fights/search — completed bouts, filtered, newest first (at least one narrowing filter; 25 a page, 10 pages deep) |
| fightScorecards(id) | GET /v1/fights/{id}/scorecards — judges, rounds, totals, deductions |
| judges({ q, org, minFights }) / judge(id) / judgeScorecards(id) | GET /v1/judges / …/{id} / …/{id}/scorecards (lastMeta.league = baseline rates; each card row flags lone_dissent / split and lists colleagues) |
| referees({ q, org }) / referee(slug) / refereeFights(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 (async iterators for the lists) |
| 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 |
| splitDecisions({ org, from, to, limit }) | 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) |
| fighterHistory / fighterStats / fighterRankings / fighterPowerIndex | GET /v1/fighters/{slug}/… |
| rankings(org, { date }) / divisionRankings(org, division) / champions() | GET /v1/rankings/… / /v1/champions (entries carry movement, is_new, country_code; lastMeta names the previous/next snapshot) |
| powerIndex(org, { view, division, days, limit }) / predictionsUpcoming({ event }) | GET /v1/power-index/{org} (view: current · movers · peaks) / /v1/predictions/upcoming |
| matchmaker(org, { division, limit }) / whosNext(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 }) / recordBook(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 |
| orgDivision(org, division) | GET /v1/orgs/{org}/divisions/{division} — one weight class: rankings board, upcoming bouts, latest results, roster by recency |
| yearStats(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 |
| eventStorylines(slug) | GET /v1/events/{slug}/storylines — the talking points of one card: title fights, eliminators, closest bout, rematches, streaks, debuts, returns, ranked fighters, nations |
| eventPickem(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 |
| broadcastRights(org, { country, series }) / venue(id) / search(q) / usage() | misc (series: 'dwcs' \| 'rtufc' = a UFC sub-series grid) |
| venues({ q, country, limit }) / venueEvents(id, { status, from, to, order, limit }) | GET /v1/venues / …/{id}/events — venue search, and every covered event at one venue, newest first (paginated) |
| articles({ q, tag, locale, limit }) / 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 |
| webhookEndpoints() / createWebhookEndpoint(url, events) / rotateWebhookSecret(id) / deleteWebhookEndpoint(id) | /v1/webhook-endpoints (Pro+; kinds event.announced, fight.result, card.changed, event.completed, odds.moved — the consensus line moved 5+ points or the favourite flipped) |
| calendarIcsUrl(org) | GET /v1/calendar/{org}.ics (URL builder — no request) |
| get(path, params) / getWithMeta(path, params) | escape hatch for anything new |
Hand-written types cover events, fights, fighters, rankings, scorecards, consensus odds and plans; the generated paths / operations types from the OpenAPI document are exported for everything else.
Consensus odds
const o = await api.fightOdds(83379);
console.log(o.consensus?.a.american, o.consensus?.b.american, o.consensus?.sources);
console.log(o.movement?.delta_points_a, o.closing); // closing = last line before the start (settled bouts)
for await (const point of api.fightOddsHistory(83379)) { // Pro plans and up
console.log(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.
Agents and MCP
The same data is a Model Context Protocol server, so an agent can call it without an HTTP client:
claude mcp add --transport http ufcalendar https://api.ufcalendar.com/mcp \
--header "Authorization: Bearer $UFCALENDAR_API_KEY"There is an installable agent skill too: npx skills add UFCalendar/fight-api-skill. Agent quickstart: https://www.ufcalendar.com/developers/agents.
Options
new FightAPI({
apiKey, // or UFCALENDAR_API_KEY / UFCAL_API_KEY in the environment
baseUrl, // default https://api.ufcalendar.com/v1
fetch, // inject your own fetch
timeoutMs, // default 30_000
headers, // extra headers on every request
maxRetries, // per-minute 429 retries on GETs, default 3 (0 disables)
});Errors and rate limits
Every error throws FightAPIError with .status, .code, .message and .requestId — quote the request id when you write to [email protected]. After each call api.lastRateLimit holds the X-RateLimit-* headers (monthly quota).
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 waits for Retry-After plus a little jitter and tries again, up to maxRetries 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.
const api = new FightAPI('ufcalendar_...', { maxRetries: 5 }); // wait out more per-minute 429s
const strict = new FightAPI('ufcalendar_...', { maxRetries: 0 }); // throw on the first 429The monthly quota (429 quota_exceeded), a 429 with a long or missing Retry-After, and every
non-GET request (webhook endpoint create / delete / rotate) throw FightAPIError at once.
Notes
- Odds are the UFCalendar consensus line only (no book identities). Information only, not betting advice.
- Fighter
imagesare Wikimedia Commons / Creative Commons files: displaying thelicenseandartistfields as a credit is a licence requirement, not a style preference. - Scorecards are the official commission record. Media-member and fan scorecards are not part of the API.
source/source_slugvalues (typed asPublicSource) name official publishers only —ufc-stats,ufc-official,ufc-official-cards, a promotion's*-official,wikipedia,ufcalendar. Every other publisher arrives aspublic-records(career records, history rows) orcommission-record(judges' scorecards).- Not affiliated with UFC, Zuffa, TKO or any promotion. Terms: https://www.ufcalendar.com/developers/terms
Full reference: https://api.ufcalendar.com/docs · OpenAPI 3.1: https://api.ufcalendar.com/openapi.json · Also on PyPI (Python client), RapidAPI Hub, Postman and the hosted MCP server
MIT licensed.
