# Flashscore Odds Movement & Closing Line Tracker (`incognito_mode/flashscore-odds-tracker`) Actor

Track betting line movement on Flashscore. Run it on a schedule: each run snapshots pre-match odds from 100+ bookmakers and returns only the prices that moved, with previous, current and opening odds, plus closing lines at kick-off. 1X2, totals, handicaps, 21 sports.

- **URL**: https://apify.com/incognito\_mode/flashscore-odds-tracker.md
- **Developed by:** [Elena Vance](https://apify.com/incognito_mode) (community)
- **Categories:** Sports, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.70 / 1,000 odds movements

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

## Flashscore Odds Movement & Closing Line Tracker

Track how betting odds move on [Flashscore](https://www.flashscore.com)'s odds
comparison — **1X2, moneyline, over/under, Asian handicap** and more, from
**100+ bookmakers across countries** — and capture every line's **closing
price at kick-off**. Football, tennis, basketball, hockey and 17 more sports.

Flashscore shows only two prices per outcome: the opening one and the current
one. Nothing in between is kept anywhere. This Actor builds that history for
you: **run it on a schedule**, and each run compares the bookmakers' prices
with the previous run and returns **only the lines that moved** — previous
price, new price, change in percent, and the opening price for context.

You pay for movement, not for polling: a run where nothing moved returns
nothing and costs only the start fee.

No API key, no login, no proxy, no browser. Export to **JSON, CSV, Excel** or
pull it from the API.

***

### How it works

1. **Every run** lists the upcoming matches in your leagues (starting within
   `horizonHours`), reads every bookmaker's prices, and compares each line with
   what it last reported for it.
2. **A line that moved** becomes one row: `changeType: moved`, with
   `homePreviousOdds → homeOdds` and so on. A main over/under line moving from
   2.5 to 2.75 goals is a movement too (`previousLine → line`).
3. **A line seen for the first time** — on the tracker's first run, or when a
   match enters the horizon — becomes one `first-seen` row with its current
   and opening price: the baseline later movements are measured from.
4. **After kick-off**, the next run writes every tracked line's **closing
   price** (`recordType: closing`). Flashscore freezes pre-match odds at
   kick-off, so this is the exact closing line, not an approximation from the
   last snapshot.

The tracker remembers prices between runs in a named key-value store,
`flashscore-odds-tracker-{trackerName}`. Runs with the same `trackerName`
share it; give each schedule its own name.

### Quick start: schedule it

1. Fill the input — for example the Premier League, GB bookmakers:

   ```json
   {
     "trackerName": "premier-league",
     "leagues": ["ENGLAND: Premier League"],
     "horizonHours": 48,
     "bookmakerCountries": ["GB"],
     "markets": ["HOME_DRAW_AWAY", "OVER_UNDER", "ASIAN_HANDICAP"],
     "historyDatasetName": "odds-history-epl"
   }
   ```

2. Save it as a **task**, then open **Schedules → Create new**, add the task,
   and pick an interval. Every **15 minutes** (`*/15 * * * *`) catches most
   moves; every 5 minutes before big matches catches steam; hourly is plenty
   for early-week markets.

3. Set `historyDatasetName` so every run appends to **one continuous
   dataset** instead of scattering rows across one dataset per run.

The schedule interval is the tracker's time resolution: a move is timestamped
between `previousObservedAt` and `observedAt`.

### What you get

Real, unedited output — Betway moving on Uzbekistan U23 v Japan U23, 44
minutes before kick-off:

| Field | Value |
| --- | --- |
| `recordType` / `changeType` | `movement` / `moved` |
| `eventId` / `eventUrl` | `S2o8i9BD` / `https://www.flashscore.com/match/S2o8i9BD/` |
| `tournament` / `startTime` | `ASIA: Asian Games - Play Offs` / `2026-09-30T10:30:00Z` |
| `homeName` / `awayName` | `Uzbekistan U23` / `Japan U23` |
| `bookmakerName` / `bookmakerId` / `bookmakerCountry` | `Betway` / `26` / `GB` |
| `market` / `period` | `HOME_DRAW_AWAY` / `FULL_TIME` |
| `homeOpeningOdds` → `homePreviousOdds` → `homeOdds` | `4.2` → `6.0` → `5.5` |
| `drawOpeningOdds` → `drawPreviousOdds` → `drawOdds` | `3.4` → `3.75` → `3.75` |
| `awayOpeningOdds` → `awayPreviousOdds` → `awayOdds` | `1.75` → `1.5` → `1.53` |
| `maxChangePercent` / `shortenedOutcome` | `-8.33` / `home` |
| `margin` / `previousMargin` | `0.1021` / `0.1` |
| `previousObservedAt` / `observedAt` / `minutesToStart` | `2026-09-30T09:42:29Z` / `2026-09-30T09:45:22Z` / `44` |
| `outcomes` | `[{"name":"home","odds":5.5,"previousOdds":6,"change":-0.5,"changePercent":-8.33,"openingOdds":4.2,"active":true}, …]` |

The usual outcomes have fixed columns — `home`, `draw`, `away`, `over`,
`under`, `yes`, `no`, each as `…Odds`, `…PreviousOdds` and `…OpeningOdds` —
so a spreadsheet or SQL table needs no JSON unpicking. Every outcome, including
correct scores, is also in `outcomes` with its own change.

#### Row types

| `recordType` | `changeType` | Meaning |
| --- | --- | --- |
| `movement` | `first-seen` | First sighting of a line: current price next to the opening price |
| `movement` | `moved` | A price (or the main line itself) changed since it was last reported |
| `movement` | `suspended` / `reactivated` | The bookmaker pulled or restored the line |
| `closing` | `closing` | The closing price after kick-off, next to the last price the tracker saw and the opening price |

#### Closing lines and CLV

Closing rows carry the **closing price** (`homeOdds`…), the **opening price**
(`homeOpeningOdds`…) and the tracker's **last pre-kick-off observation**
(`homePreviousOdds`…). Join them with your bets on `eventId` + `bookmakerId` +
`market` + `line` to measure closing-line value. `closingSource` is
`flashscore` when the closing price was read after kick-off (the normal case)
and `last-snapshot` if that read failed.

#### Handicaps are always from the home side

`line: -1.5` on an Asian handicap row means **the home side gives 1.5**.
Flashscore quotes each side from its own view; this Actor pairs them, so every
handicap row is a complete two-way book. Tennis set and game lines are kept
apart by `lineType` (`SETS` / `GAMES`).

### Input

| Field | Default | What it does |
| --- | --- | --- |
| `trackerName` | `default` | Names the tracker's memory. One name per schedule. |
| `sports` | football | Sports to follow. |
| `leagues` | all | Competition names (`ENGLAND: Premier League`) or Flashscore league URLs. **Strongly recommended.** |
| `matchIds` | — | Follow specific matches (id or URL) until kick-off instead of leagues. |
| `horizonHours` | 24 | Follow matches starting within this many hours (max 168). |
| `bookmakerCountries` | `GB` | Bookmaker countries; several are merged, each bookmaker once. `US-NJ`, `CA-ON` for per-state markets. |
| `bookmakers` | all | Only these bookmakers (part of the name or id). |
| `markets` | 1X2, moneyline, over/under, Asian handicap | Markets to track. |
| `periods` | `FULL_TIME` | `FIRST_HALF`, `FIRST_SET`, … |
| `lines` | main | Main line only, or every line. |
| `minChangePercent` | 0 | Report a line only once a price has moved this much since it was last reported. Small drifts add up. |
| `emitFirstSeen` | true | Report first sightings (the baseline). |
| `emitClosingLines` | true | Write closing lines after kick-off. |
| `historyDatasetName` | — | Also append every row to this named dataset. |
| `maxMatches` | 50 | Matches per run, soonest first (matches without odds don't count). |
| `maxItems` | 5000 | Charged rows per run. Anything over it is carried to the next run, not lost. |

### Pricing

Pay per event — you pay only for what is reported:

| Event | Price |
| --- | --- |
| Price movement or first sighting (`odds-movement`) | $2.00 per 1,000 rows |
| Closing line (`closing-line`) | $1.00 per 1,000 rows |
| Run start | $0.00005 per run |

A row is a whole bookmaker line — all three 1X2 prices, or both sides of a
total — not a single price. Unchanged lines are free. What a schedule costs
depends on how many lines you follow and how much they move. As a guide, one
Premier League round (10 matches, GB bookmakers, 1X2 + over/under + Asian
handicap, main lines) is about 800 lines:

- **First sightings:** ~800 rows once per round ≈ $1.60.
- **Movement:** typically 2–10 % of lines move per 15-minute run, more in the
  last hours before kick-off ≈ $0.03–0.16 per run.
- **Closing lines:** ~800 rows per round ≈ $0.80.

Set `minChangePercent` (for example 2) to report only meaningful moves, and
`maxItems` and the run's maximum charge to cap any single run.

### FAQ

**Why is the first run bigger than the rest?** It reports every line once as
`first-seen`, with its opening price — the baseline. Later runs report only
changes. Turn off `emitFirstSeen` to skip the baseline.

**What if two scheduled runs overlap?** The second one sees the first holding
the tracker's lock and exits immediately without charging for data. A lock
left by a crashed run expires after 45 minutes.

**What if a run hits my maximum charge?** It stops gracefully. Movements it
could not report are kept and reported by the next run, measured from the last
price you actually received.

**Does it track in-play odds?** No. Flashscore's odds comparison is pre-match;
at kick-off the prices freeze, which is what makes the closing line exact.

**Can I get history from before I started?** Only the opening price, which
every row carries. Flashscore keeps no history in between, which is why this
tracker exists — start it early in the week.

**Postponed matches?** They are dropped without a closing line. A delayed
kick-off is followed until the match actually starts.

**How do I reset a tracker?** Use a new `trackerName`, or delete the key-value
store `flashscore-odds-tracker-{name}` in Storage.

### Related

- **[Flashscore Betting Odds & Opening Lines Scraper](https://apify.com/incognito_mode/flashscore-odds-scraper)** —
  a one-off snapshot of every bookmaker's current and opening odds, for any
  match, including finished ones.
- **[Flashscore Tennis Scraper](https://apify.com/incognito_mode/flashscore-tennis-scraper)** —
  tennis results, statistics, point-by-point, rankings and draws.

This Actor is not affiliated with Flashscore or Livesport. It reads publicly
available odds; check that your use complies with the site's terms and your
local gambling regulations.

***

### Ready-made examples

Open one, press **Try for free**, and change the input to your own:

- [Track Premier League odds movement](https://apify.com/incognito_mode/flashscore-odds-tracker/examples/premier-league-odds-movement)
- [Collect closing lines for Europe's top 5 leagues](https://apify.com/incognito_mode/flashscore-odds-tracker/examples/top-5-leagues-closing-lines)
- [Detect steam moves in football odds](https://apify.com/incognito_mode/flashscore-odds-tracker/examples/football-steam-moves)
- [Track bet365 football odds over time](https://apify.com/incognito_mode/flashscore-odds-tracker/examples/bet365-odds-tracker)

### More Flashscore Actors

Same data source, same flat rows and pay-per-result pricing:

- [Flashscore Betting Odds Scraper](https://apify.com/incognito_mode/flashscore-odds-scraper) — pre-match odds from 100+ bookmakers with opening prices, plus outright winner odds
- [Flashscore Match Stats Scraper](https://apify.com/incognito_mode/flashscore-match-stats-scraper) — player stats with xG, lineups, box scores and team stats per match
- [Flashscore League Archive](https://apify.com/incognito_mode/flashscore-league-archive-scraper) — every season's results, tables and top scorers, back to 1901
- [Flashscore Teams & Players](https://apify.com/incognito_mode/flashscore-team-player-scraper) — squads, transfers with fees, market values and player careers
- [Flashscore Tennis Scraper](https://apify.com/incognito_mode/flashscore-tennis-scraper) — ATP/WTA results, serve stats, point-by-point, draws and rankings

# Actor input Schema

## `trackerName` (type: `string`):

Names the tracker's memory: runs with the same name share one key-value store (flashscore-odds-tracker-{name}) and compare against each other's snapshots. Use one name per schedule, e.g. premier-league. Letters, digits and hyphens, up to 30 characters.

## `sports` (type: `array`):

Which sports to follow. Ignored when Match IDs are given.

## `leagues` (type: `array`):

Only matches from these competitions — strongly recommended, since every sport lists hundreds of minor fixtures a day. Either part of the name as Flashscore shows it ("ENGLAND: Premier League") or a Flashscore league URL, which matches exactly. Leave empty for every competition.

## `matchIds` (type: `array`):

Follow specific matches instead of leagues: the 8-character id (MNThRu6l) or any Flashscore match URL. Each is tracked until kick-off, whenever that is. When set, Sports, Leagues and Horizon are ignored.

## `horizonHours` (type: `integer`):

Follow matches starting within this many hours. A match enters tracking when it comes into the horizon and leaves at kick-off, when its closing line is written. Up to 168 (7 days), the furthest Flashscore lists.

## `bookmakerCountries` (type: `array`):

Flashscore shows the bookmakers licensed in one country at a time. List several and their bookmakers are merged, each bookmaker once: GB alone is about 20 bookmakers. Two-letter codes; per-state markets take a region (US-NJ, US-PA, CA-ON). Each country is one extra request per match per run.

## `bookmakers` (type: `array`):

Only these bookmakers, by part of the name or by numeric id. "bet365" also matches bet365.it and bet365.us. Leave empty for every bookmaker in the chosen countries.

## `markets` (type: `array`):

Which betting markets to track. Every market multiplies what can move: correct score alone is 25 outcomes per bookmaker.

## `periods` (type: `array`):

Which part of the match: FULL\_TIME, FIRST\_HALF, SECOND\_HALF, FULL\_TIME\_OVER\_TIME (including overtime), FIRST\_SET, SECOND\_SET. An empty list returns every period.

## `lines` (type: `string`):

Over/under and handicap markets come in many lines per bookmaker. "Main line only" follows the one each bookmaker prices closest to even money, so a move from 2.5 to 2.75 goals is reported as a line movement. "All lines" follows every line separately and reports 5-10 times the rows.

## `minChangePercent` (type: `integer`):

Only report a line once one of its prices has moved at least this much since it was last reported. Small drifts add up: 1.90 → 1.88 → 1.86 → 1.84 is reported at a 3% threshold once the total move reaches 3%. 0 reports every change.

## `emitFirstSeen` (type: `boolean`):

The first run of a tracker, and every match as it enters the horizon, reports each line once with its current and opening price: the baseline later movements are measured from. Turn off to report only movements observed by the tracker itself.

## `emitClosingLines` (type: `boolean`):

After kick-off, write every tracked line's closing price (Flashscore freezes pre-match odds at kick-off) next to the last price the tracker saw and the opening price — the data for closing-line value.

## `historyDatasetName` (type: `string`):

Also append every reported row to this named dataset, so a schedule builds one continuous history instead of one dataset per run. Letters, digits and hyphens, e.g. odds-history-epl. Leave empty to use only each run's own dataset.

## `maxMatches` (type: `integer`):

Follow at most this many matches per run, soonest kick-off first. Matches with no odds yet do not count.

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

Hard cap on charged rows per run. Anything over it is not lost: unreported movements and closing lines are carried to the next run.

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

Leave this off. Flashscore's data is reachable directly, and the bookmaker country is chosen above, not by IP — a proxy adds cost and no bookmakers. A datacenter IP is tried automatically only if a request is actually refused.

## Actor input object example

```json
{
  "trackerName": "default",
  "sports": [
    "football"
  ],
  "leagues": [
    "https://www.flashscore.com/football/england/premier-league/",
    "SPAIN: LaLiga"
  ],
  "matchIds": [
    "https://www.flashscore.com/match/MNThRu6l/"
  ],
  "horizonHours": 24,
  "bookmakerCountries": [
    "GB",
    "DE",
    "US-NJ"
  ],
  "bookmakers": [
    "bet365",
    "Betfair",
    "William Hill"
  ],
  "markets": [
    "HOME_DRAW_AWAY",
    "HOME_AWAY",
    "OVER_UNDER",
    "ASIAN_HANDICAP"
  ],
  "periods": [
    "FULL_TIME"
  ],
  "lines": "main",
  "minChangePercent": 0,
  "emitFirstSeen": true,
  "emitClosingLines": true,
  "historyDatasetName": "odds-history-epl",
  "maxMatches": 50,
  "maxItems": 5000,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `dataset` (type: `string`):

Dataset containing every reported movement and closing line.

# 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 = {
    "trackerName": "default",
    "sports": [
        "football"
    ],
    "leagues": [
        "ENGLAND: Premier League"
    ],
    "bookmakerCountries": [
        "GB"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("incognito_mode/flashscore-odds-tracker").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 = {
    "trackerName": "default",
    "sports": ["football"],
    "leagues": ["ENGLAND: Premier League"],
    "bookmakerCountries": ["GB"],
}

# Run the Actor and wait for it to finish
run = client.actor("incognito_mode/flashscore-odds-tracker").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 '{
  "trackerName": "default",
  "sports": [
    "football"
  ],
  "leagues": [
    "ENGLAND: Premier League"
  ],
  "bookmakerCountries": [
    "GB"
  ]
}' |
apify call incognito_mode/flashscore-odds-tracker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,incognito_mode/flashscore-odds-tracker"
        }
    }
}
```

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/kPRppea0CmMIgIaMd/builds/cQBd8waSuYWVXaeur/openapi.json
