# Sports Injury Report Scraper & Change Monitor - NFL NBA MLB NHL (`neverempty/espn-injury-report-scraper`) Actor

Turn monitoring on and every later run returns only the injury reports that are new or have changed, so you are never charged for the same report twice. Every ESPN injury report as JSON: player, team, position, status and analyst comments. NFL, NBA, MLB, NHL, WNBA and college football.

- **URL**: https://apify.com/neverempty/espn-injury-report-scraper.md
- **Developed by:** [NeverEmpty](https://apify.com/neverempty) (community)
- **Categories:** Sports, News, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.46 / 1,000 injury report returneds

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?

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

## Sports Injury Report Scraper & Change Monitor — NFL, NBA, MLB, NHL

**Turn monitoring on and every later run returns only the reports that are new or have changed, so you are never charged for the same report twice.**

Every injury ESPN publishes, as one flat row: the player and their ESPN id, the team, the position, the status as ESPN prints it *and* as ESPN's own code, the timestamp, and ESPN's short and long analyst commentary.

> **Unofficial.** This Actor is not affiliated with or endorsed by ESPN. It reads ESPN's public injuries feed — no key, no login, no HTML parsing.

### What it does

- 🏥 **Six leagues, one row shape.** NFL, NBA, MLB, NHL, WNBA and college football. On 2026-09-06 that was **1,261 reports** (NFL 800, MLB 283, NBA 75, NHL 61, WNBA 39, NCAAF 3) — **1,219** of them in the four leagues read by default.
- 🔁 **Change monitoring, not just new rows.** An injury keeps the same ESPN id while its status moves from `Out` to `Day-To-Day`. Monitoring compares the **status, timestamp and comment**, so a *changed* report comes back as new — which is the whole point of watching an injury list.
- 🗣 **Analyst commentary included.** 772 of the 800 NFL reports carried a short and long comment on 2026-09-06.
- 🆔 **The player's ESPN id, which ESPN does not put in the record.** It only exists inside the player-card link; this Actor reads it out so you can join with any other ESPN dataset. 1,261 of 1,261 rows have it.
- ⚡ **No HTML, no proxy, no key.** A public JSON feed, read directly.

### 🔴 What this Actor refuses to guess

**ESPN answers HTTP 200 with an empty feed for leagues it has no injury data for** — every soccer league and college basketball. A scraper that just asks and reports what comes back will tell you *"no injuries"* for the Premier League, which is false: there is simply no feed.

So this Actor **only asks for the six leagues it has verified**, and refuses the rest with an explanation instead of returning an empty result as fact. A league with a real but empty feed, a feed that could not be read, and filters that matched nothing each come back as **their own row with the reason written out**, and none of them are charged.

### What you get in every row

| Field | What it is |
|---|---|
| `injuryId` | ESPN's id for the report (stays the same while the report is updated) |
| `reportKey` | Changes whenever the status, timestamp or comment changes — this is what monitoring compares |
| `league`, `leagueName`, `sport` | `nfl` / `NFL` / `football` |
| `seasonYear`, `seasonType` | e.g. 2026, `Preseason` |
| `teamId`, `teamName`, `teamAbbreviation`, `teamSlug` | `22`, `Arizona Cardinals`, `ARI`, `arizona-cardinals` |
| `athleteId`, `athleteName`, `athleteShortName` | `2578570`, `Jacoby Brissett`, `J. Brissett` |
| `position`, `positionAbbreviation` | `Quarterback`, `QB` |
| `athleteUrl`, `headshotUrl` | ESPN player card and headshot (794 of 800 NFL rows have a headshot) |
| `injuryStatus` | As ESPN prints it: `Active`, `Out`, `Questionable`, `Day-To-Day`, `Injured Reserve`, `Suspension`, `Bereavement`, `7-Day IL`, `10-Day-IL`, `15-Day-IL`, `60-Day-IL` |
| `injuryStatusCode` | ESPN's own code, e.g. `INJURY_STATUS_DAYTODAY` (11 distinct codes) |
| `injuryStatusAbbreviation`, `injuryStatusDescription` | `A`, `active` |
| `reportedAt`, `reportedAtRaw` | The timestamp as an ISO instant, **and** exactly as ESPN wrote it |
| `shortComment`, `longComment` | ESPN's analyst commentary |

#### Two things about the data worth knowing

- **ESPN prints some statuses in both cases** — `Suspension` and `suspension`, `Bereavement` and `bereavement`. The value is passed through unchanged rather than "tidied", so what you get is what ESPN shows. Filtering on status ignores case, and also matches ESPN's code, so `"suspension"` catches both.
- **ESPN omits the seconds** in its timestamps (`2026-09-04T21:36Z`). `reportedAt` is the parsed instant; `reportedAtRaw` is ESPN's own string, so you can always check the conversion rather than trust it.

### What you can do with it

**🔔 Watch a team without paying twice.** Monitoring mode plus `teams: ["Cardinals"]` on a schedule: each run returns only what actually changed.

**📊 Feed a fantasy or betting model.** Status plus analyst commentary plus the player's ESPN id, ready to join with scores and rosters.

**📰 Drive a news bot.** `changedSince` and `requireComment` give you just the reports with fresh commentary.

**🩺 Track one player's history.** `players: ["Brissett"]` across scheduled runs builds a timeline of status changes.

### Configure the run

A first run with the defaults reads the four major leagues:

```json
{
  "leagues": ["nfl", "nba", "mlb", "nhl"],
  "maxInjuries": 200
}
```

Watch one team on a schedule, paying only for changes:

```json
{
  "leagues": ["nfl"],
  "teams": ["Cardinals"],
  "monitoringMode": true,
  "maxInjuries": 100
}
```

Only players who are actually sidelined, with commentary:

```json
{
  "leagues": ["nba", "nhl"],
  "statuses": ["out", "day-to-day"],
  "requireComment": true
}
```

### Input reference

Every field is optional; the defaults read the four major leagues.

| Field | Type | Default | What it does |
|---|---|---|---|
| `leagues` | array | `["nfl", "nba", "mlb", "nhl"]` | Which leagues to read. Only these six have an ESPN injury feed with real data (checked 2026-09-06: NFL 800 reports, MLB 283, NBA 75, NHL 61, WNBA 39, college football 3). ESPN… |
| `maxInjuries` | integer | `200` | How many injury reports to return. You are charged for the rows you actually receive. All four default leagues together carried 1,219 reports on 2026-09-06. |
| `monitoringMode` | boolean | `false` | Off = return every report for the leagues you picked. On = remember what was already returned and, on later runs, return only reports that are new or have changed. An injury… |
| `resetMonitoringState` | boolean | `false` | Clear the remembered reports before this run, so everything counts as new again. |
| `statuses` | array | `[]` | Keep a report only if its status matches one of these (case-insensitive; matched against both the readable status and ESPN's code). Seen on 2026-09-06: Active, Out,… |
| `teams` | array | `[]` | Keep a report only if the team name, abbreviation or slug contains one of these (case-insensitive, partial). Example: Cardinals, ARI, arizona-cardinals. |
| `players` | array | `[]` | Keep a report only if the player's name contains one of these (case-insensitive, partial). Example: Brissett. |
| `positions` | array | `[]` | Keep a report only if the position or its abbreviation contains one of these (case-insensitive, partial). Example: Quarterback, QB, Pitcher. |
| `requireComment` | boolean | `false` | Drop reports that carry no short or long comment. On 2026-09-06, 772 of the 800 NFL reports had one. |
| `changedSince` | string | `(empty)` | Keep only reports ESPN timestamped on or after this date. Reports with an unreadable timestamp are kept, because unknown is not the same as old. |
| `keywords` | array | `[]` | Keep a report only if one (Any) or all (All) of these appear in the player, team, position, status or the analyst comments. Example: ankle, hamstring. |
| `keywordMatch` | string | `any` | Any = at least one keyword. All = every keyword. |
| `excludeKeywords` | array | `[]` | Drop a report if any of these appear in the same fields. |
| `useProxy` | boolean | `false` | Off by default: ESPN's public feed answered 200 from a plain Apify datacentre address when this Actor was built (2026-09-06). If it starts answering 403, 429 or 503, the Actor… |

### Pricing

Pay per result: you are charged for the injury rows you actually receive. Rows that only explain why there is nothing (`no-results`, `no-such-league`, `unreadable`, `no-filter-match`, `no-new-injuries`) are never charged. Reading a league's feed is free — only returned reports cost anything.

### Every number in this README is checked

The fixtures in `test/fixtures/` are the real feeds this Actor was built from (2026-09-06). The test suite re-counts every figure quoted above — 800, 283, 75, 61, 39, 3, 1261, 1219, 772, 794, 11, 32 — so if ESPN changes its feed, the tests fail rather than the data going quietly wrong.

# Actor input Schema

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

Which leagues to read. Only these six have an ESPN injury feed with real data (checked 2026-09-06: NFL 800 reports, MLB 283, NBA 75, NHL 61, WNBA 39, college football 3). ESPN answers HTTP 200 with an empty feed for every soccer league and college basketball, which is not the same as 'nobody is injured', so those are refused rather than reported as empty.

## `maxInjuries` (type: `integer`):

How many injury reports to return. You are charged for the rows you actually receive. All four default leagues together carried 1,219 reports on 2026-09-06.

## `monitoringMode` (type: `boolean`):

Off = return every report for the leagues you picked. On = remember what was already returned and, on later runs, return only reports that are new or have changed. An injury keeps the same id while its status moves from Out to Day-To-Day, so this compares the status, the timestamp and the comment - not just the id - and a changed report comes back as new.

## `resetMonitoringState` (type: `boolean`):

Clear the remembered reports before this run, so everything counts as new again.

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

Keep a report only if its status matches one of these (case-insensitive; matched against both the readable status and ESPN's code). Seen on 2026-09-06: Active, Out, Questionable, Day-To-Day, Injured Reserve, Suspension, Bereavement, 7-Day IL, 10-Day-IL, 15-Day-IL, 60-Day-IL. Note ESPN prints some of them in both cases (Suspension and suspension).

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

Keep a report only if the team name, abbreviation or slug contains one of these (case-insensitive, partial). Example: Cardinals, ARI, arizona-cardinals.

## `players` (type: `array`):

Keep a report only if the player's name contains one of these (case-insensitive, partial). Example: Brissett.

## `positions` (type: `array`):

Keep a report only if the position or its abbreviation contains one of these (case-insensitive, partial). Example: Quarterback, QB, Pitcher.

## `requireComment` (type: `boolean`):

Drop reports that carry no short or long comment. On 2026-09-06, 772 of the 800 NFL reports had one.

## `changedSince` (type: `string`):

Keep only reports ESPN timestamped on or after this date. Reports with an unreadable timestamp are kept, because unknown is not the same as old.

## `keywords` (type: `array`):

Keep a report only if one (Any) or all (All) of these appear in the player, team, position, status or the analyst comments. Example: ankle, hamstring.

## `keywordMatch` (type: `string`):

Any = at least one keyword. All = every keyword.

## `excludeKeywords` (type: `array`):

Drop a report if any of these appear in the same fields.

## `useProxy` (type: `boolean`):

Off by default: ESPN's public feed answered 200 from a plain Apify datacentre address when this Actor was built (2026-09-06). If it starts answering 403, 429 or 503, the Actor switches to a proxy session on its own and tries again.

## Actor input object example

```json
{
  "leagues": [
    "nfl",
    "nba",
    "mlb",
    "nhl"
  ],
  "maxInjuries": 200,
  "monitoringMode": false,
  "resetMonitoringState": false,
  "statuses": [],
  "teams": [],
  "players": [],
  "positions": [],
  "requireComment": false,
  "keywords": [],
  "keywordMatch": "any",
  "excludeKeywords": [],
  "useProxy": false
}
```

# Actor output Schema

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

One row per injury report: the league, season and season type; the team with its id, abbreviation and slug; the player with their ESPN id (read out of the player-card link, because ESPN does not put it in the record itself), display and short name, position and headshot; the status both as ESPN prints it (Out, Day-To-Day, Injured Reserve) and as ESPN's own code (INJURY\_STATUS\_OUT), plus its abbreviation and description; the timestamp both as an ISO instant and exactly as ESPN wrote it (ESPN omits the seconds); and ESPN's short and long analyst commentary. Every row also carries a reportKey that changes when the status, timestamp or comment changes, which is what monitoring mode compares. Leagues with no injuries, leagues with no ESPN feed, filters that matched nothing and feeds that could not be read come back as their own rows and are not charged.

# 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 = {
    "leagues": [
        "nfl",
        "nba",
        "mlb",
        "nhl"
    ],
    "changedSince": ""
};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/espn-injury-report-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 = {
    "leagues": [
        "nfl",
        "nba",
        "mlb",
        "nhl",
    ],
    "changedSince": "",
}

# Run the Actor and wait for it to finish
run = client.actor("neverempty/espn-injury-report-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 '{
  "leagues": [
    "nfl",
    "nba",
    "mlb",
    "nhl"
  ],
  "changedSince": ""
}' |
apify call neverempty/espn-injury-report-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,neverempty/espn-injury-report-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/hYXuanndA80cXRcEk/builds/r1JfDQoRocx6HShA0/openapi.json
