# UFC & MMA Stats Scraper — Fighters, Fights, Events & Rankings (`memo23/ufc-mma-stats-scraper`) Actor

Build MMA betting models, fantasy tools and fight databases on clean data: bout results with method and round, 42 measured stats per fighter per fight, reach, stance and gym per athlete. 19 promotions — UFC, PFL, ONE, Bellator, plus defunct PRIDE and Strikeforce. UFC back to 1994.

- **URL**: https://apify.com/memo23/ufc-mma-stats-scraper.md
- **Developed by:** [Muhamed Didovic](https://apify.com/memo23) (community)
- **Categories:** Sports, Developer tools, AI
- **Stats:** 2 total users, 1 monthly users, 77.8% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $50.00 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## UFC & MMA Stats Scraper — Fighters, Fights, Events & Rankings

Pull complete MMA data from ESPN's public API: every bout with its winner, method, round and finish time, the full 42-stat striking and grappling breakdown for each fighter in each fight, fighter profiles with reach, stance and gym, and every event card with venue. Covers 19 promotions — UFC, PFL, ONE, Bellator, Cage Warriors, KSW, RIZIN and more, including defunct PRIDE, Strikeforce and WEC. UFC history runs back to 1994.

![How it works](https://raw.githubusercontent.com/muhamed-didovic/muhamed-didovic.github.io/main/assets/how-it-works-ufc-mma.png)

### Why Use This Scraper

Most MMA scrapers give you the UFC and stop there. This one covers 19 promotions and reaches back to 1994, so a training set or a betting model is not capped at one organisation's calendar.

It also returns the real statistical line, not just results. Every bout carries 42 measured values per fighter — significant strikes split by head, body and leg across distance, clinch and ground positions; takedowns attempted, landed and their accuracy; guard passes and positional advances; reversals, submissions, knockdowns and time in control. That is the depth serious models need, and it comes from an API with no key, no login and no scraping challenge to defeat.

### Overview

| | |
|---|---|
| **Source** | ESPN public MMA API |
| **Promotions** | 19 (see Supported Inputs) |
| **History** | UFC from 1994; other promotions vary |
| **Modes** | Fights, Fight stats, Fighters, Fighter career, Rankings, Events |
| **Auth needed** | None |
| **Output** | JSON, CSV, Excel, XML |

### Supported Inputs

Promotions accepted in the `promotions` field:

| Slug | Promotion | Slug | Promotion |
|---|---|---|---|
| `ufc` | Ultimate Fighting Championship | `pancrase` | Pancrase |
| `pfl` | Professional Fighters League | `m1` | M-1 Global |
| `bellator` | Bellator | `tfc` | Titan FC |
| `ofc` | ONE Championship | `strikeforce` | Strikeforce |
| `lfa` | Legacy Fighting Alliance | `wec` | World Extreme Cagefighting |
| `cage-warriors` | Cage Warriors | `pride` | PRIDE Fighting Championships |
| `ksw` | Konfrontacja Sztuk Walki | `xfc` | Xtreme Fighting Championships |
| `rizin` | Rizin Fight Federation | `k1` | K-1 |
| `ifc` | Invicta FC | `dream` | Dream |
| | | `ifl` | International Fight League |

You can also target a run by year, by an explicit `YYYYMMDD` date range, by specific ESPN event IDs, or by specific fighter IDs.

### Use Cases

**Betting and prediction models.** Per-fighter striking accuracy, takedown defence and control time across a fighter's whole history are the standard feature set for MMA modelling. Pull several seasons in one run.

**Fantasy MMA.** Finish method and round drive most scoring systems. Fights mode gives both for every bout on a card.

**Sports media and stat pages.** Event cards with venue, plus fighter bios with reach, stance and gym, populate a fight preview without manual entry.

**Historical research.** PRIDE, Strikeforce and WEC data lets you compare eras rather than only the current UFC roster.

**Fighter scouting.** Filter the roster by weight class, stance, reach or gym association.

### How It Works

The actor talks to ESPN's core sports API. It lists events for each promotion and year you request, then walks each event down to its bouts, and each bout down to its two competitors, their athlete profiles and their statistical lines. Requests are spaced apart and run at low concurrency by default, because ESPN rate-limits bursts and will refuse traffic for several minutes if pushed too hard. Rows are pushed as they are produced, so a run that hits its result cap still returns everything gathered up to that point.

### Input Configuration

| Field | Type | Default | Notes |
|---|---|---|---|
| `mode` | select | `fights` | `fights`, `fightStats`, `fighters`, `fighterCareer`, `rankings`, `events` |
| `promotions` | array | `["ufc"]` | Any of the 19 slugs above |
| `years` | array | current year | Several years per run |
| `fromDate` / `toDate` | string | — | `YYYYMMDD`; overrides `years` |
| `eventIds` | array | — | Restrict to specific ESPN event IDs |
| `fighterIds` | array | — | Restrict Fighters mode to specific athletes |
| `searchQuery` | string | — | Find a fighter by name or nickname; resolves in seconds |
| `stance` | select | — | Orthodox, Southpaw, Switch, Open Stance, Sideways |
| `minWins` / `minLosses` | integer | — | Filter by career record |
| `proxyConfiguration` | proxy | off | Rotating IPs for long runs; ESPN throttles per address |
| `maxItems` | integer | 1000 | Caps run size and cost |
| `concurrency` | integer | 4 | Raise carefully — ESPN rate-limits |
| `throttleMs` | integer | 120 | Minimum gap between requests |

Example — every 2024 and 2025 UFC bout:

```json
{
  "mode": "fights",
  "promotions": ["ufc"],
  "years": [2024, 2025],
  "maxItems": 2000
}
```

Example — full striking breakdown for one event:

```json
{
  "mode": "fightStats",
  "promotions": ["ufc"],
  "eventIds": ["600039893"],
  "years": [2024]
}
```

### Output Overview

Each mode produces one row per entity: one per bout in `fights`, one per fighter per bout in `fightStats` (so a bout yields two rows), one per fighter in `fighters` and `fighterCareer`, one per ranked fighter in `rankings`, one per card in `events`. Every row is flat and one level deep, so it reads the same as JSON or as spreadsheet columns.

### Output Samples

**Fights**

```json
{
  "recordType": "fight",
  "promotion": "ufc",
  "promotionName": "Ultimate Fighting Championship",
  "fightId": "401623977",
  "eventId": "600039893",
  "eventName": "UFC Fight Night: Ankalaev vs. Walker 2",
  "date": "2024-01-13T21:00Z",
  "weightClass": "Flyweight",
  "scheduledRounds": 3,
  "roundLengthSeconds": 300,
  "fighter1Id": "4916248",
  "fighter1Name": "Felipe Bunes",
  "fighter2Id": "5120301",
  "fighter2Name": "Joshua Van",
  "winnerId": "5120301",
  "winnerName": "Joshua Van",
  "loserId": "4916248",
  "loserName": "Felipe Bunes",
  "isDraw": false,
  "isNoContest": false,
  "method": "KO/TKO",
  "methodDetail": "Punches",
  "methodTarget": "Head",
  "endRound": 2,
  "endClock": "4:31",
  "statusType": "STATUS_FINAL",
  "url": "https://www.espn.com/mma/fightcenter/_/id/600039893/league/ufc"
}
```

**Fight stats** (trimmed — 55 fields per row)

```json
{
  "recordType": "fightStats",
  "fightId": "401623977",
  "eventName": "UFC Fight Night: Ankalaev vs. Walker 2",
  "weightClass": "Flyweight",
  "fighterId": "4916248",
  "fighterName": "Felipe Bunes",
  "opponentName": "Joshua Van",
  "won": false,
  "knockDowns": 0,
  "totalStrikesAttempted": 101,
  "totalStrikesLanded": 57,
  "sigStrikesAttempted": 91,
  "sigStrikesLanded": 50,
  "sigDistanceHeadStrikesLanded": 28,
  "takedownsLanded": 0,
  "takedownAccuracy": 0,
  "submissions": 0,
  "timeInControl": 0
}
```

**Fighters**

```json
{
  "recordType": "fighter",
  "fighterId": "2354359",
  "fullName": "Jason Reinhardt",
  "nickname": null,
  "weightClass": "Bantamweight",
  "heightInches": 66,
  "displayHeight": "5' 6\"",
  "weightPounds": 145,
  "reachInches": null,
  "stance": "Orthodox",
  "association": null,
  "gender": "MALE",
  "active": false,
  "record": "20-3-0",
  "url": "https://www.espn.com/mma/fighter/_/id/2354359/jason-reinhardt"
}
```

**Events**

```json
{
  "recordType": "event",
  "eventId": "600039893",
  "name": "UFC Fight Night: Ankalaev vs. Walker 2",
  "date": "2024-01-13T21:00Z",
  "season": 2024,
  "venueName": "Meta APEX",
  "venueCity": "Las Vegas",
  "venueState": "NV",
  "venueCountry": "USA",
  "boutCount": 11
}
```

**Fighter career** (computed from that fighter's own bouts)

```json
{
  "recordType": "fighterCareer",
  "fighterId": "5120301",
  "fullName": "Joshua Van",
  "nickname": "The Fearless",
  "weightClass": "Flyweight",
  "stance": "Orthodox",
  "reachInches": 65,
  "association": "4oz Fight Club",
  "record": "17-2-0",
  "fightsFound": 20,
  "fightsWithStats": 12,
  "totalFightMinutes": 145.83,
  "sigStrikesLanded": 1230,
  "sigStrikesAttempted": 2144,
  "sigStrikesAbsorbed": 849,
  "sigStrikesLandedPerMin": 8.43,
  "sigStrikesAbsorbedPerMin": 5.82,
  "strikingAccuracy": 0.5737,
  "strikingDefense": 0.5826,
  "takedownsPer15Min": 0.82,
  "takedownAccuracy": 0.6154,
  "takedownDefense": 0.7568,
  "submissionsPer15Min": 0.21,
  "controlSeconds": 640
}
```

**Rankings**

```json
{
  "recordType": "ranking",
  "rankingName": "Men's Pound for Pound Rankings",
  "rankingType": "pound-for-pound",
  "gender": "MALE",
  "isChampionGroup": false,
  "rank": 1,
  "trend": "-",
  "titleDefenses": 2,
  "hasAccolade": true,
  "fighterName": "Kamaru Usman",
  "weightClass": "Welterweight"
}
```

### Key Output Fields

| Field | Description |
|---|---|
| `method` / `methodDetail` / `methodTarget` | How the fight ended — "KO/TKO", "Submission", "Decision - Unanimous"; detail and target are populated for finishes only |
| `endRound` / `endClock` | Round and clock at the finish; a decision reads as the final round at full time |
| `winnerId` / `loserId` | Result, by fighter ID rather than by corner |
| `isDraw` / `isNoContest` | Kept separate on purpose — a no contest is struck from official records, a draw is not |
| `weightClass` | Contracted weight class for the bout |
| `sigStrikes*` | Significant strikes, split by target (head/body/leg) and position (distance/clinch/ground) |
| `takedownsLanded` / `takedownAccuracy` | Grappling volume and efficiency |
| `timeInControl` | Control time, in seconds |
| `reachInches` / `stance` / `association` | Fighter reach, stance and gym; null when ESPN has no value |
| `record` | Career record summary, e.g. "20-3-0" |
| `sigStrikesLandedPerMin` / `AbsorbedPerMin` | Career striking volume, per minute of cage time |
| `strikingAccuracy` / `strikingDefense` | Landed ÷ attempted, and the inverse for strikes faced |
| `takedownAccuracy` / `takedownDefense` | Grappling efficiency both ways |
| `fightsFound` / `fightsWithStats` | How many bouts were seen versus how many had a stat line — every rate uses only the latter |
| `rank` / `trend` / `titleDefenses` | Ranking position, movement, and title defences for champions |

### FAQ

**Do I need an API key or ESPN account?** No. The source API is public.

**Why are `methodDetail` and `methodTarget` empty on some rows?** They describe the finishing blow. A fight that goes to the judges has no finishing strike, so both are null on decisions. That is correct, not missing data.

**Why is `reachInches` sometimes null?** ESPN stores unknown measurements as 0. Reporting a 0 would corrupt any average or filter, so unknowns are returned as null instead.

**What is the difference between `isDraw` and `isNoContest`?** A draw is an official result both fighters keep on their record. A no contest (an illegal blow, or a result overturned afterwards) is struck from the record entirely. Treating them as one value skews win-rate features, so they are separate booleans.

**How far back does history go?** UFC to 1994. Other promotions vary — PRIDE and Strikeforce cover their active years, PFL and LFA are recent. Ask for a year a promotion did not run and you get an empty result rather than an error.

**Why is the default concurrency only 4?** ESPN refuses traffic after a burst and stays cold for minutes. The conservative default finishes large runs faster than an aggressive one that gets throttled.

**Can I get a single event?** Yes — pass its ESPN ID in `eventIds` along with the year it took place.

**Are rankings included?** Yes — `rankings` mode returns 24 groups: every division's rankings and champions, plus men's and women's pound-for-pound, with rank, movement and title defences.

**Where do the career rate metrics come from?** ESPN publishes no career aggregate for MMA, so `fighterCareer` sums the fighter's own per-bout stat lines and derives the rates from those totals. Both the totals and the rates ship in the row, so any number can be recomputed or re-weighted. `fightsWithStats` tells you how many bouts actually carried a stat line — older and regional fights often do not, and those are excluded from every rate rather than counted as zeros.

**Why is fighter search fast but an unfiltered fighter run slow?** ESPN's roster index returns bare references with no names, so an unfiltered walk fetches every profile. A `searchQuery` goes through ESPN's search service instead and resolves to ids in one call.

### Support

Issues and feature requests go to the actor's Issues tab on Apify Console.

### Additional Services

Need a different site scraped, or this data pushed into your own pipeline? Get in touch through the Apify contact form on the actor page.

### Explore More Scrapers

- [MLB Stats Scraper](https://apify.com/memo23/mlb-stats-scraper) — baseball teams, players, scores and standings
- [Tennis Scraper](https://apify.com/memo23/flashscore-sofascore-tennis-scraper) — Flashscore, SofaScore and TennisExplorer data

### 🤖 For AI Agents & LLM Apps

This actor is a clean data source for agent pipelines. Every row is flat, typed and one level deep, so it drops straight into a vector store, a dataframe or a tool-call result without reshaping.

Call it from an agent with the Apify MCP server, or directly:

```json
{
  "mode": "fights",
  "promotions": ["ufc"],
  "years": [2025],
  "maxItems": 500
}
```

Field names are stable and self-describing (`winnerName`, `method`, `endRound`, `sigStrikesLanded`), which keeps prompt overhead low — an LLM can read a row without a schema doc. `recordType` on every row lets an agent route mixed datasets without inspecting keys.

### ⚠️ Disclaimer

This actor collects publicly available data from ESPN's public API. It does not access private data, bypass authentication, or defeat access controls. You are responsible for using the collected data in line with ESPN's terms, applicable law, and any restrictions on redistribution. This actor is not affiliated with, endorsed by, or connected to ESPN, the UFC, or any promotion named above. All trademarks belong to their respective owners.

### SEO Keywords

UFC scraper, MMA scraper, UFC stats API, MMA stats API, UFC fighter data, UFC fight results scraper, MMA fight statistics, UFC event scraper, fighter profile scraper, significant strikes data, takedown statistics, MMA betting data, fantasy MMA data, PFL scraper, Bellator scraper, ONE Championship data, PRIDE FC historical data, Strikeforce results, Cage Warriors data, KSW scraper, RIZIN data, Invicta FC scraper, MMA historical database, combat sports data, UFC API no key, ESPN MMA API, MMA machine learning dataset, fight card scraper

# Actor input Schema

## `mode` (type: `string`):

fights = one row per bout (fighters, weight class, winner, method, round, time). fightStats = one row per fighter per bout with the full 42-stat striking and grappling breakdown. fighters = one row per fighter profile. fighterCareer = career totals plus strikes-per-minute, accuracy and defence, computed from that fighter's own bouts. rankings = current division rankings, champions and pound-for-pound. events = one row per fight card.

## `promotions` (type: `array`):

Which promotions to cover. UFC has the deepest history (1994 to today). Defunct promotions such as PRIDE, Strikeforce and WEC are included for historical research.

## `years` (type: `array`):

Calendar years to cover, e.g. 2024, 2025. One run can span several years. Defaults to the current year. Ignored when a date range is set below.

## `fromDate` (type: `string`):

Start of an explicit date window, e.g. 20240101. Overrides Years when both this and To date are set.

## `toDate` (type: `string`):

End of an explicit date window, e.g. 20241231.

## `eventIds` (type: `array`):

Restrict the run to specific ESPN event IDs. Leave empty to take every event in the window.

## `fighterIds` (type: `array`):

Restrict Fighters mode to specific ESPN athlete IDs. Leave empty to take the whole roster.

## `searchQuery` (type: `string`):

Find a fighter by name or nickname, e.g. 'McGregor' or 'The Notorious'. Resolved through ESPN's search, so it returns in seconds. Applies to Fighters and Fighter career modes.

## `stance` (type: `string`):

Only return fighters with this stance. Leave empty for all.

## `minWins` (type: `integer`):

Only include fighters with at least this many wins, read from their record.

## `minLosses` (type: `integer`):

Only include fighters with at least this many losses.

## `proxyConfiguration` (type: `object`):

Optional. ESPN rate-limits by IP, so a long run behind rotating proxies survives where a single address gets refused. Not needed for small runs.

## `maxItems` (type: `integer`):

Stop the run after this many rows. Keeps a run's cost predictable.

## `concurrency` (type: `integer`):

Parallel requests. The default of 4 is deliberately conservative — ESPN rate-limits bursts and will refuse traffic for several minutes if pushed.

## `throttleMs` (type: `integer`):

Minimum gap between requests. Raise it if a large run starts hitting refusals.

## Actor input object example

```json
{
  "mode": "fights",
  "promotions": [
    "ufc"
  ],
  "years": [
    "2025"
  ],
  "stance": "",
  "proxyConfiguration": {
    "useApifyProxy": false
  },
  "maxItems": 1000,
  "concurrency": 4,
  "throttleMs": 120
}
```

# Actor output Schema

## `results` (type: `string`):

Every row produced by the run, in the mode's own column set.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "years": [
        "2025"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("memo23/ufc-mma-stats-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "years": ["2025"] }

# Run the Actor and wait for it to finish
run = client.actor("memo23/ufc-mma-stats-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "years": [
    "2025"
  ]
}' |
apify call memo23/ufc-mma-stats-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,memo23/ufc-mma-stats-scraper"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/aO8qndDfpNuLdgCzk/builds/RBs0p3CA8sCYvcfk8/openapi.json
