# Transfermarkt Player Market Values & Latest Transfers (`superslowsloth/transfermarkt-market-values`) Actor

Transfermarkt player market values for any competition (Premier League, LaLiga, Bundesliga, Serie A, Ligue 1 and more) or the latest transfers feed. One flat row per player: name, club, position, age, nationality, market value in EUR; for transfers the clubs left and joined, fee and date.

- **URL**: https://apify.com/superslowsloth/transfermarkt-market-values.md
- **Developed by:** [Superslow Sloth](https://apify.com/superslowsloth) (community)
- **Categories:** Sports, Automation
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.70 / 1,000 player scrapeds

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?

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

## Transfermarkt Player Market Values & Latest Transfers

Read [Transfermarkt](https://www.transfermarkt.com)'s football **market values**
for any competition (Premier League, LaLiga, Bundesliga, Serie A, Ligue 1 and
every other league it ranks), or its **latest transfers** feed, and get one flat
row per player: name, profile link, club, position, age, nationality, the market
value as a number in euro **and** as Transfermarkt writes it, and for transfers
the club left, the club joined, the transfer date and the fee. Run it on a
schedule and diff on `player_id` to watch valuations move or new transfers land.

No account, no API key, no login, no browser. One plain page request returns 25
players.

### Input

| Field | Notes |
|---|---|
| `mode` | `market-values` (default) reads each competition's top market-value table. `latest-transfers` reads the latest-transfers feed in its detailed view. |
| `competitions` | Competition codes, one per line, for `market-values`: `GB1` Premier League, `ES1` LaLiga, `L1` Bundesliga, `IT1` Serie A, `FR1` Ligue 1, and any other Transfermarkt code (the last part of a competition page's address, `.../wettbewerb/GB1`). Ignored by `latest-transfers`. |
| `maxItems` | Players to return **per competition** (or for the feed). Default and maximum 100. 25 per page. |
| `proxyConfiguration` | Apify proxy by default. |

### Output

One row per player. Both modes return the same columns, so a dataset loads into
one table; the columns that belong to the other mode are `null`.

| Field | Notes |
|---|---|
| `mode`, `player_id`, `name`, `url`, `image_url` | Transfermarkt's numeric player id (the number after `/spieler/`; stable, diff on it), the name, the absolute profile link and the portrait. |
| `position`, `age`, `nationality`, `nationalities` | As shown. `nationality` is the first flag (Transfermarkt lists the primary one first); `nationalities` is every flag. |
| `market_value_eur` | The market value as a whole number of euro: `€120.00m` is `120000000`, `€500k` is `500000`. **Null when Transfermarkt shows `-` or anything that is not a euro amount**, never 0. |
| `market_value_raw` | The same cell as Transfermarkt writes it (`€120.00m`, `€500k`, `-`). |
| `competition_code`, `competition`, `season`, `rank` | Market-values mode: the code asked for, the competition name and season from the page title (`Premier League`, `26/27`), and the position in the market-value ranking (1 = highest; players on the same value share a rank, as Transfermarkt shows it: 1, 2, 2, 2, 2, 6). |
| `club`, `club_id`, `club_url` | Market-values mode: the player's club in the table. |
| `transfer_id`, `transfer_date` | Transfers mode: Transfermarkt's id for the move (with `player_id` it identifies the row; a player can move twice) and the date as `YYYY-MM-DD`. The date **can be in the future**: the feed lists agreed moves that take effect later. |
| `from_club`, `from_club_id`, `from_club_url`, `from_league`, `from_league_country`, `from_league_code` | Transfers mode: the club left and its league. A destination such as `Retired` comes as a name with no id, link or league (all null). |
| `to_club`, `to_club_id`, `to_club_url`, `to_league`, `to_league_country`, `to_league_code` | Transfers mode: the club joined, same shape. |
| `fee_eur`, `fee_raw`, `fee_kind` | Transfers mode: the fee. `fee_raw` is the cell as shown. `fee_kind` is `fee` (an amount; `fee_eur` is set), `free` (free transfer), `loan` (loan transfer), `unknown` (Transfermarkt shows `?`), `other` (text this actor does not know; read `fee_raw`), or null (`-`). **`fee_eur` is null for everything but an amount**: a free transfer has no fee to measure, and it is not written as 0. |

#### Honest nulls

A field Transfermarkt did not send is `null`, never `0` or `""`. `market_value_eur`
is null for a player shown with `-`; the page's own string stays in
`market_value_raw`. A row that is not a player row (no profile link) is not
returned, and the log says how many were dropped.

### Pricing

Pay per event:

- **$0.002** per run (`actor-start`), charged only after your input is
  validated, so a run that fails on bad input costs nothing.
- **$0.00035** per player row written (`player-scraped`), i.e. $0.35 per 1,000
  players, in both modes.

For comparison, the other Transfermarkt scrapers on the Store charged between
$0.0005 and $0.0023 per player when this actor was priced (2026-10-13).

### How it fetches

Two kinds of plain HTTP request, no browser, through a Chrome TLS fingerprint
(`curl_cffi`):

1. Market values: `https://www.transfermarkt.com/x/marktwerte/wettbewerb/<CODE>`
   and `.../page/<n>`. The rows are read from the page's `table.items`.
2. Latest transfers: `https://www.transfermarkt.com/transfers/neuestetransfers/statistik`
   in the detailed view (`plus=1`) with the market-value floor lowered to 0
   (`minMarktwert=0`), `&page=<n>` for later pages.

Things worth knowing, all measured on 2026-10-13:

- **A competition has 4 pages of 25: you get the top 100 by market value.** The
  Premier League, LaLiga, Bundesliga, Serie A and Ligue 1 each end at page 4.
  That is a limit of Transfermarkt's table, not of this actor. Players outside
  the top 100 of a competition are not reachable from this table.
- **Pages past the end are not errors.** Transfermarkt answers page 5 or 99 with
  HTTP 200, a title that says "Page 5" and the rows of page 4. The actor reads
  the last page from the page's pagination and never asks past it, and a repeated
  page is detected rather than returned twice.
- **The transfers feed is short: about 70 rows.** At the time of testing the
  detailed feed had 3 pages (25 + 25 + 21). Pages 4 to 1000 repeat page 3.
- **The feed's plain address ignores `?page=`.** `.../neuestetransfers/statistik?page=2`
  returns the same 20 rows as page 1 (HTTP 200, titled "Page 2"), with no market
  value or date column. The paging that works is the detailed view with the
  market-value floor at 0, which is what the actor requests; it therefore returns
  every transfer in the feed rather than only those of players valued at
  EUR 500,000 or more.
- **Bot checks.** From a datacenter address Transfermarkt answered about two
  requests in five with an AWS WAF challenge (HTTP 202, `window.gokuProps`),
  interleaved with normal pages and constant over 45 minutes. The actor treats a
  202, 403, 429, 5xx, or a 200 without the table as a refusal of that exit
  address and retries from a fresh one. An unknown competition code is a
  different case (Transfermarkt redirects to its competitions index): it fails
  that code only, with no retry and no charge.
- **Not offered.** Filters (season, position, club, age) and player profile
  pages (career history, contract end) are not read.

# Actor input Schema

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

'Market values' reads each competition's top market-value table (25 players a page, the top 100 of the competition). 'Latest transfers' reads Transfermarkt's latest-transfers feed in its detailed view (player, club left, club joined, transfer date, market value, fee), newest first; the competitions below are not used.

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

Transfermarkt competition codes, one per line, read in turn in 'Market values' mode: GB1 (Premier League), ES1 (LaLiga), L1 (Bundesliga), IT1 (Serie A), FR1 (Ligue 1), NL1, PO1, TR1, BE1 and so on. The code is the last part of a competition page's address, e.g. transfermarkt.com/premier-league/startseite/wettbewerb/GB1. A code Transfermarkt does not know fails that competition only, at no charge.

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

Return at most this many players for each competition, or for the transfers feed. Transfermarkt serves 25 rows a page and 4 pages of each market-value table (the top 100), and about 3 pages of the latest-transfers feed (about 70 transfers at the time of testing), so 100 is the most any one competition can return. A player is returned and charged once per run.

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

Apify proxy (automatic datacenter exits) is the default. About two requests in five are answered with a bot-check from a datacenter address; the actor retries each from a fresh address, which is what the proxy is for.

## Actor input object example

```json
{
  "mode": "market-values",
  "competitions": [
    "GB1"
  ],
  "maxItems": 100,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `items` (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 = {
    "competitions": [
        "GB1"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("superslowsloth/transfermarkt-market-values").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"],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("superslowsloth/transfermarkt-market-values").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"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call superslowsloth/transfermarkt-market-values --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,superslowsloth/transfermarkt-market-values"
        }
    }
}
```

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/gVvy6OGcLJjbQaJl4/builds/JneqYZF8seu27CbS0/openapi.json
