# Skill Rating — OpenSkill, Glicko-2 & Elo from match results (`keith-apis/skill-rating`) Actor

Turn a match log into skill ratings and a leaderboard with OpenSkill (patent-free TrueSkill alternative), Glicko-2 or Elo, plus win probabilities. Singles, doubles, teams, free-for-alls and draws. $0.20 per 1,000 rated matches.

- **URL**: https://apify.com/keith-apis/skill-rating.md
- **Developed by:** [Lai Keith](https://apify.com/keith-apis) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.20 / 1,000 match rateds

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

## Skill Rating: OpenSkill, Glicko-2 and Elo ratings from match results

**Skill Rating is an Apify Actor that turns a match log (who played, who won, optional timestamps) into skill ratings and a leaderboard using OpenSkill, Glicko-2 or Elo, and predicts the win probability of any matchup.** It handles singles, doubles, teams, free-for-alls, draws and weighted matches, and you pay only for matches rated: $0.20 per 1,000.

### What does Skill Rating do?

You give it your match history as JSON. It replays the matches in order (or by timestamp) and returns:

- **One dataset row per player:** rank, rating, μ (skill), σ (uncertainty), games, wins, losses and draws, ready to export as CSV, JSON or Excel.
- **An OUTPUT record** with the full ratings. Pass its `players` object back next time to continue: rating in two runs gives exactly the same result as one run over the whole history.
- **Win probabilities:** list hypothetical matchups in `predict` and get each team's chance of winning plus a 0–1 match-quality score (1 = perfectly balanced). Not charged.
- **Optional per-match history:** each match's expected win probability before it was played and how much every rating moved.

| Algorithm | Best for |
|---|---|
| `openskill` (default) | Teams, doubles and free-for-alls. Bayesian μ/σ ratings (Weng-Lin), the patent-free TrueSkill alternative |
| `glicko2` | 1v1 with uncertainty: rating, deviation and volatility, as used in chess |
| `elo` | The classic single number with a K-factor; simplest to explain |

### When should I use it?

- **Game leaderboards and matchmaking:** rate players from match logs and pair them with balanced opponents.
- **Sports clubs, leagues and ladders:** table tennis, padel, chess, darts, pool, five-a-side, esports.
- **AI model and prompt evaluation:** treat each model as a player and each pairwise judgement as a match (Chatbot-Arena style), with σ showing which rankings are still uncertain.
- **A/B and preference tests:** rank options from head-to-head votes.

If you need ratings from your own code on every match, use the [Skill Rating API](https://ranking-api.fly.dev/) instead. It runs the same engine behind a REST endpoint.

### Input

```json
{
  "algorithm": "openskill",
  "matches": [
    { "teams": [["ana"], ["ben"]], "winner": 0, "timestamp": "2026-10-01T18:00:00Z" },
    { "teams": [["ana", "dan"], ["ben", "cat"]], "winner": 0, "timestamp": "2026-10-02T19:00:00Z" },
    { "teams": [["ana"], ["dan"]], "ranks": [0, 0], "timestamp": "2026-10-03T18:00:00Z" },
    { "teams": [["ben"], ["cat"]], "winner": 0, "weight": 0.5, "timestamp": "2026-10-03T18:45:00Z" }
  ],
  "predict": [[["ana"], ["cat"]]]
}
```

| Field | Required | Description |
|---|---|---|
| `matches` | yes | Your match history. Up to 5,000 per run |
| `matches[].teams` | yes | 2 to 8 teams of 1 to 8 player ids. Singles: `[["ana"], ["ben"]]` |
| `matches[].winner` | no | Index of the winning team (default 0) |
| `matches[].ranks` | no | Finishing position per team, lower is better. Equal ranks are a draw: `[0, 0]`. Use it for free-for-alls |
| `matches[].weight` | no | 0.1 to 3: how much the match counts (friendly 0.5, final 2) |
| `matches[].timestamp` | no | ISO 8601 or epoch ms. If every match has one, matches are rated in time order; otherwise in list order |
| `algorithm` | no | `openskill` (default), `glicko2` or `elo` |
| `players` | no | The `players` object from a previous run's OUTPUT, to continue where you left off |
| `predict` | no | Matchups to predict from the final ratings, each a list of teams |
| `params` | no | Algorithm parameters, e.g. `{"k": 24}` for Elo |
| `includeHistory` | no | Add per-match expected win probability and rating changes to OUTPUT |

**Limits per run** are the same as the API's per request: 5,000 matches, 2,000 players and 2 MB of JSON input. For a longer history, split it over several runs and pass `players` from each run's OUTPUT into the next.

### Output

One dataset row per player (this is the real output for the Actor's example input):

```json
{ "rank": 1, "player": "ana", "rating": 8.0511, "mu": 30.5018, "sigma": 7.4836, "games": 4, "wins": 3, "losses": 0, "draws": 1, "algorithm": "openskill" }
```

`rating` is what to show users. For OpenSkill it is the conservative μ − 3σ, so new players start near 0 and climb as the Actor becomes sure of them; for Glicko-2 and Elo it is the rating itself (new players start at 1500).

The OUTPUT record holds `players` (μ, σ, games, display per player), the `leaderboard`, `predictions` such as `{ "teams": [["ana"], ["cat"]], "win_probability": [0.7329, 0.2671], "quality": 0.2907 }`, and `history` when requested.

### How much does it cost?

It's **pay per event: $0.20 per 1,000 rated matches** ($0.0002 per match), plus a little Apify platform usage. Predictions are free. Invalid input fails before anything is charged, and if you set a maximum spend on the run, the Actor rates only as many matches as the limit covers.

### FAQ

**How do I calculate Elo or Glicko-2 ratings from a spreadsheet of match results?**
Turn each row into `{"teams": [["player1"], ["player2"]], "winner": 0}` (or `"ranks": [0, 0]` for a draw), paste the list into `matches`, pick the algorithm and run. Export the dataset as CSV or Excel.

**How do I rate doubles or team games?**
Use OpenSkill (the default) and list each team's players: `[["ana", "ben"], ["cat", "dan"]]`. Each player's rating moves according to their own uncertainty.

**What is a patent-free alternative to TrueSkill?**
OpenSkill. It gives the same kind of μ/σ team ratings, while Microsoft permits the TrueSkill brand only for Xbox Live games and non-commercial projects.

**Can I add new matches later without re-rating everything?**
Yes. Pass the `players` object from the last run's OUTPUT along with only the new matches. The result is identical to re-rating the full history.

**Is my data stored?**
Results stay in your own Apify storage under your account's retention settings. Matches are rated inside the run and are not sent to any other service.

### Related

- [Skill Rating API](https://ranking-api.fly.dev/): the same engine as a REST API on RapidAPI, from free for 200 requests a month; each request rates up to 5,000 matches.
- [Free Elo rating calculator](https://ranking-api.fly.dev/elo-calculator): win probability and new ratings for a single game, in your browser.
- [keith-apis-mcp](https://github.com/Keithhk/keith-apis-mcp): an MCP server that gives AI assistants rating and win-probability tools.

# Actor input Schema

## `matches` (type: `array`):

Your match history. Each match: "teams" (2–8 teams of 1–8 player ids; singles = one id per team) and who won: "winner" (team index, default 0) or "ranks" (finishing position per team; equal ranks = draw). Optional: "weight" 0.1–3 and "timestamp" (ISO 8601 or epoch ms; if every match has one they are rated in time order, otherwise in list order). Charged per rated match.

## `algorithm` (type: `string`):

OpenSkill is the patent-free TrueSkill alternative and handles teams natively. Glicko-2 adds uncertainty for 1v1. Elo is one number with a K-factor.

## `players` (type: `object`):

The `players` object from a previous run's OUTPUT record, to continue where you left off. The result is identical to rating the whole history in one run.

## `predict` (type: `array`):

Hypothetical matches, each a list of teams, e.g. \[\["ana"],\["cat"]]. Returns each team's win probability and a 0–1 match quality from the final ratings, in the OUTPUT record. Not charged.

## `params` (type: `object`):

Override defaults, e.g. {"k": 24} for Elo or {"tau": 0.5} for Glicko-2. The ratings API's GET /algorithms lists every key.

## `includeHistory` (type: `boolean`):

Add each match's expected win probability and rating changes to the OUTPUT record.

## Actor input object example

```json
{
  "matches": [
    {
      "teams": [
        [
          "ana"
        ],
        [
          "ben"
        ]
      ],
      "winner": 0,
      "timestamp": "2026-10-01T18:00:00Z"
    },
    {
      "teams": [
        [
          "cat"
        ],
        [
          "dan"
        ]
      ],
      "winner": 1,
      "timestamp": "2026-10-01T18:30:00Z"
    },
    {
      "teams": [
        [
          "ana",
          "dan"
        ],
        [
          "ben",
          "cat"
        ]
      ],
      "winner": 0,
      "timestamp": "2026-10-02T19:00:00Z"
    },
    {
      "teams": [
        [
          "ana"
        ],
        [
          "dan"
        ]
      ],
      "ranks": [
        0,
        0
      ],
      "timestamp": "2026-10-03T18:00:00Z"
    },
    {
      "teams": [
        [
          "ben"
        ],
        [
          "cat"
        ]
      ],
      "winner": 0,
      "weight": 0.5,
      "timestamp": "2026-10-03T18:45:00Z"
    },
    {
      "teams": [
        [
          "ana"
        ],
        [
          "cat"
        ]
      ],
      "winner": 0,
      "timestamp": "2026-10-04T18:00:00Z"
    }
  ],
  "algorithm": "openskill",
  "predict": [
    [
      [
        "ana"
      ],
      [
        "cat"
      ]
    ],
    [
      [
        "ana",
        "ben"
      ],
      [
        "cat",
        "dan"
      ]
    ]
  ],
  "includeHistory": false
}
```

# Actor output Schema

## `ratings` (type: `string`):

No description

## `output` (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 = {
    "matches": [
        {
            "teams": [
                [
                    "ana"
                ],
                [
                    "ben"
                ]
            ],
            "winner": 0,
            "timestamp": "2026-10-01T18:00:00Z"
        },
        {
            "teams": [
                [
                    "cat"
                ],
                [
                    "dan"
                ]
            ],
            "winner": 1,
            "timestamp": "2026-10-01T18:30:00Z"
        },
        {
            "teams": [
                [
                    "ana",
                    "dan"
                ],
                [
                    "ben",
                    "cat"
                ]
            ],
            "winner": 0,
            "timestamp": "2026-10-02T19:00:00Z"
        },
        {
            "teams": [
                [
                    "ana"
                ],
                [
                    "dan"
                ]
            ],
            "ranks": [
                0,
                0
            ],
            "timestamp": "2026-10-03T18:00:00Z"
        },
        {
            "teams": [
                [
                    "ben"
                ],
                [
                    "cat"
                ]
            ],
            "winner": 0,
            "weight": 0.5,
            "timestamp": "2026-10-03T18:45:00Z"
        },
        {
            "teams": [
                [
                    "ana"
                ],
                [
                    "cat"
                ]
            ],
            "winner": 0,
            "timestamp": "2026-10-04T18:00:00Z"
        }
    ],
    "algorithm": "openskill",
    "predict": [
        [
            [
                "ana"
            ],
            [
                "cat"
            ]
        ],
        [
            [
                "ana",
                "ben"
            ],
            [
                "cat",
                "dan"
            ]
        ]
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("keith-apis/skill-rating").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 = {
    "matches": [
        {
            "teams": [
                ["ana"],
                ["ben"],
            ],
            "winner": 0,
            "timestamp": "2026-10-01T18:00:00Z",
        },
        {
            "teams": [
                ["cat"],
                ["dan"],
            ],
            "winner": 1,
            "timestamp": "2026-10-01T18:30:00Z",
        },
        {
            "teams": [
                [
                    "ana",
                    "dan",
                ],
                [
                    "ben",
                    "cat",
                ],
            ],
            "winner": 0,
            "timestamp": "2026-10-02T19:00:00Z",
        },
        {
            "teams": [
                ["ana"],
                ["dan"],
            ],
            "ranks": [
                0,
                0,
            ],
            "timestamp": "2026-10-03T18:00:00Z",
        },
        {
            "teams": [
                ["ben"],
                ["cat"],
            ],
            "winner": 0,
            "weight": 0.5,
            "timestamp": "2026-10-03T18:45:00Z",
        },
        {
            "teams": [
                ["ana"],
                ["cat"],
            ],
            "winner": 0,
            "timestamp": "2026-10-04T18:00:00Z",
        },
    ],
    "algorithm": "openskill",
    "predict": [
        [
            ["ana"],
            ["cat"],
        ],
        [
            [
                "ana",
                "ben",
            ],
            [
                "cat",
                "dan",
            ],
        ],
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("keith-apis/skill-rating").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 '{
  "matches": [
    {
      "teams": [
        [
          "ana"
        ],
        [
          "ben"
        ]
      ],
      "winner": 0,
      "timestamp": "2026-10-01T18:00:00Z"
    },
    {
      "teams": [
        [
          "cat"
        ],
        [
          "dan"
        ]
      ],
      "winner": 1,
      "timestamp": "2026-10-01T18:30:00Z"
    },
    {
      "teams": [
        [
          "ana",
          "dan"
        ],
        [
          "ben",
          "cat"
        ]
      ],
      "winner": 0,
      "timestamp": "2026-10-02T19:00:00Z"
    },
    {
      "teams": [
        [
          "ana"
        ],
        [
          "dan"
        ]
      ],
      "ranks": [
        0,
        0
      ],
      "timestamp": "2026-10-03T18:00:00Z"
    },
    {
      "teams": [
        [
          "ben"
        ],
        [
          "cat"
        ]
      ],
      "winner": 0,
      "weight": 0.5,
      "timestamp": "2026-10-03T18:45:00Z"
    },
    {
      "teams": [
        [
          "ana"
        ],
        [
          "cat"
        ]
      ],
      "winner": 0,
      "timestamp": "2026-10-04T18:00:00Z"
    }
  ],
  "algorithm": "openskill",
  "predict": [
    [
      [
        "ana"
      ],
      [
        "cat"
      ]
    ],
    [
      [
        "ana",
        "ben"
      ],
      [
        "cat",
        "dan"
      ]
    ]
  ]
}' |
apify call keith-apis/skill-rating --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,keith-apis/skill-rating"
        }
    }
}
```

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/cNXCnv4tw6BuBW8RB/builds/jHVNvvwII3vLa2O8C/openapi.json
