# Chess Opponent Analyzer (`trovevault/chess-opponent-analyzer`) Actor

Analyzes a chess player's game history and opening repertoire from Lichess, Chess.com, or official tournaments. Extracts ECO statistics, opening trees, performance metrics, recent trends, and surprise detections. Export to PGN or JSON.

- **URL**: https://apify.com/trovevault/chess-opponent-analyzer.md
- **Developed by:** [Trove Vault](https://apify.com/trovevault) (community)
- **Categories:** Automation, AI, News
- **Stats:** 4 total users, 1 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.25 / 1,000 openings

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are a software tools running on the Apify platform, for all kinds of web data extraction and automation use cases.
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.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp.md).

If your project is in a different language, use 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

## Chess Opponent Analyzer

Fetch a chess player's full game history from Lichess and Chess.com, analyze their opening repertoire, and get an actionable preparation report before your next game.

---

### What Does the Chess Opponent Analyzer Do?

The Chess Opponent Analyzer fetches a player's game history from Lichess and Chess.com and produces a detailed opening repertoire report. You get win rates per opening, a full move-tree, and signals for recent preparation changes -- everything a coach or player needs the night before a tournament game.

Core capabilities:

- 📥 Fetches up to 1,500 games from Lichess (NDJSON API) and Chess.com (monthly archives API)
- ♟️ Builds a per-color opening tree (up to 30 plies deep) with win/draw/loss stats at every node
- 📊 Ranks top openings by ECO code with frequency and win rate
- 📈 Detects recent trends: openings the opponent started playing more or less in the last 30 days
- 🚨 Flags surprise openings: lines played recently but historically rare (< 3% of games)
- 🔁 Surfaces rare repeated lines (< 5% frequency, 3+ games) -- signature sidelines worth preparing
- ⏱️ Splits performance by time control: classical, rapid, blitz, bullet
- 🔗 Combines both platforms in a single merged analysis when platform is set to "all"

---

### What Data Does the Chess Opponent Analyzer Return?

| Opening analysis | Performance data | Metadata |
|---|---|---|
| ♟️ ECO code and opening name | 📊 Win / draw / loss counts | 🗓️ Date range analyzed |
| 🌳 Full opening tree (configurable depth) | 📈 Win rate, draw rate, loss rate | 🕐 Time of analysis |
| 🎯 First-move frequency (White) | 🕐 Performance by time control | 🎮 Source platform (Lichess / Chess.com) |
| 🔄 Black responses vs 1.e4 / 1.d4 / 1.c4 / 1.Nf3 | 📉 Recent 30-day trends | 🆔 Game IDs and URLs |
| 🚨 Surprise openings (new preparation signals) | 🔁 Rare repeated sidelines | 🌐 Game count per platform |

---

### Can I Use the Chess Opponent Analyzer to Study Magnus Carlsen, Hikaru Nakamura, or Any Other Player?

Yes. The Chess Opponent Analyzer works for any public Lichess or Chess.com profile. For Lichess, enter the username (for example, `DrNykterstein` for Magnus Carlsen or `nihalsarin` for Nihal Sarin). For Chess.com, enter the Chess.com username (for example, `MagnusCarlsen` or `Hikaru`). Set `platform: "all"` to merge both platforms into a single analysis.

---

### How Does the Chess Opponent Analyzer Work?

**The Process**

1. You provide a Lichess username, a Chess.com username, or both, plus optional filters (date range, time control, rated/casual, color, minimum opponent rating)
2. The actor fetches games from the selected platform(s): Lichess via NDJSON streaming, Chess.com via monthly archive API
3. Each game is normalized to a common format: result, ECO code, opening name, player color, opponent rating, and move list
4. Games from both platforms are merged into a single pool (when using `platform: "all"`)
5. The move list is parsed into SAN tokens (clock annotations and move numbers stripped)
6. An opening tree is built by traversing moves up to your chosen depth and counting wins/draws/losses at every node
7. Statistical layers are added: ECO rankings, first-move frequencies, Black response maps, trend analysis, surprise detection, and rare-line surfacing
8. The full analysis is pushed as a single JSON record to the Apify dataset

