npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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.

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/sdk

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

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 card

Quickstart

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 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) throw 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: displaying the license and artist fields 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_slug values (typed as PublicSource) name official publishers only — ufc-stats, ufc-official, ufc-official-cards, a promotion's *-official, wikipedia, ufcalendar. Every other publisher arrives as public-records (career records, history rows) or commission-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.