# Darts Player Form: Averages, Checkout % & 180s (`thatmike1/darts-player-form`) Actor

Darts player form for any date window from Darts Orakel: weighted 3-dart average, checkout percentage and 180s with per-match evidence and coverage.

- **URL**: https://apify.com/thatmike1/darts-player-form.md
- **Developed by:** [Michal Pšenčík](https://apify.com/thatmike1) (community)
- **Categories:** Sports, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.00005 / actor start

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

## Darts Player Form: Averages, Checkout % & 180s

Check a darts player's form over any date window from the public statistics on [Darts Orakel](https://dartsorakel.com).
For each player ID you give, the actor reads the player's match list three times (averages, 180s and checkout
percentage), checks that the three lists describe exactly the same matches, and returns **one row per player and
window**: the weighted three-dart average, checkout percentage, 180 count, wins and losses, plus every match as
nested evidence and an explicit statement of what the source covered.

It is built for people who refresh a fixed watchlist before updating their own models or notes. A quiet window is
still a useful, validated answer (`no_matches`), and is kept clearly apart from a failed fetch.

### Input

```json
{
    "playerIds": ["2367"],
    "dateFrom": "2026-01-01",
    "dateTo": "2026-10-16",
    "maxRows": 20,
    "watchName": null,
    "requestIntervalMs": 2100
}
```

- **playerIds**: 1 to 20 numeric Darts Orakel player IDs, from a public profile URL such as
  `dartsorakel.com/player/details/2367/maikel-verberk`. Duplicates are checked once. Names, URLs and other text are
  rejected; the actor does not search for players.
- **dateFrom / dateTo**: inclusive source calendar dates (`YYYY-MM-DD`), at most 366 days, not after today's UTC date.
  These are the source's own match dates, not UTC instants. Compute rolling windows in your scheduler.
- **maxRows**: stop after this many delivered checks (1 to 20).
- **watchName** (optional): compare each check with the last check of the same player and window under this name.
  Rows then say `new`, `unchanged` or `changed`. Leave it empty for no stored state (`untracked`).
- **requestIntervalMs**: pause after each source response, 2,100 to 60,000 ms. One request at a time.

### Output

One row per delivered check. Main fields:

| Field                                              | Meaning                                                                                                     |
| -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `playerId`, `playerName`                           | The requested ID and the player name as the source publishes it (`null` when no record was returned).       |
| `checkStatus`                                      | `form_available`, or `no_matches` for a validated window with no eligible match.                            |
| `emptyReason`                                      | `empty_source_window` (the source returned no records) or `only_byes`; `null` otherwise.                    |
| `matchCount`, `wins`, `losses`                     | Completed non-bye matches in the window.                                                                    |
| `threeDartAverage`                                 | `3 × total points ÷ total darts` over the window, six decimals. `null` if any match lacks the figure.       |
| `checkoutPercent`                                  | `100 × total successful doubles ÷ total attempts`, six decimals. `null` if any match lacks the figure.      |
| `count180`                                         | Sum of reported 180s. `null` if any match lacks the figure.                                                 |
| `statTotals`                                       | The exact integer totals behind those rates, including the known part of a partial metric.                  |
| `coverage`, `missingStatFlags`                     | How many matches carried each statistic, byes excluded, and which metrics were partial.                     |
| `matches`                                          | Every included match: date, event, opponent, result, scores, per-match figures and the source's raw parts.  |
| `checkEvidence`                                    | Per view: counts the source reported, rows returned, a SHA-256 of the raw body and the time it was checked. |
| `contentHash`, `changeType`, `previousContentHash` | A hash of the check's content and, with a watch name, how it compares to the previous check.                |

Example (shortened, from a real run of the default input):

```json
{
    "playerId": "2367",
    "playerName": "Maikel Verberk",
    "checkStatus": "form_available",
    "matchCount": 84,
    "wins": 55,
    "losses": 29,
    "threeDartAverage": 87.109247,
    "checkoutPercent": 36.219641,
    "count180": 155,
    "statTotals": {
        "averageNumerator": 273494,
        "averageDenominator": 9419,
        "checkoutNumerator": 343,
        "checkoutDenominator": 947,
        "count180KnownTotal": 155
    },
    "missingStatFlags": ["unknown_score_unit"],
    "changeType": "untracked"
}
```

Use JSON for the full nested row. For a spreadsheet, export CSV/Excel with the platform's `fields` option on the
scalar columns (for example `playerId,playerName,checkStatus,matchCount,wins,losses,threeDartAverage,checkoutPercent,count180,changeType,fetchedAt`).
Exporting nested matches as extra lines does not create extra billed checks.

A free `SUMMARY` record in the run's key-value store lists every player's outcome, every error with its code, the
number of source requests and bytes, and how many rows were stored and charged.

#### What the figures mean, and what they do not

- **Completeness is the source's, not the real world's.** A check covers exactly what Darts Orakel returned for the
  window; all three lists must report complete, matching match sets or nothing is delivered.
  `realWorldCompletenessGuaranteed` is always `false`.