Think of it as running your own personal opening preparation team, except it processes 500 games in under 60 seconds.

---

### Why Use the Chess Opponent Analyzer?

| Feature | Manual research | Chess Opponent Analyzer |
|---|---|---|
| Time to analyze 500 games | Several hours | Under 60 seconds |
| Opening tree depth | Limited by patience | Up to 30 plies (configurable) |
| Surprise detection | Hard to spot manually | Automatic: flags openings with < 3% historical rate |
| Trend analysis | Requires date-based sorting | Built-in 30-day vs 60-day comparison |
| Cross-platform coverage | Separate sessions per site | Lichess + Chess.com merged in one run |
| Multi-time-control split | Separate sessions | Automatic breakdown per time control |
| Structured output for pipelines | Not possible | Full JSON via Apify dataset API |

---

### What Can You Do With Opening Analysis Data?

**Tournament preparation**

Import the opening tree JSON into your preparation workflow. The tree structure maps directly to standard repertoire formats -- you can see exactly what your opponent plays after 1.e4 e5 2.Nf3 Nc6 and how their win rate changes move by move. Filter to classical or rapid only when preparing for a classical event. Use `platform: "all"` to get the widest picture across both Lichess and Chess.com.

**Coaching and training**

Coaches can run the actor for every upcoming opponent of a student and store all results in a shared Apify dataset. Use the `datasetId` parameter to append all players into a single dataset for easy comparison. The structured JSON output integrates with any custom dashboard.

**Opening research**

The `recentTrends` and `surprises` arrays answer the key preparation question: "Has this player changed their opening approach lately?" An increasing trend in a line you haven't prepared is a concrete signal. A surprise opening with 3 recent games but near-zero historical rate means they may have specifically prepared it.

**Scheduling**

Run weekly in the month before a major tournament to catch preparation changes early. Run daily for rapid or blitz events where opponents may have evolved their openings recently. Use the Apify scheduler or connect via the API to trigger runs automatically.

---

### How to Use the Chess Opponent Analyzer

1. Go to the Chess Opponent Analyzer page on the Apify Store and click **Try for free**
2. In the **Input** tab, select your platform (Lichess, Chess.com, or All) and enter the corresponding username(s)
3. Set your filters: date range, time controls, rated/casual, minimum opponent rating
4. Optionally increase **Max Games** (up to 1,500) for deeper statistical analysis
5. Click **Start** and wait for the run to complete (usually 10--60 seconds)
6. Open the **Output** tab to see the full analysis record
7. Download as JSON, CSV, or use the Apify API to pull results into your own tools

**Recommendation**: start with `timeControls: ["rapid"]` and `maxGames: 200`. Rapid games reflect a player's real preparation better than blitz and contain more opening theory than bullet.

---

### How Much Does the Chess Opponent Analyzer Cost?

The Chess Opponent Analyzer uses the pay-per-event model. A typical run analyzing 500 rapid games costs less than $0.05 in compute units. The main cost driver is the number of games fetched -- more games means more parsing and analysis.

Cost optimization tips:

- Use `maxGames: 200-500` for most preparation needs -- diminishing returns above 500 for active players
- Set `dateFrom` to limit to the last 12--18 months -- older games may not reflect current preparation
- Set `minOpponentRating` (e.g. 2300+) to filter out low-quality games and focus the analysis
- Add a `lichessApiToken` to reduce Lichess rate-limit delays -- reduces runtime, not cost

---

### Input

The Chess Opponent Analyzer accepts the following input parameters:

