# Football Contract Expiry Radar - Transfermarkt (`datagrit/football-contract-expiry-radar`) Actor

Football players whose contracts end soon, per league and expiry window, with market value, trend, agent and extension options from Transfermarkt end-of-contract lists.

- **URL**: https://apify.com/datagrit/football-contract-expiry-radar.md
- **Developed by:** [datagrit](https://apify.com/datagrit) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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

### What does Football Contract Expiry Radar - Transfermarkt do?

Football Contract Expiry Radar lists the players whose contracts end soon, league by league, from the Transfermarkt end-of-contract lists. You choose leagues and an expiry window (for example the next six months, or a date range) and get one flat row per player: club, position, age, nationality, contract end date, days until expiry, market value with its latest trend, extension option, fee paid and agency. Export the data as JSON, CSV or Excel, call it through the Apify API, or plug it into n8n, Make and AI agents through MCP.

It is built for scouts, agents, transfer-market analysts, football-manager communities, betting and fantasy researchers who want the free-agent and renewal pipeline of a league without opening hundreds of player profiles.

### What can I use the expiring contracts list for?

- Build a free-agent shortlist for the summer window: goalkeepers under 25 with a market value above 2 million euros whose contract ends within 12 months.
- Track renewal risk of a squad: players with no extension option whose contract ends in under six months, sorted by market value.
- Feed a transfer-rumour or betting model with a weekly list of new expiring contracts per league (only-new mode delivers each player once).
- Find young talent in smaller leagues: maximum age 21, any of 37 leagues, sorted by market value.
- Compare agencies: the agency of every expiring player is a flat field you can group by.

### Example output

| playerName | clubName | position | age | contractEndDate | daysUntilExpiry | marketValueEur | marketValueTrend | extensionOptionType |
|---|---|---|---|---|---|---|---|---|
| Lisandro Martínez | Manchester United | Centre-Back | 28 | 2027-06-30 | 272 | 45000000 | up | unspecified |

```json
{
  "playerId": "480762",
  "playerName": "Lisandro Martínez",
  "position": "Centre-Back",
  "positionGroup": "Defender",
  "age": 28,
  "ageAtExpiry": 29,
  "nationality": "Argentina",
  "clubName": "Manchester United",
  "competitionCode": "GB1",
  "competitionName": "Premier League (England)",
  "contractEndDate": "2027-06-30",
  "daysUntilExpiry": 272,
  "monthsUntilExpiry": 8.9,
  "expiresWithin6Months": false,
  "contractOption": "Option for a further year",
  "extensionOptionType": "unspecified",
  "extensionYears": 1,
  "marketValueEur": 45000000,
  "previousMarketValueEur": 40000000,
  "marketValueTrend": "up",
  "feePaidEur": 57370000,
  "feeType": "fee",
  "agentName": "Score Futbol",
  "found": true
}
```

When nothing matches your window and filters, the dataset holds a single status row with `found: false`, and the run status says why.

### How much does it cost?

You pay per player row returned, with no separate charge for status rows or for players skipped as duplicates. The Maximum results field caps both the dataset and your bill, and the Actor stops at your spending limit. A league and calendar year takes about three to seven page requests, so a typical run for the five biggest leagues finishes in well under a minute. Platform compute for a run like that is a few cents at most.

### Input

- **Competitions**: Transfermarkt codes such as GB1 (Premier League), ES1 (LaLiga), IT1 (Serie A), L1 (Bundesliga), FR1 (Ligue 1) or ALL for the 37 leagues in the supported list. Other leagues go into **Other competition codes**.
- **Contract ends within (months)** or exact **from** and **until** dates.
- **Position groups, nationalities, minimum market value, minimum and maximum age, extension option** narrow the list.
- **Include end dates already passed this year** adds players whose date has passed but who are still listed.
- **Only players not delivered before** returns each player once per filter set.
- **Sort and cut by** expiry date or market value, then **Maximum results**.

### FAQ

#### Where does the data come from?

From the public Transfermarkt end-of-contract lists, the same pages you see in a browser without logging in. The Actor reads them politely, at most two requests at a time with a pause between requests, and retries with backoff.

#### How fresh is it?

As fresh as Transfermarkt. Contract dates and market values change when the community updates a profile; schedule the Actor daily or weekly and use only-new mode to see changes.

#### What does extensionOptionType mean?

It is derived from the contract option text: club, both, performance, unspecified or other, and none when the list shows no option. A club option means the contract may run longer than the end date shown.

#### Why is a player missing?

Only players listed with an end date in the chosen calendar years appear. Loans and players without a published end date are not on those lists, and a player who is dropped by your filters is counted in the run status, not in the dataset.

#### Is it legal to scrape this?

The Actor collects only publicly visible football data and does not log in or bypass any protection. You are responsible for how you use the data and for complying with the source's terms.

### Related Actors

See other data Actors from the same publisher on the Store profile.

# Changelog

This Actor's version history is a separate document: https://apify.com/datagrit/football-contract-expiry-radar/changelog.md

# Actor input Schema

## `competitions` (type: `array`):

Competition codes to read, for example GB1 (Premier League), ES1 (LaLiga), IT1 (Serie A), L1 (Bundesliga), FR1 (Ligue 1), NL1, PO1, TR1, SA1, GB2. Supported leagues: GB1 Premier League England; GB2 Championship England; ES1 LaLiga Spain; ES2 LaLiga2 Spain; IT1 Serie A Italy; IT2 Serie B Italy; L1 Bundesliga Germany; L2 2. Bundesliga Germany; FR1 Ligue 1 France; FR2 Ligue 2 France; NL1 Eredivisie Netherlands; PO1 Liga Portugal Portugal; TR1 Super Lig Turkiye; BE1 Jupiler Pro League Belgium; SC1 Scottish Premiership Scotland; RU1 Premier Liga Russia; UKR1 Premier Liga Ukraine; GR1 Super League 1 Greece; DK1 Superliga Denmark; A1 Bundesliga Austria; C1 Super League Switzerland; TS1 Chance Liga Czechia; PL1 Ekstraklasa Poland; RO1 SuperLiga Romania; KR1 HNL Croatia; SER1 Super liga Srbije Serbia; BU1 efbet Liga Bulgaria; UNG1 Nemzeti Bajnoksag Hungary; SLO1 Nike Liga Slovakia; ZYP1 Cyprus League Cyprus; SA1 Saudi Pro League Saudi Arabia; QSL Qatar Stars League Qatar; UAE1 UAE Pro League United Arab Emirates; EGY1 Egyptian Premier League Egypt; JAP1 J1 League Japan; AUS1 A-League Men Australia; ISR1 Ligat ha'Al Israel. Each league and calendar year in the expiry window costs about 3 to 7 page requests. Enter ALL to read every league in the list; with a long window this takes many minutes, so raise Maximum run time. For a league that is not in the list, put its Transfermarkt code in "Other competition codes".

## `customCompetitionCodes` (type: `array`):

Optional. Extra Transfermarkt competition codes, for example BRA1 for the Brazilian Serie A or MLS1 for Major League Soccer. The code is the last part of the competition page URL (transfermarkt.com/premier-league/startseite/wettbewerb/GB1 means GB1). Codes Transfermarkt does not recognise are skipped and listed in the run status.

## `expiringWithinMonths` (type: `integer`):

Expiry window counted from today: 6 lists players whose contract ends in the next six months, 12 within a year. Used for the end of the window when "Contract ends until" is empty.

## `expiresFrom` (type: `string`):

Optional. Earliest contract end date, YYYY-MM-DD. Replaces the start of the window (today). A date in the past returns players whose end date has already passed. When set in the future without "Contract ends until", the window runs the months value from that date.

## `expiresTo` (type: `string`):

Optional. Latest contract end date, YYYY-MM-DD. Replaces the end of the window.

## `includePastDates` (type: `boolean`):

Transfermarkt keeps players on the list after their end date until the club updates the profile. Turn on to include them from 1 January of this year (marked endDateInPast=true); off starts the window today.

## `positionGroups` (type: `array`):

Optional. Keep only these groups: Goalkeeper, Defender, Midfielder, Attacker (case-insensitive; any other value fails the run). Players whose detailed position does not map to a group are dropped when any group is chosen.

## `nationalities` (type: `array`):

Optional. English country names as Transfermarkt writes them, for example Spain or Brazil. A player passes when any of their listed nationalities matches (case-insensitive).

## `minMarketValueEur` (type: `integer`):

Skip players with a market value below this amount. Players without a listed market value are skipped as soon as a minimum above 0 is set. 0 keeps everyone.

## `minAge` (type: `integer`):

Age today in whole years. 0 means no lower limit.

## `maxAge` (type: `integer`):

Age today in whole years, for example 23 for young talents. 0 means no upper limit.

## `extensionOption` (type: `string`):

any keeps all players; withOption keeps players whose contract lists an extension option (a club can extend, so the contract may not really end); withoutOption keeps players with no option listed.

## `onlyNewSinceLastRun` (type: `boolean`):

Skip players delivered by an earlier run with the same competitions, window and filters (Maximum results, sort order and run time do not count as filters). A player comes back when the end date on Transfermarkt changes. Only players you actually received are remembered.

## `sortBy` (type: `string`):

expiryDate returns the nearest end dates first, marketValue the most valuable players first. Maximum results cuts the sorted list, so the order decides which players you keep.

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

Stop after this many players in total. Each player is one billed result.

## `maxRunSeconds` (type: `integer`):

Hard time budget for reading Transfermarkt (about 0.7 seconds per page request). When reached, the Actor keeps everything read so far and says how many lists it covered in the run status. 0 removes the limit.

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

Optional proxy. Leave disabled unless Transfermarkt blocks datacenter traffic from your run; residential proxy raises the platform cost of the run.

## Actor input object example

```json
{
  "competitions": [
    "GB1"
  ],
  "customCompetitionCodes": [],
  "expiringWithinMonths": 12,
  "expiresFrom": "",
  "expiresTo": "",
  "includePastDates": false,
  "positionGroups": [],
  "nationalities": [],
  "minMarketValueEur": 0,
  "minAge": 0,
  "maxAge": 0,
  "extensionOption": "any",
  "onlyNewSinceLastRun": false,
  "sortBy": "expiryDate",
  "maxItems": 20,
  "maxRunSeconds": 600,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

All extracted records as a dataset.

# 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 = {
    "competitions": [
        "GB1"
    ],
    "customCompetitionCodes": [],
    "expiringWithinMonths": 12,
    "expiresFrom": "",
    "expiresTo": "",
    "includePastDates": false,
    "positionGroups": [],
    "nationalities": [],
    "minMarketValueEur": 0,
    "minAge": 0,
    "maxAge": 0,
    "extensionOption": "any",
    "onlyNewSinceLastRun": false,
    "sortBy": "expiryDate",
    "maxItems": 20,
    "maxRunSeconds": 600,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("datagrit/football-contract-expiry-radar").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 = {
    "competitions": ["GB1"],
    "customCompetitionCodes": [],
    "expiringWithinMonths": 12,
    "expiresFrom": "",
    "expiresTo": "",
    "includePastDates": False,
    "positionGroups": [],
    "nationalities": [],
    "minMarketValueEur": 0,
    "minAge": 0,
    "maxAge": 0,
    "extensionOption": "any",
    "onlyNewSinceLastRun": False,
    "sortBy": "expiryDate",
    "maxItems": 20,
    "maxRunSeconds": 600,
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("datagrit/football-contract-expiry-radar").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 '{
  "competitions": [
    "GB1"
  ],
  "customCompetitionCodes": [],
  "expiringWithinMonths": 12,
  "expiresFrom": "",
  "expiresTo": "",
  "includePastDates": false,
  "positionGroups": [],
  "nationalities": [],
  "minMarketValueEur": 0,
  "minAge": 0,
  "maxAge": 0,
  "extensionOption": "any",
  "onlyNewSinceLastRun": false,
  "sortBy": "expiryDate",
  "maxItems": 20,
  "maxRunSeconds": 600,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call datagrit/football-contract-expiry-radar --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,datagrit/football-contract-expiry-radar"
        }
    }
}
```

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/8vehZVPc4zGqTW1N5/builds/OFuhPn3LlzSGC7c8Y/openapi.json
