# BestFightOdds MMA Odds Scraper (`artsiom_k/bestfightodds-scraper`) Actor

UFC and MMA betting odds from BestFightOdds: every sportsbook's moneyline and props, consensus opening and closing lines back to 2008, and full timestamped line movement.

- **URL**: https://apify.com/artsiom_k/bestfightodds-scraper.md
- **Developed by:** [Artsiom Kunitsyn](https://apify.com/artsiom_k) (community)
- **Categories:** Other
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.40 / 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.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

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

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## BestFightOdds MMA Odds Scraper

Scrapes UFC and MMA betting odds from [BestFightOdds](https://www.bestfightodds.com): **every sportsbook's
moneyline and prop prices**, the **consensus opening and closing line** for fights back to 2008, and **full
timestamped line movement**, for the UFC, PFL, ONE, Bellator, Rizin, LFA, Cage Warriors, KSW, Oktagon, BKFC
and dozens of other promotions. No login required.

### Contents

- [Key features](#key-features)
- [Output](#output)
- [Input](#input)
- [Input examples](#input-examples)
- [Incremental mode](#incremental-mode)
- [FAQ](#faq)

### 🔑 Key features

- **Every book side by side.** FanDuel, DraftKings, BetMGM, Caesars, BetRivers, BetWay, Unibet, plus the
  Kalshi and Polymarket prediction markets, with the best price and the book offering it.
- **Opening and closing lines.** Each row carries the consensus open and close, when the line opened and last
  moved, its range and how many times it moved: closing-line value and steam analysis without a second source.
- **Line movement, point by point.** `lineHistory` returns every recorded price change, per sportsbook and for
  the consensus, with a UTC timestamp.
- **Props.** Turn on `includeProps` for method of victory, rounds over/under, goes the distance, round betting,
  significant strikes and more, often 50+ markets per fight.
- **History back to 2008.** Every event since 2007 is listed. Per-book prices exist for recent years (about
  2021 on); for older fights the consensus line is still there.
- **Fighter odds histories.** `fighters` returns each fight a fighter had odds for: opening price, closing
  range and movement for both sides.
- **Incremental runs.** Schedule `odds` and get only the lines that moved since the last run.
- **`maxItems` defaults to 50**: a fast, cheap preview. Clear it (`null`) for everything in your selection.

### 📋 Output

The **Output** tab lists every field.

**`odds`**: one row per side of a market per fight:

```json
{
  "event": "UFC 334",
  "event_date": "2026-11-15",
  "promotion": "ufc",
  "matchup_id": 43676,
  "fighter_1": "Ciryl Gane",
  "fighter_2": "Josh Hokit",
  "market_type": "moneyline",
  "market": "Moneyline",
  "selection": "Ciryl Gane",
  "odds": { "FanDuel": -265, "Caesars": -275, "BetRivers": -275, "BetWay": -275, "Unibet": -275 },
  "best_odds": -265,
  "best_odds_decimal": 1.3774,
  "best_book": "FanDuel",
  "consensus_open": -150,
  "consensus_close": -271,
  "consensus_opened_at": "2026-06-15T13:20:03Z",
  "consensus_last_change_at": "2026-10-22T09:29:20Z",
  "consensus_changes": 36,
  "change_type": "new"
}
```

Prop rows have `market_type: "prop"`, the market as the site names it (`"Over 2½ rounds"`, `"Gane wins by
TKO/KO"`) and the `selection` for this side (`"Under 2½ rounds"`, `"Any other result"`).

**`lineHistory`**: one row per recorded price: event, fight, market, selection, `book` (a sportsbook or
`Consensus`), `timestamp`, `odds` (American) and `odds_decimal`.

**`fighters`**: one row per fighter with `fights`: event, date, `open`, `close_low`, `close_high`,
`movement_pct`, and the opponent's open and closing range.

### 🔧 Input

| Field | Type | Default | Description |
|---|---|---|---|
| `entityType` | string | `odds` | `odds`, `lineHistory` or `fighters`. |
| `promotions` | array | `["ufc"]` | Promotions to scrape. **Empty = every promotion.** |
| `startUrls` | array | — | Specific event or fighter pages. Replaces the promotion and date selection. |
| `dateFrom` / `dateTo` | string | — | Only events in this window (`YYYY-MM-DD`). |
| `includeProps` | boolean | `false` | Add every prop market next to the moneyline. |
| `consensus` | string | `moneyline` | `odds`: add the consensus opening and closing line to `moneyline` rows, `all` rows (props too: about 1 to 2 minutes per event) or `none`. |
| `historyBooks` | string | `all` | `lineHistory`: `all`, `consensus` or `books`. |
| `maxItems` | integer | `50` | Stop after this many rows. `null` = everything selected. |
| `mode` | string | `auto` | `auto` / `full` / `incremental`, `odds` only. |
| `concurrency` | integer | `6` | Requests in flight (max 12). |
| `impersonate` | string | `chrome` | Browser TLS fingerprint. Change only if requests are blocked. |
| `proxyConfiguration` | object | no proxy | Apify Proxy settings. Not needed in testing. |

### 📥 Input examples

**Default preview (upcoming UFC moneylines):**

```json
{}
```

**Every UFC moneyline and prop for 2025 (moneylines with opening and closing lines):**

```json
{ "promotions": ["ufc"], "dateFrom": "2025-01-01", "dateTo": "2025-12-31", "includeProps": true, "maxItems": null }
```

**Line movement of one card, every sportsbook:**

```json
{ "entityType": "lineHistory", "startUrls": ["https://www.bestfightodds.com/events/ufc-edmonton-4403"], "maxItems": null }
```

**Consensus closing lines for every PFL and ONE event since 2015:**

```json
{ "promotions": ["pfl", "one"], "dateFrom": "2015-01-01", "maxItems": null }
```

**A fighter's odds history:**

```json
{ "entityType": "fighters", "startUrls": ["https://www.bestfightodds.com/fighters/jon-jones-819"] }
```

### 🔁 Incremental mode

`odds` only, and only for a run with `maxItems` cleared (`null`): a capped run is a preview and always returns
rows. After the first complete run, a scheduled run returns only lines that are new or whose price moved
(`change_type` `new` or `changed`). Events older than 30 days are skipped in incremental runs, since their
prices no longer move. Runs with `startUrls` never update the stored state.

### ❓ FAQ

**Why are some `odds` empty?** BestFightOdds shows per-book prices only for the books it lists today. Fights
from before about 2021 keep their matchups, props and the consensus line (`consensus_open`, `consensus_close`),
which is the price history the site still has for them.

**What is the consensus line?** The average of the books' prices that BestFightOdds charts as its "Mean" line.
`consensus_close` is its latest value: the closing line once the event is over.

**Why does Kalshi or Polymarket differ from the sportsbooks after an event?** Prediction markets keep trading
until the market settles, so their last price can move after the books have closed.

**Do you have fight results?** No; BestFightOdds is an odds site. For results, methods and records, use the
[Sherdog MMA Scraper](https://apify.com/artsiom_k/sherdog-scraper) and join on fighter names and event dates.

**How do I pick a promotion that's not in the list?** Leave `promotions` empty for every event, or paste the
event pages into `startUrls`.

### Related sports data scrapers

- [Sherdog MMA Scraper](https://apify.com/artsiom_k/sherdog-scraper) — MMA fighter profiles, full fight histories, event cards and bout results for every promotion.
- [Action Network Scraper](https://apify.com/artsiom_k/actionnetwork-scraper) — US sportsbook odds, public betting splits, line movement, props, injuries and futures.
- [Kalshi & Polymarket Scraper](https://apify.com/artsiom_k/prediction-markets-scraper) — prediction market odds, settled results and price history for sports, politics, economics and crypto.
- [Sports Betting Odds History Scraper](https://apify.com/artsiom_k/sports-odds-history-scraper) — opening and closing spreads, moneylines and totals for US leagues, and results against the spread.
- [Smarkets Betting Exchange Scraper](https://apify.com/artsiom_k/smarkets-scraper) — betting exchange back and lay odds, matched volume, results and traded-price history.
- [Flashscore Scraper](https://apify.com/artsiom_k/flashscore-scraper) — results, statistics, lineups, incidents and opening/closing odds from ~20 bookmakers for 17 sports.

# Actor input Schema

## `entityType` (type: `string`):

"odds": one row per side of a market per fight, with every listed sportsbook's price, the best price and the consensus opening and closing line. "lineHistory": one row per recorded price change, per sportsbook and for the consensus. "fighters": one row per fighter with every fight's opening odds and closing range.

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

Promotions to scrape. Empty = every promotion BestFightOdds covers.

## `startUrls` (type: `array`):

Optional BestFightOdds event pages (https://www.bestfightodds.com/events/ufc-edmonton-4403) or fighter pages (.../fighters/jon-jones-819). When set, they replace the promotion and date selection.

## `dateFrom` (type: `string`):

Only events on or after this date. Events go back to 2007; sportsbook prices per book exist from about 2021, the consensus line for every year.

## `dateTo` (type: `string`):

Only events on or before this date.

## `includeProps` (type: `boolean`):

Odds and line history: add every prop market (method of victory, rounds over/under, goes the distance, round betting, significant strikes and more; often 50+ per fight) next to the moneyline.

## `consensus` (type: `string`):

Odds only: add the consensus (average of books) opening and closing price, when it opened and last moved, its range and how often it moved. One extra request per row, so for props it is slow (about 1 to 2 minutes per event). For events before about 2021 it is the only price there is.

## `historyBooks` (type: `string`):

Line history only: the consensus line, each sportsbook's own line, or both.

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

Stop after pushing this many rows. Defaults to 50 - a fast, cheap preview. Set to null for everything in your selection.

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

Odds only. "incremental" returns only rows that are new or whose prices moved since the last complete run; "full" returns everything. "auto" is incremental when Max results is cleared and an earlier run exists.

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

Requests in flight at once. The site starts refusing requests above about 10.

## `impersonate` (type: `string`):

Browser TLS fingerprint. Change only if requests are blocked.

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

Apify Proxy settings. Not needed in testing.

## Actor input object example

```json
{
  "entityType": "odds",
  "promotions": [
    "ufc"
  ],
  "includeProps": false,
  "consensus": "moneyline",
  "historyBooks": "all",
  "maxItems": 50,
  "mode": "auto",
  "concurrency": 6,
  "impersonate": "chrome",
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

No description

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("artsiom_k/bestfightodds-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 = {}

# Run the Actor and wait for it to finish
run = client.actor("artsiom_k/bestfightodds-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 '{}' |
apify call artsiom_k/bestfightodds-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,artsiom_k/bestfightodds-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/CjDzGi15N6dHOPHc7/builds/n8hfPEPCoIXrZxVRa/openapi.json