| Parameter | Type | Default | Description |
|---|---|---|---|
| `lichessUsername` | string | -- | Lichess username to analyze (e.g. `DrNykterstein`) |
| `chesscomUsername` | string | -- | Chess.com username to analyze (e.g. `MagnusCarlsen`) |
| `platform` | string | `lichess` | Source platform: `lichess`, `chesscom`, or `all` |
| `dateFrom` | string | -- | Start date in ISO format: `2024-01-01` |
| `dateTo` | string | -- | End date in ISO format: `2024-12-31` |
| `timeControls` | array | `["all"]` | One or more of: `classical`, `rapid`, `blitz`, `bullet`, `all` |
| `rated` | string | `rated` | `rated`, `casual`, or `all` |
| `color` | string | `both` | `white`, `black`, or `both` |
| `minOpponentRating` | integer | `0` | Only include games vs opponents at or above this rating |
| `maxGames` | integer | `500` | Maximum games to fetch (up to 1,500) |
| `openingTreeDepth` | integer | `12` | Plies to include in opening tree (4--30) |
| `lichessApiToken` | string | -- | Optional Lichess token to increase rate limits |
| `datasetId` | string | -- | Append results to an existing Apify dataset |
| `runId` | string | -- | Associate results with a pipeline run |

**Example input -- Lichess tournament preparation:**

```json
{
    "lichessUsername": "nihalsarin",
    "platform": "lichess",
    "timeControls": ["rapid", "classical"],
    "dateFrom": "2024-01-01",
    "rated": "rated",
    "minOpponentRating": 2500,
    "maxGames": 500
}
````

**Example input -- Chess.com blitz analysis:**

```json
{
    "chesscomUsername": "Hikaru",
    "platform": "chesscom",
    "timeControls": ["blitz"],
    "maxGames": 500
}
```

**Example input -- cross-platform merged analysis:**

```json
{
    "lichessUsername": "DrNykterstein",
    "chesscomUsername": "MagnusCarlsen",
    "platform": "all",
    "timeControls": ["rapid"],
    "maxGames": 500
}
```

***

### Output

The Chess Opponent Analyzer produces a single JSON record per run pushed to the Apify dataset.

**Example output structure:**

```json
{
    "player": "nihalsarin",
    "gamesAnalyzed": 312,
    "dateRange": { "from": "2024-01-15", "to": "2024-12-20" },
    "sources": { "lichess": 312 },
    "summary": {
        "games": 312,
        "wins": 180,
        "draws": 100,
        "losses": 32,
        "winRate": 0.577,
        "drawRate": 0.321,
        "lossRate": 0.103
    },
    "white": {
        "games": 156,
        "summary": { "wins": 95, "draws": 50, "losses": 11, "winRate": 0.609 },
        "firstMoves": {
            "e4": { "games": 96, "wins": 62, "draws": 28, "losses": 6, "frequency": 0.615, "winRate": 0.646 }
        },
        "topEcos": [
            { "eco": "C65", "name": "Ruy Lopez Berlin Defense", "games": 42, "winRate": 0.69, "frequency": 0.269 }
        ],
        "openingTree": {
            "e4": {
                "games": 96,
                "wins": 62,
                "draws": 28,
                "losses": 6,
                "winRate": 0.646,
                "continuations": {
                    "e5": { "games": 48, "winRate": 0.625, "continuations": {} },
                    "c5": { "games": 32, "winRate": 0.688, "continuations": {} }
                }
            }
        }
    },
    "black": {
        "games": 156,
        "firstMoves": {
            "vs1e4": { "e5": { "games": 42, "frequency": 0.41, "winRate": 0.571 } },
            "vs1d4": { "Nf6": { "games": 38, "frequency": 0.37, "winRate": 0.605 } },
            "vs1c4": {},
            "vs1Nf3": {}
        }
    },
    "performanceByTimeControl": {
        "rapid": { "games": 200, "winRate": 0.60 },
        "blitz": { "games": 112, "winRate": 0.527 }
    },
    "recentTrends": [
        {
            "eco": "B12",
            "name": "Caro Kann Defense",
            "trend": "increasing",
            "last30Games": 8,
            "prev60Games": 3,
            "recentRate": 0.267,
            "prevRate": 0.05
        }
    ],
    "surprises": [
        {
            "eco": "A00",
            "name": "Larsen Opening",
            "firstSeenRecently": "2024-11-01",
            "recentGames": 3,
            "historicalGames": 1,
            "historicalRate": 0.008
        }
    ],
    "rareRepeated": [
        { "eco": "C10", "name": "French Defense", "games": 4, "frequency": 0.026 }
    ],
    "scrapedAt": "2024-12-21T14:30:00.000Z"
}
```

Results can be downloaded as JSON or CSV from the Output tab, or fetched programmatically via the Apify API.

***

### Are There Other Chess Tools on Apify Store?

- [Chess Ratings Aggregator](https://apify.com/trovevault/chess-ratings-aggregator) -- Aggregate FIDE, Chess.com, and Lichess ratings for the top 100+ players side-by-side with 12-month progression and peak ratings
- [Apify Store](https://apify.com/store) -- Browse hundreds of other scrapers and data tools

***

### Frequently Asked Questions

**Can I analyze any Lichess player with the Chess Opponent Analyzer?**

Yes. Any public Lichess profile is available without authentication. Simply enter the username. For players with a large game history, add a Lichess API token in the `lichessApiToken` field to avoid rate limiting when fetching large game sets.

**Does the Chess Opponent Analyzer work for Chess.com players?**

Yes. Set `platform: "chesscom"` and enter the player's Chess.com username in the `chesscomUsername` field. The actor fetches games from Chess.com's public monthly archives API. No API key is required. Examples: `MagnusCarlsen`, `Hikaru`, `FabianoCaruana`.

**Can I merge Lichess and Chess.com games into a single analysis?**

Yes. Set `platform: "all"` and provide both `lichessUsername` and `chesscomUsername`. The actor fetches from both platforms, normalizes all games to the same format, and produces a single merged analysis. This gives the most complete picture of a player's repertoire when they are active on both platforms.

**How many games can I analyze per run?**

Up to 1,500 games per run, set via `maxGames`. For most players 200--500 games is sufficient for statistically reliable opening frequencies. Use higher values for players with rare sidelines or when preparing for very specific lines.

**What is the opening tree depth setting?**

Opening tree depth is measured in plies (half-moves). 12 plies = 6 full moves, which covers most standard opening theory. Use 20--24 plies for deep line preparation (for example, studying a player's handling of a specific 10-move variation). Higher depth produces larger output but gives more precise preparation signals.

**How do I schedule regular analysis runs before a tournament?**

Use the Apify scheduler to run the actor automatically. A recommended schedule for tournament preparation is once per week starting 4 weeks before the event, then daily in the final week. The `recentTrends` and `surprises` arrays are the key fields to monitor -- they will change if the opponent is actively preparing new lines.

**Can I use the Chess Opponent Analyzer with the Apify API?**

Yes. Every Apify actor is accessible via REST API. Send a POST request to `https://api.apify.com/v2/acts/trovevault~chess-opponent-analyzer/runs` with your input JSON and API token. Use the `datasetId` parameter to accumulate results for multiple players into a single dataset for comparison.

