# Premier League Injuries & Availability Tracker (`gganbukim/premier-league-injury-scraper`) Actor

Every Premier League player who is injured, doubtful or suspended, with the official status note, the chance of playing the next round and when it was posted. Reads the league's own feed - no API key, no club pages to scrape. Monitor mode reports only what changed since the last run.

- **URL**: https://apify.com/gganbukim/premier-league-injury-scraper.md
- **Developed by:** [DONGMIN KIM](https://apify.com/gganbukim) (community)
- **Categories:** Sports, News, Automation
- **Stats:** 3 total users, 2 monthly users, 97.4% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.20 / 1,000 availability rows

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

## Premier League Injuries & Availability

![Premier League Injuries & Availability](https://raw.githubusercontent.com/gganbukim1/apify-actor-images/master/cards/premier-league-injury-scraper.png)

Every Premier League player who is **injured, doubtful, suspended or otherwise unavailable** — with the official status note, the percentage chance of playing the next round, and the date that note was posted.

One run covers all 20 clubs. No API key, no login, no club pages to scrape.

```json
{
  "webName": "Bruno G.",
  "name": "Bruno Guimarães",
  "team": "Newcastle United",
  "teamShort": "NEW",
  "position": "Midfielder",
  "status": "d",
  "statusLabel": "doubtful",
  "kind": "injury",
  "chanceOfPlayingNextRound": 75,
  "reason": "Thigh injury",
  "detail": "75% chance of playing",
  "expectedReturn": null,
  "news": "Thigh injury - 75% chance of playing",
  "newsAdded": "2026-08-20T17:30:22.531453Z",
  "daysSinceNews": 6,
  "price": 6.5,
  "source": "Official Fantasy Premier League API"
}
```

### Where the data comes from

The **Premier League's own Fantasy Premier League feed**. It is the league's public availability data: one status per player, a percentage chance of playing the next round, and a short note in plain English, updated by the game's editorial team as news breaks.

That matters for three reasons:

- **It is the club's position, not a rumour.** The note is what the league published, not an aggregation of press speculation.
- **It is stable.** No key, no rate-limit tier, no login, no headless browser. One request serves the whole league, so scheduled runs do not quietly break.
- **It is timestamped.** `newsAdded` tells you when the status last moved, so a stale note is visible as stale instead of reading like today's news.

This Actor is not affiliated with, endorsed by, or connected to the Premier League or Fantasy Premier League. It reads a public endpoint and reshapes it; all data belongs to its publisher.

### What you can ask for

| Option | What it does |
|---|---|
| **Clubs** | Club names or three-letter codes — `Arsenal`, `MCI`, `Newcastle`. Empty means all 20. |
| **Statuses** | Injured, doubtful, suspended, unavailable, not in squad, available. Empty means *everyone with something to report*. |
| **Kind of absence** | Injury, suspension, transfer or loan, personal, unclassified. |
| **Include fully fit players** | Off by default. On, you get all ~600 players as a squad snapshot. |
| **Minimum / maximum chance of playing** | Set both to isolate the genuinely doubtful, e.g. 25–75. |
| **Maximum rows** | 0 for no cap. A whole-league run is roughly 100–150 rows. |
| **Monitor mode** | Only what changed since the last run. See below. |

#### The filter rule worth knowing

**A filter that cannot be evaluated excludes the row.** If you set a minimum chance of playing and the league has not published a chance for a given player, that player is left out rather than let through. The alternative — treating "no value" as "nothing to object to" — delivers rows that were checked against nothing, and you would be paying for them.

#### Injuries, not transfers

The league files a sold player and a torn hamstring under the same status code. This Actor separates them: pick `Injury` under **Kind of absence** and the winger who joined another club last week stays out of your results.

### Monitor mode — only what changed

Turn on **Monitor mode** and each run compares against the previous one, returning only movement:

| Change | Meaning |
|---|---|
| `new_absence` | A fit player is now injured, doubtful or suspended |
| `returned` | A player who was unavailable is available again |
| `status_changed` | Injured → doubtful, doubtful → suspended, and so on |
| `chance_changed` | The percentage chance of playing moved, e.g. 25% → 75% |
| `news_updated` | Same status, rewritten note — often the first sign of a return date |

Each change row carries `previousStatus`, `previousStatusChance` and `previousNews`, so you can see what it moved from without keeping your own history.

**The first monitor run records a baseline and charges nothing.** Reporting the entire league as "newly injured" the first time would be a bill for news that is not news. Run it on a schedule — hourly or daily — and every run after the first returns only what moved.

Memory lives in a named key-value store (**Monitor memory store**, default `pl-availability-state`). Use different names to watch different filters independently; turn on **Reset the monitor memory** to start a fresh baseline.

### Output fields

| Field | Notes |
|---|---|
| `playerId`, `playerCode` | Stable ids for joining across runs |
| `name`, `webName` | Full name and the short form used in tables |
| `team`, `teamShort`, `teamId` | Club |
| `position`, `positionShort` | Goalkeeper / Defender / Midfielder / Forward |
| `status`, `statusLabel` | The league's code and its meaning |
| `kind` | injury · suspension · transfer · personal · unknown |
| `chanceOfPlayingNextRound` | 0–100, or `null` when unpublished |
| `chanceOfPlayingThisRound` | Same for the current round |
| `reason`, `detail` | The note, split at the dash: "Thigh injury" / "75% chance of playing" |
| `expectedReturn` | `YYYY-MM-DD`, **only when the note actually names a date** |
| `news`, `newsAdded`, `daysSinceNews` | The raw note, when it was posted, and how old it is |
| `price`, `selectedByPercent`, `minutes`, `totalPoints` | Fantasy price, ownership and season form |
| `source`, `sourceUrl`, `scrapedAt` | Provenance for every row |
| `changeType`, `previousStatus`, `previousStatusChance`, `previousNews`, `detectedAt` | Monitor mode only |

#### About `expectedReturn`

The league writes return dates as "Expected back 12 Sep" — day and month, never a year. This Actor resolves that to the **next occurrence at or after the run date** and returns `null` whenever the note names no date at all. A guessed date is worse than none: you can act on "unknown", and you cannot act on a wrong Tuesday.

### Pricing

Pay per event — you are charged for rows delivered, never for a failed run.

| Event | Price |
|---|---|
| Availability row | **$0.20 per 1,000** |
| Availability change reported (monitor mode) | **$0.40 per 1,000** |

A whole-league snapshot is around 100–150 rows, so a full run costs about three hundredths of a cent. A daily monitor typically reports a handful of changes a day.

Rows are billed **after** they are written to your dataset. If a run stops at your **Max total charge**, it says so in the log rather than silently truncating.

### What this does not do

Stated plainly, so you can decide before you run it:

- **Premier League only.** The feed covers the English top flight. No La Liga, Serie A, Bundesliga or Ligue 1.
- **No expected return date for most players.** The league publishes one when a club has given one; otherwise the note says "Unknown return date" and this Actor returns `null` rather than inventing an estimate.
- **No injury history.** Rows describe the current state, and monitor mode gives you the transitions from the moment you start watching. There is no backfill of past seasons.
- **No medical detail.** "Groin injury" is what is published; grade, scan results and treatment are not.

### Suggested uses

- **Fantasy and prediction tools** — availability is the input that changes lineups, and `chanceOfPlayingNextRound` is a number you can model with directly.
- **Team-news alerts** — monitor mode into Slack, Discord or a webhook, so a status flip reaches you within the hour.
- **Betting and trading models** — an absence detected before it is widely priced is the whole value; `newsAdded` tells you how fresh the signal is.
- **Sports media** — a maintained injury table without maintaining a scraper.
- **Squad planning and scouting** — combine the availability flag with `minutes` and `totalPoints` to see who is actually being relied on.

### Run it from code

Nothing here needs a login to the source, only your Apify token.

**HTTP** — start a run and wait for the rows:

```bash
curl -X POST "https://api.apify.com/v2/acts/gganbukim~premier-league-injury-scraper/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "content-type: application/json" \
  -d @input.json
```

**JavaScript**

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

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('gganbukim/premier-league-injury-scraper').call(input);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

**Python**

```python
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("gganbukim/premier-league-injury-scraper").call(run_input=input)
items = client.dataset(run["defaultDatasetId"]).list_items().items
```

**Scheduled or event-driven** — attach a [schedule](https://docs.apify.com/platform/schedules) to run it on a cron, or a [webhook](https://docs.apify.com/platform/integrations/webhooks) to push each finished run into your own endpoint. It also connects through Apify's [Zapier, Make, n8n and LangChain integrations](https://docs.apify.com/platform/integrations), and is reachable from an [MCP server](https://docs.apify.com/platform/integrations/mcp) if you are driving it from an agent.

**Standby / API mode** — the run above is synchronous: one call in, rows out, no polling. That is the shape to use if you are calling this per request rather than in a batch.

### Errors, limits and what you are charged for

- **You pay for delivered rows only.** A row your filters removed, a page that failed, a retry — none of it is billed. Starting a run costs $0.00001: the platform minimum, charged once per gigabyte, and this Actor runs on 512 MB.
- **A run that delivers nothing still costs the start fee and nothing else.** If the input resolved to zero items, the run fails loudly with the reason rather than finishing green on an empty dataset.
- **Blocking is handled by changing address, not by waiting.** The Actor starts on cheap datacenter proxies and moves up only after a tier has actually been refused several times in a row, then drops back down once the cheap tier answers cleanly again. You are not paying for residential bandwidth that was never needed.
- **Rate limits belong to the source, not to this Actor.** Very large inputs are worked through in batches; the run reports how many items succeeded, were filtered, and failed, so a partial result is never presented as a complete one.
- **Dataset retention follows your Apify plan.** Export what you need, or push it out with a webhook, if you want it past that window.

### Is this legal?

This Actor reads pages and public endpoints that anyone can open in a browser without an account. It does not log in, does not defeat a paywall, and does not touch anything behind authentication.

Scraping public data is broadly lawful in the US and the EU, and courts have repeatedly said so — but "public" is not the same as "unrestricted", and what you may then *do* with the data is a separate question from whether you may collect it. Personal data pulls in the GDPR and similar regimes whatever the source, so if your rows contain people, you need a lawful basis for keeping them.

Apify publishes a fuller treatment in [Is web scraping legal?](https://blog.apify.com/is-web-scraping-legal/) and an [ethical scraping guide](https://docs.apify.com/academy/web-scraping-for-beginners/crawling/scraping-ethics). None of this is legal advice; if the use is commercial and the data is personal, ask someone qualified.

### Something wrong, or missing?

Open an issue on the [Actor's Issues tab](https://apify.com/gganbukim/premier-league-injury-scraper/issues) — it goes straight to the developer and is the fastest route. Include the run ID; it carries the input and the log, which is usually enough to reproduce the problem without another round trip.

Sources change without warning, and a field that quietly goes null is worth reporting even if the run succeeded. A broken parser looks exactly like a quiet day in the data until someone says so.

# Actor input Schema

## `teams` (type: `array`):

Club names or three-letter codes — Arsenal, MCI, Newcastle. Leave empty for all 20.

## `statuses` (type: `array`):

Leave empty for every player who is not fully available. Availability is the league's own status code, not our judgement.

## `kinds` (type: `array`):

The league files a sold player and a torn hamstring under the same status. Pick 'Injury' to leave transfers out.

## `includeAvailable` (type: `boolean`):

Off by default: a run returns only players with something to report. Turn on to get all ~600 as a squad snapshot.

## `minChanceOfPlaying` (type: `integer`):

Players whose chance the league has not published are excluded when this is set — an unknown chance is not a pass.

## `maxChanceOfPlaying` (type: `integer`):

Use with the minimum to isolate the genuinely doubtful, e.g. 25 to 75.

## `maxRows` (type: `integer`):

0 means no cap. A whole-league run is around 100-150 rows.

## `onlyChanges` (type: `boolean`):

Compares against the previous run and returns only movement: a new absence, a return, a changed chance of playing, a rewritten note. The first run records a baseline and charges nothing.

## `changeTypes` (type: `array`):

Monitor mode only. Empty means all of them.

## `memoryStoreName` (type: `string`):

Named key-value store holding the last snapshot. Use different names to watch different filters independently.

## `resetMemory` (type: `boolean`):

Discards the stored snapshot so the next run starts a fresh baseline.

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

Leave the default. The run goes direct first, which costs nothing, and only changes route if the league refuses that address.

## Actor input object example

```json
{
  "teams": [],
  "statuses": [],
  "kinds": [],
  "includeAvailable": false,
  "maxRows": 0,
  "onlyChanges": false,
  "changeTypes": [],
  "memoryStoreName": "pl-availability-state",
  "resetMemory": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

One row per player: status, kind of absence, chance of playing the next round, the league's note and when it was posted. In monitor mode, one row per change.

## `runSummary` (type: `string`):

Counts for this run: players read, absences found, rows delivered, whether the run was a monitor baseline, and whether it stopped at its charge limit.

# 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 = {
    "teams": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("gganbukim/premier-league-injury-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 = { "teams": [] }

# Run the Actor and wait for it to finish
run = client.actor("gganbukim/premier-league-injury-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 '{
  "teams": []
}' |
apify call gganbukim/premier-league-injury-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,gganbukim/premier-league-injury-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/TdU0LeHYLnnJX2qAU/builds/bge2yITbsxCO0nuhu/openapi.json
