# Web Game Distribution & Platform Intelligence (`obliging_persimmon_cki/web-game-distribution-platform-intelligence`) Actor

Discover where browser and HTML5 games are distributed across YouTube Playables, CrazyGames, and Poki. Normalize platform records into one canonical game entity with availability, metadata differences, qualified channel changes, match confidence, provenance, and partial coverage.

- **URL**: https://apify.com/obliging\_persimmon\_cki/web-game-distribution-platform-intelligence.md
- **Developed by:** [Dung Huynh](https://apify.com/obliging_persimmon_cki) (community)
- **Categories:** Developer tools, Automation, Other
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-usage

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — 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

## Web Game Distribution & Platform Intelligence

This JavaScript ESM Apify Actor accepts bounded browser-game queries and reports normalized observations for YouTube Playables, CrazyGames, and Poki. It resolves platform candidates conservatively, preserves sanitized provenance, emits useful partial results, compares a prior compact snapshot, and writes one dataset record per unique `recordId`.

The implementation uses injected sanitized fixtures for deterministic checks. Fixture success is parser evidence only; it is not a claim of current live source coverage.

### Run locally

```bash
npm ci --ignore-scripts --no-audit --no-fund
npm test
apify validate-schema
node scripts/run-fixture-benchmark.mjs --verify
```

`apify run --purge --input-file default-input.json` is the local Actor command. It exercises the configured source transports and is not a substitute for Factory Cloud QA or human review of live coverage.

### Canonical default input

The following JSON object is the canonical runnable input. It is byte-equivalent in `default-input.json`, `sample-input.json`, `storage/key_value_stores/default/INPUT.json`, and the defaults in `.actor/input_schema.json`.

```json
{
  "queries": [
    {
      "name": "Cut the Rope",
      "url": "https://www.crazygames.com/game/cut-the-rope"
    }
  ],
  "platforms": [
    "youtube-playables",
    "crazygames",
    "poki"
  ],
  "maxCandidatesPerPlatform": 10,
  "minMatchConfidence": 0.75,
  "comparisonSnapshotKey": "LAST-SNAPSHOT",
  "persistSnapshot": true,
  "maxConcurrency": 3
}
```

### Input contract

`queries` is required. A query must contain a non-empty name, a safe supported-platform URL, or both. Names are trimmed and collapsed. URLs must use HTTPS, have no credentials, fragments, explicit ports, sensitive parameters, or unsupported routes. Accepted routes are CrazyGames `/game/...`, Poki `/g/...` and `/en/g/...`, and YouTube Playables under `/playables` or `playables.youtube.com`.

| Field | Type | Default | Bounds and behavior |
| --- | --- | --- | --- |
| `queries` | array<object> | the canonical query above | 1–25 items; each has a name, URL, or both |
| `platforms` | array<string> | `youtube-playables`, `crazygames`, `poki` | unique supported adapter IDs |
| `maxCandidatesPerPlatform` | integer | `10` | 1–20 candidates per query and platform |
| `minMatchConfidence` | number | `0.75` | finite threshold from 0 to 1 |
| `comparisonSnapshotKey` | string | `LAST-SNAPSHOT` | safe 1–64 character key |
| `persistSnapshot` | boolean | `true` | replace the compact snapshot only after output succeeds |
| `maxConcurrency` | integer | `3` | 1–6 simultaneous source operations |

The Apify item editor schema cannot express the conditional name-or-URL rule, so `normalizeInput()` is authoritative for that validation.

### Output contract

Every written record contains the following fields. `.actor/dataset_schema.json` is the typed dataset contract and `.actor/output_schema.json` describes Store-facing field templates.

| Field | Required | Meaning |
| --- | --- | --- |
| `recordId` | yes | Stable dataset identity: `game:<canonical-id>` or `unresolved:<normalized-query>` |
| `canonicalGameId` | no | Stable ID only when evidence safely resolves the entity |
| `canonicalTitle` | no | Highest-confidence normalized display title |
| `inputQueries` | yes | Deduplicated normalized queries represented by the record |
| `resolutionStatus` | yes | `matched`, `ambiguous`, `not_found`, `partial`, or `source_unavailable` |
| `matchConfidence` | yes | Bounded entity-resolution score, not availability probability |
| `aliases` | yes | Deduplicated title aliases retained as evidence |
| `platformAvailability` | yes | Per-platform status, URL, metadata, match score, evidence, and observation status |
| `metadataDifferences` | yes | Compared values from successfully matched platform records |
| `distributionChanges` | yes | Qualified `added`, `removed`, `metadata_changed`, or `unchanged` channels |
| `provenance` | yes | Sanitized platform, source URL, retrieval time, evidence type, and confidence |
| `coverage` | yes | Requested, successful, failed, access-restricted, unobserved, and complete counts |
| `warnings` | yes | Bounded machine-readable warning codes |
| `observedAt` | yes | ISO 8601 observation time |

The typed nested output contract is in `.actor/dataset_schema.json`. Apify's `.actor/output_schema.json` uses string templates for the item editor, which cannot express nested dataset types; optional canonical fields are omitted when they cannot be resolved safely.

Confidence is explainable normalized identity evidence. It is not a probability and does not upgrade a failed or restricted source. `coverage.complete` is true only when every requested platform has a successful qualified observation without access restriction or an unobserved adapter. A partial record may contain matched channels and explicit failures; `source_unavailable` is used when all requested sources fail or are inaccessible.

### Runtime and state behavior

`src/runtime/actor-runtime.mjs` initializes the Apify Actor, reads and normalizes canonical input, creates the three isolated adapters, loads the named snapshot, runs bounded orchestration, validates all output records, writes the default dataset, persists a compact versioned snapshot, stores run statistics under `RUN-STATS`, logs bounded lifecycle messages, and exits cleanly.

Output validation rejects malformed records, empty IDs, and duplicate IDs before the first dataset write. Snapshot state is comparison state only and is never copied into current availability or provenance. A corrupt or incompatible prior snapshot suppresses change claims with a warning. Snapshot write failure preserves successful dataset output. Dataset failure prevents snapshot replacement.

### Bounded source behavior and limitations

- The Actor supports only public HTTP-first adapter boundaries for YouTube Playables, CrazyGames, and Poki.
- Each run accepts at most 25 queries, inspects at most 20 candidates per platform, allows at most six-way source concurrency, and uses at most two total attempts per request.
- Response bodies, titles, metadata arrays, warnings, provenance, and snapshots are size-bounded. Raw HTML, credentials, cookies, tokens, and secrets are not output or logged.
- Browser automation, authenticated sources, anti-bot bypass, alerts, scheduling, and additional platforms are outside this contract.
- Public catalogs, markup, regions, robots rules, source terms, and access restrictions can change. YouTube Playables may expose no stable public catalog or require session state; the adapter reports restricted, unavailable, parse-failed, timeout, or transport outcomes instead of fabricating absence.
- A removed channel is reported only when a prior available channel exists and the current source successfully observes absence. Failed, restricted, and unobserved sources never qualify a removal.

### Deterministic evidence

`node scripts/run-fixture-benchmark.mjs --verify` runs sanitized local fixtures with a fixed clock and records measured request count, local latency, entity resolution, duplicate collapse, snapshot change detection, partial-result behavior, and serialized output size in `BENCHMARK_NOTES.md`. It performs no DNS or network work. Those measurements are bounded local evidence and do not establish live coverage, cost, or availability.

Factory Cloud QA and human review remain the handoff for live source coverage, robots.txt and source-term considerations, confidence behavior, partial-result examples, and publication readiness. This workspace does not deploy, publish, schedule, automate, or change pricing.

# Actor input Schema

## `queries` (type: `array`):

One to twenty-five game queries. Each query has a name, a safe supported-platform URL, or both.

## `platforms` (type: `array`):

Supported platform adapter IDs.

## `maxCandidatesPerPlatform` (type: `integer`):

Maximum discovery candidates inspected for each query and platform.

## `minMatchConfidence` (type: `number`):

Zero-to-one threshold required for conservative automatic entity linking.

## `comparisonSnapshotKey` (type: `string`):

Safe key-value-store key for the previous compact comparison snapshot.

## `persistSnapshot` (type: `boolean`):

Whether a successful current snapshot may replace the named prior snapshot.

## `maxConcurrency` (type: `integer`):

Maximum number of simultaneous bounded source operations.

## Actor input object example

```json
{
  "queries": [
    {
      "name": "Cut the Rope",
      "url": "https://www.crazygames.com/game/cut-the-rope"
    }
  ],
  "platforms": [
    "youtube-playables",
    "crazygames",
    "poki"
  ],
  "maxCandidatesPerPlatform": 10,
  "minMatchConfidence": 0.75,
  "comparisonSnapshotKey": "LAST-SNAPSHOT",
  "persistSnapshot": true,
  "maxConcurrency": 3
}
```

# Actor output Schema

## `recordId` (type: `string`):

Stable identity derived from a canonical game ID or normalized unresolved query.

## `canonicalGameId` (type: `string`):

Stable canonical entity ID when safely resolved.

## `canonicalTitle` (type: `string`):

Selected normalized display title.

## `inputQueries` (type: `string`):

Normalized input queries merged into this canonical or unresolved record.

## `resolutionStatus` (type: `string`):

One of matched, ambiguous, not\_found, partial, or source\_unavailable.

## `matchConfidence` (type: `string`):

Bounded zero-to-one entity-resolution confidence, not a probability of platform availability.

## `aliases` (type: `string`):

Deduplicated platform title aliases retained as match evidence.

## `platformAvailability` (type: `string`):

Normalized per-platform status, URL, title, metadata, match confidence, evidence, and observation state.

## `metadataDifferences` (type: `string`):

Field-level compared values across successfully matched platform records with provenance.

## `distributionChanges` (type: `string`):

Qualified added, removed, metadata\_changed, or unchanged platform channels.

## `provenance` (type: `string`):

Source platform, safe URL, retrieval time, evidence type, and bounded confidence without raw HTML or secrets.

## `coverage` (type: `string`):

Requested, successful, failed, access-restricted, unobserved, and complete coverage counts.

## `warnings` (type: `string`):

Machine-readable warning codes and concise safe messages for ambiguity, partial coverage, and adapter errors.

## `observedAt` (type: `string`):

ISO 8601 timestamp for the current observation.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("obliging_persimmon_cki/web-game-distribution-platform-intelligence").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("obliging_persimmon_cki/web-game-distribution-platform-intelligence").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 '{}' |
apify call obliging_persimmon_cki/web-game-distribution-platform-intelligence --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,obliging_persimmon_cki/web-game-distribution-platform-intelligence"
        }
    }
}
```

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/4t5qcQmda5MEJy1rZ/builds/CbbHkSAs0nMXRXENj/openapi.json