**Can I use the Chess Opponent Analyzer through an MCP Server?**

Yes. Apify provides a Model Context Protocol (MCP) server at `mcp.apify.com` that exposes all store actors as tools. Connect any MCP-compatible client (Claude, Cursor, etc.) to run the Chess Opponent Analyzer directly from your AI assistant. This is useful for automated preparation workflows where you ask your AI to analyze several opponents in one session.

**Is this data analysis legal?**

Yes. The Chess Opponent Analyzer only processes publicly available game data. Lichess explicitly provides a free public API for this purpose -- all games are public by default and the terms of use permit automated access. Chess.com exposes public game archives at `api.chess.com/pub` without authentication for published games. No private data, account credentials, or paid content are accessed.

**What if the actor returns no results?**

The most common cause is filters that are too strict. If `gamesAnalyzed: 0` is returned, try: removing the `minOpponentRating` filter, expanding the date range, including more time controls, or using `rated: "all"`. The error record includes a `fix` field with specific recommendations.

***

### Your Feedback

Found a bug or have a feature request? Open an issue on the [actor's Issues tab](https://apify.com/trovevault/chess-opponent-analyzer/issues). Phase 3 (official tournament games via TWIC) is on the roadmap -- upvote or comment to help prioritize.

# Actor input Schema

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

Array of player objects for analyzing multiple opponents in a single run. Each object can have: lichessUsername, chesscomUsername, and platform (optional, overrides the global Platform setting). When this field is set, the individual lichessUsername and chesscomUsername fields are ignored. Each player produces one row per opening in the dataset. Example: \[{"lichessUsername": "nihalsarin", "platform": "lichess"}, {"chesscomUsername": "Hikaru", "platform": "chesscom"}]

## `lichessUsername` (type: `string`):

The Lichess username of the player to analyze. The actor fetches all available rated games for this player and builds a full opening repertoire analysis. Example: 'DrNykterstein' (Magnus Carlsen) or 'nihalsarin' (Nihal Sarin). Case-insensitive. Required when platform is 'lichess' or 'all'.

## `chesscomUsername` (type: `string`):

The Chess.com username of the player to analyze. The actor fetches games from the Chess.com public API. Example: 'MagnusCarlsen' or 'Hikaru'. Required when platform is 'chesscom' or 'all'. Chess.com usernames are case-insensitive.

## `platform` (type: `string`):

Which platform(s) to fetch games from. 'lichess' uses the free Lichess API (no token required). 'chesscom' uses the public Chess.com API. 'all' fetches from both and merges results. Recommendation: start with 'lichess' -- it has the best API and includes many official events.

## `dateFrom` (type: `string`):

Start of the date range to fetch games from, in ISO format (YYYY-MM-DD). Example: '2024-01-01'. Leave empty to fetch from the beginning of the player's history. Combine with dateTo to focus on a specific tournament preparation window, e.g. last 6 months.

## `dateTo` (type: `string`):

End of the date range to fetch games from, in ISO format (YYYY-MM-DD). Example: '2024-12-31'. Leave empty to fetch up to today. Useful for studying a player's form before a specific event.

## `timeControls` (type: `array`):

Which time controls to include. Select multiple to compare repertoire differences across speeds. Recommendation: use 'classical' or 'rapid' for serious tournament preparation -- blitz/bullet repertoires often differ significantly from classical play.

## `rated` (type: `string`):

Whether to include rated games, casual games, or both. Recommendation: use 'rated' -- rated games reflect a player's real preparation and effort. Casual games may include experiments or intentional losses.

## `color` (type: `string`):

Whether to analyze games where the target player had white, black, or both colors. Use 'both' for a full repertoire overview. Use 'white' or 'black' to study a specific color in depth -- e.g. to answer 'what does he play against 1.e4?'

## `minOpponentRating` (type: `integer`):

Only include games where the opponent's rating is at or above this value. Example: 2400 filters out games vs club players and focuses on elite-level games. Set to 0 to include all games. For professional preparation, 2300-2500 is a reasonable threshold.

## `maxGames` (type: `integer`):

Maximum number of games to fetch and analyze. Higher values give more statistically significant opening frequencies but take longer and cost more. Recommendation: 200-500 for most players. Use 1000+ only for players with very large game libraries or rare openings.

## `openingTreeDepth` (type: `integer`):

How many half-moves (plies) to include in the opening tree. 12 plies = 6 full moves, which covers most opening theory. Higher values reveal deeper preparation but produce larger output. Recommendation: 12 for general study, 20-24 for specific line preparation.

## `lichessApiToken` (type: `string`):

Optional Lichess personal API token. Without a token, Lichess allows ~20 requests/second. With a token, rate limits are higher and you can access private game data. Generate a token at lichess.org/account/oauth/token. Scopes needed: none for public games.

## `datasetId` (type: `string`):

ID of an existing Apify dataset to append results to. Useful for aggregating analysis from multiple players into a single dataset for comparison. Leave blank to use only the default run dataset.

## `runId` (type: `string`):

ID of an existing Apify actor run to associate results with. Used when chaining actors in multi-step pipelines. Leave blank for standalone runs.

## Actor input object example

```json
{
  "players": [
    {
      "lichessUsername": "nihalsarin",
      "platform": "lichess"
    },
    {
      "lichessUsername": "DrNykterstein",
      "platform": "lichess"
    }
  ],
  "lichessUsername": "DrNykterstein",
  "platform": "lichess",
  "timeControls": [
    "all"
  ],
  "rated": "rated",
  "color": "both",
  "minOpponentRating": 0,
  "maxGames": 500,
  "openingTreeDepth": 12
}
```

# Actor output Schema

## `dataset` (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 = {
    "players": [
        {
            "lichessUsername": "nihalsarin",
            "platform": "lichess"
        },
        {
            "lichessUsername": "DrNykterstein",
            "platform": "lichess"
        }
    ],
    "lichessUsername": "DrNykterstein",
    "dateFrom": "",
    "dateTo": ""
};