- **Points/darts and doubles/attempts are inferred** from the site's labels and from the fact that every sampled
  per-match figure equals `3 × stat1 ÷ stat2` (average) or `100 × stat1 ÷ stat2` (checkout). The actor re-checks
  that agreement on every match and rejects a view that disagrees.
- **Scores have no unit.** A score such as 6-5 may be legs or sets depending on the format; `scoreUnit` is always
  `unknown`.
- **Missing figures are never zeros.** A match without a figure lowers coverage; reported zeros stay zero.
- **An empty window does not prove anything about the player.** It does not show that the ID exists, that the player
  did not play, or that the source is complete (`playerExistence` is `unknown`).
- Byes are excluded from form and counted in `coverage.excludedByeCount`.
- No betting advice, odds, predictions or premium statistics.

### Pricing

Pay per event:

- **$0.00005 per run start**, charged even when the run ends in an error.
- **$0.003 per delivered check** (one dataset row), including validated empty and unchanged checks.

Errors, skipped players and the SUMMARY are never charged as checks. Ten players in one run cost $0.03005; twenty
quiet (empty) windows cost $0.06005. A spending limit below $0.00305 cannot cover one check, so the run stops before
contacting the source.

### Limits

- 20 players per run, three requests per player plus one `robots.txt` request, at most 122 requests in total.
- One request at a time with at least 2.1 s after each response, 20 s per request, one retry for timeouts, rate
  limits and server errors. Responses over 4 MiB, more than 16 MiB per run, or more than 1,000 matches per view stop
  the run.
- Work stops 810 s after the run starts; the remaining players are reported as not attempted. At slow pacing (for
  example 60 s) only a few players finish.
- Rows up to 1 MiB each and 4 MiB per run.
- Storage traffic (rows, read-backs, SUMMARY and watch state, counted as sent plus 1 KiB per call) is capped at
  16 MiB per run. The run stops before passing it and always keeps room for the final SUMMARY.
- A blocked or challenged request (HTTP 401 or 403, a login page or redirect, bot check) stops the whole run: the actor never retries with
  other addresses, headers, browsers or proxies.
- Runs at 256 MB; a run started with more memory is rejected before any request.
- Watch state keeps only hashes and dates: at most 500 player-windows and 128 KiB per watch name, each kept for 30 days
  after its last check, then treated as new. Do not run two runs with the same watch name at the same time.
  A run that cannot release its watch name or save its final SUMMARY ends as failed, even after delivering rows.

### Source and your responsibilities

Data comes from the public, login-free pages and JSON of [dartsorakel.com](https://dartsorakel.com). Its
[terms](https://dartsorakel.com/terms-and-conditions) reserve rights and restrict redistribution; this actor is not
affiliated with or endorsed by Darts Orakel and is not a licence to reuse its data. Rows contain player names and
sporting results as published. You are the controller of any personal data you collect and are responsible for
your use of it. Accuracy for modelling or betting is not guaranteed.

# Actor input Schema

## `playerIds` (type: `array`):

1 to 20 numeric Darts Orakel player IDs, taken from a public profile URL (for example 2367 in dartsorakel.com/player/details/2367/maikel-verberk). Duplicates are checked once. Names and URLs are not accepted.

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

First source calendar date, YYYY-MM-DD. Dates are the source's own match dates, not UTC instants.

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

Last source calendar date, YYYY-MM-DD, not after today's UTC date. The window may span at most 366 calendar days.

## `maxRows` (type: `integer`):

Stop after this many delivered checks (each is billed, including empty and unchanged ones). The run stops before fetching the next player once reached.

## `watchName` (type: `string`):

Set a name to compare each check with the previous run of the same player and window: rows then say new, unchanged or changed. Only content hashes are kept, for 30 days after the last check. Leave empty for no state (rows say untracked). Letters, digits, \_ and -, up to 80 characters. Do not run two runs with the same watch name at once.

## `requestIntervalMs` (type: `integer`):

Wait after each source response before the next request; one request at a time. Slower pacing uses the same fixed run time, so fewer players may finish.

## Actor input object example

```json
{
  "playerIds": [
    "2367"
  ],
  "dateFrom": "2026-01-01",
  "dateTo": "2026-10-16",
  "maxRows": 20,
  "requestIntervalMs": 2100
}
```

# Actor output Schema

## `checks` (type: `string`):

No description

## `summary` (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 = {
    "playerIds": [
        "2367"
    ],
    "dateFrom": "2026-01-01",
    "dateTo": "2026-10-16"
};

// Run the Actor and wait for it to finish
const run = await client.actor("thatmike1/darts-player-form").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 = {
    "playerIds": ["2367"],
    "dateFrom": "2026-01-01",
    "dateTo": "2026-10-16",
}

# Run the Actor and wait for it to finish
run = client.actor("thatmike1/darts-player-form").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 '{
  "playerIds": [
    "2367"
  ],
  "dateFrom": "2026-01-01",
  "dateTo": "2026-10-16"
}' |
apify call thatmike1/darts-player-form --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,thatmike1/darts-player-form"
        }
    }
}
```

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/PRtFwg01rpXcDbMBa/builds/xTTOGg3VYi0lHK6jm/openapi.json