// Run the Actor and wait for it to finish
const run = await client.actor("trovevault/chess-opponent-analyzer").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 = {
    "players": [
        {
            "lichessUsername": "nihalsarin",
            "platform": "lichess",
        },
        {
            "lichessUsername": "DrNykterstein",
            "platform": "lichess",
        },
    ],
    "lichessUsername": "DrNykterstein",
    "dateFrom": "",
    "dateTo": "",
}

# Run the Actor and wait for it to finish
run = client.actor("trovevault/chess-opponent-analyzer").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "players": [
    {
      "lichessUsername": "nihalsarin",
      "platform": "lichess"
    },
    {
      "lichessUsername": "DrNykterstein",
      "platform": "lichess"
    }
  ],
  "lichessUsername": "DrNykterstein",
  "dateFrom": "",
  "dateTo": ""
}' |
apify call trovevault/chess-opponent-analyzer --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=trovevault/chess-opponent-analyzer",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Chess Opponent Analyzer",
        "description": "Analyzes a chess player's game history and opening repertoire from Lichess, Chess.com, or official tournaments. Extracts ECO statistics, opening trees, performance metrics, recent trends, and surprise detections. Export to PGN or JSON.",
        "version": "0.1",
        "x-build-id": "LRDHJEFAMMIudxwyv"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/trovevault~chess-opponent-analyzer/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-trovevault-chess-opponent-analyzer",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/trovevault~chess-opponent-analyzer/runs": {
            "post": {
                "operationId": "runs-sync-trovevault-chess-opponent-analyzer",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/trovevault~chess-opponent-analyzer/run-sync": {
            "post": {
                "operationId": "run-sync-trovevault-chess-opponent-analyzer",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "properties": {
                    "players": {
                        "title": "Players (multi-player mode)",
                        "type": "array",
                        "description": "Array of player objects for analyzing multiple opponents in a single run. Each object can have: lichessUsername, chesscomUsername, and platform (optional, overrides the global Platform setting). When this field is set, the individual lichessUsername and chesscomUsername fields are ignored. Each player produces one row per opening in the dataset. Example: [{\"lichessUsername\": \"nihalsarin\", \"platform\": \"lichess\"}, {\"chesscomUsername\": \"Hikaru\", \"platform\": \"chesscom\"}]"
                    },
                    "lichessUsername": {
                        "title": "Lichess Username",
                        "type": "string",
                        "description": "The Lichess username of the player to analyze. The actor fetches all available rated games for this player and builds a full opening repertoire analysis. Example: 'DrNykterstein' (Magnus Carlsen) or 'nihalsarin' (Nihal Sarin). Case-insensitive. Required when platform is 'lichess' or 'all'."
                    },
                    "chesscomUsername": {
                        "title": "Chess.com Username",
                        "type": "string",
                        "description": "The Chess.com username of the player to analyze. The actor fetches games from the Chess.com public API. Example: 'MagnusCarlsen' or 'Hikaru'. Required when platform is 'chesscom' or 'all'. Chess.com usernames are case-insensitive."
                    },
                    "platform": {
                        "title": "Platform",
                        "enum": [
                            "lichess",
                            "chesscom",
                            "all"
                        ],
                        "type": "string",
                        "description": "Which platform(s) to fetch games from. 'lichess' uses the free Lichess API (no token required). 'chesscom' uses the public Chess.com API. 'all' fetches from both and merges results. Recommendation: start with 'lichess' -- it has the best API and includes many official events.",
                        "default": "lichess"
                    },
                    "dateFrom": {
                        "title": "Date From",
                        "type": "string",
                        "description": "Start of the date range to fetch games from, in ISO format (YYYY-MM-DD). Example: '2024-01-01'. Leave empty to fetch from the beginning of the player's history. Combine with dateTo to focus on a specific tournament preparation window, e.g. last 6 months."
                    },
                    "dateTo": {
                        "title": "Date To",
                        "type": "string",
                        "description": "End of the date range to fetch games from, in ISO format (YYYY-MM-DD). Example: '2024-12-31'. Leave empty to fetch up to today. Useful for studying a player's form before a specific event."
                    },
                    "timeControls": {
                        "title": "Time Controls",
                        "type": "array",
                        "description": "Which time controls to include. Select multiple to compare repertoire differences across speeds. Recommendation: use 'classical' or 'rapid' for serious tournament preparation -- blitz/bullet repertoires often differ significantly from classical play.",
                        "items": {
                            "type": "string",
                            "enum": [
                                "all",
                                "classical",
                                "rapid",
                                "blitz",
                                "bullet"
                            ],
                            "enumTitles": [
                                "All time controls",
                                "Classical (60+ min)",
                                "Rapid (10-60 min)",
                                "Blitz (3-10 min)",
                                "Bullet (<3 min)"
                            ]
                        },
                        "default": [
                            "all"
                        ]
                    },
                    "rated": {
                        "title": "Game Type",
                        "enum": [
                            "rated",
                            "casual",
                            "all"
                        ],
                        "type": "string",
                        "description": "Whether to include rated games, casual games, or both. Recommendation: use 'rated' -- rated games reflect a player's real preparation and effort. Casual games may include experiments or intentional losses.",
                        "default": "rated"
                    },
                    "color": {
                        "title": "Color",
                        "enum": [
                            "both",
                            "white",
                            "black"
                        ],
                        "type": "string",
                        "description": "Whether to analyze games where the target player had white, black, or both colors. Use 'both' for a full repertoire overview. Use 'white' or 'black' to study a specific color in depth -- e.g. to answer 'what does he play against 1.e4?'",
                        "default": "both"
                    },
                    "minOpponentRating": {
                        "title": "Minimum Opponent Rating",
                        "minimum": 0,
                        "type": "integer",
                        "description": "Only include games where the opponent's rating is at or above this value. Example: 2400 filters out games vs club players and focuses on elite-level games. Set to 0 to include all games. For professional preparation, 2300-2500 is a reasonable threshold.",
                        "default": 0
                    },
                    "maxGames": {
                        "title": "Max Games",
                        "minimum": 1,
                        "maximum": 1500,
                        "type": "integer",
                        "description": "Maximum number of games to fetch and analyze. Higher values give more statistically significant opening frequencies but take longer and cost more. Recommendation: 200-500 for most players. Use 1000+ only for players with very large game libraries or rare openings.",
                        "default": 500
                    },
                    "openingTreeDepth": {
                        "title": "Opening Tree Depth (plies)",
                        "minimum": 4,
                        "maximum": 30,
                        "type": "integer",
                        "description": "How many half-moves (plies) to include in the opening tree. 12 plies = 6 full moves, which covers most opening theory. Higher values reveal deeper preparation but produce larger output. Recommendation: 12 for general study, 20-24 for specific line preparation.",
                        "default": 12
                    },
                    "lichessApiToken": {
                        "title": "Lichess API Token (optional)",
                        "type": "string",
                        "description": "Optional Lichess personal API token. Without a token, Lichess allows ~20 requests/second. With a token, rate limits are higher and you can access private game data. Generate a token at lichess.org/account/oauth/token. Scopes needed: none for public games."
                    },
                    "datasetId": {
                        "title": "Dataset ID (optional)",
                        "type": "string",
                        "description": "ID of an existing Apify dataset to append results to. Useful for aggregating analysis from multiple players into a single dataset for comparison. Leave blank to use only the default run dataset."
                    },
                    "runId": {
                        "title": "Run ID (optional)",
                        "type": "string",
                        "description": "ID of an existing Apify actor run to associate results with. Used when chaining actors in multi-step pipelines. Leave blank for standalone runs."
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
