# Product Distribution Checker — EAN Coverage EU Sites (`studio-amba/product-distribution-checker`) Actor

Give a brand's EAN/product list and target countries. Runs our Geizhals and Skroutz price-comparison scrapers (Ceneo, Prisjakt, Idealo, Heureka optional), returns every matched listing per EAN per country, plus an explicit absence row when nothing matched.

- **URL**: https://apify.com/studio-amba/product-distribution-checker.md
- **Developed by:** [Studio Amba](https://apify.com/studio-amba) (community)
- **Categories:** E-commerce, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $10.00 / 1,000 sku-country checks

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/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/docs.md):

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

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python/docs.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/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

## Product Distribution Checker — EAN Coverage Across EU Price Sites

Give this actor a brand's **EAN/product list** and the **countries** you care about. It runs our own European price-comparison scrapers in parallel and tells you, per EAN per country: is this product listed, where, and at what price — and just as importantly, **where it is confirmed NOT listed**.

Channel managers, distributors, and brand protection teams pay agencies for exactly this check. This actor runs it on demand or on a schedule instead.

### What it does

- **Runs our own price-comparison scrapers.** Geizhals (AT/DE) and Skroutz (GR) by default — the two engines cloud-verified working end-to-end on a plain residential proxy. Ceneo (PL), Prisjakt (SE), Idealo (DE) and Heureka (CZ) are all available opt-in; see the reliability table below before adding them.
- **Matches each engine's result back to your product.** EAN/barcode match when an engine exposes one (none currently do — see Honest Limitations), fuzzy product-name match otherwise.
- **Returns every matching listing**, not just the cheapest one — if an engine's search turns up three variants of your product (different colors, bundle sizes), you get all three rows.
- **Emits an explicit absence row when a country was actually checked and nothing matched.** `present: false` is not a missing row, it's a returned row — the proof that the engine ran, searched, and found nothing for your EAN in that market.
- **Never fakes an absence from a failed check.** If the underlying engine's own run failed or timed out, no row is emitted for that product/country/engine at all — a real infrastructure failure and a confirmed "not listed" are different facts, and this actor keeps them separate rather than silently treating a broken check as a distribution gap.

### How to scrape product distribution data across Europe

1. Enter your **products** — each one needs an `ean` (barcode) and a `productName` (used as the search query, since none of the bundled engines support searching by EAN directly). `brand` is optional and carried through to the output.
2. Pick your **countries** — `AT`, `DE`, `GR` are covered by the cloud-verified healthy default engines. Add `PL`, `SE`, `CZ` and opt in the matching engine via `engines` if you need attempted coverage there.
3. Run it. Each row is one product/country/engine check: a matched listing (`present: true`, with price and URL) or a confirmed gap (`present: false`).
4. Group the output by `ean` + `country` downstream to build a coverage matrix — which markets carry the SKU, which don't, and at what price where it does.
5. Schedule it to catch new listings or new gaps as resellers add or drop a SKU.

### Input

| Field | Type | Description |
|---|---|---|
| `products` | array | `{ ean, productName, brand? }` objects to check. Defaults to a demo Sony product. |
| `countries` | array | ISO country codes to check. Default `["AT", "DE", "GR"]` (the cloud-verified healthy default). |
| `engines` | array | Opt-in engines beyond the default: `ceneo`, `prisjakt`, `idealo`, `heureka`. See reliability table. |
| `matchThreshold` | number | Minimum fuzzy name-similarity (0-1) to count as a match. Default `0.35`. |
| `maxItemsPerSource` | integer | Search results pulled per engine per product before matching. Default `8`. |
| `timeoutPerSourceSecs` | integer | Per-engine call timeout before treating that check as failed. Default `150`. |
| `proxyConfiguration` | object | Overrides the built-in per-engine RESIDENTIAL default. |

### Output fields

| Field | Type | Description |
|---|---|---|
| `ean` | string | Product EAN/barcode from your input |
| `productName` | string | Product name from your input |
| `brand` | string \| null | Brand from your input, if provided |
| `country` | string | ISO country code this row checks |
| `engine` | string | `geizhals`, `skroutz`, `ceneo`, `prisjakt`, `idealo`, or `heureka` |
| `sellerName` | string | The comparison site itself (see Honest Limitations — not a named reseller) |
| `price` | number \| null | Listed price in the local currency; `null` on an absence row |
| `currency` | string \| null | Local market currency, populated even on an absence row |
| `url` | string | Link to the matched listing; empty string on an absence row |
| `present` | boolean | `true` = matched listing found. `false` = engine checked, nothing matched |
| `matchType` | string \| null | `ean`, `name`, or `null` on an absence row |
| `matchScore` | number \| null | 0-1 fuzzy match confidence; `1` for an EAN match; `null` on absence |
| `offerCount` | number \| null | Resellers the engine aggregated this price from, where exposed |
| `scrapedAt` | string | ISO timestamp of the run |

### Example output

Present row:

```json
{
  "ean": "4548736112212",
  "productName": "Sony WH-1000XM5",
  "brand": "Sony",
  "country": "AT",
  "engine": "geizhals",
  "sellerName": "Geizhals",
  "price": 189,
  "currency": "EUR",
  "url": "https://geizhals.at/sony-wh-1000xm5-v101824.html",
  "present": true,
  "matchType": "name",
  "matchScore": 1,
  "offerCount": 80,
  "scrapedAt": "2026-07-20T08:35:35.179Z"
}
````

Confirmed-absence row (cloud-verified — a genuinely nonexistent SKU checked against Geizhals AT/DE):

```json
{
  "ean": "9999999999999",
  "productName": "Zorvexnaught Widget XQ7000-Nonexistent",
  "brand": "Zorvexnaught",
  "country": "AT",
  "engine": "geizhals",
  "sellerName": "Geizhals",
  "price": null,
  "currency": "EUR",
  "url": "",
  "present": false,
  "matchType": null,
  "matchScore": null,
  "offerCount": null,
  "scrapedAt": "2026-07-20T08:39:42.770Z"
}
```

### Honest limitation: no per-seller data

None of the bundled engines expose a per-seller/per-merchant breakdown — each returns a **single aggregate "lowest price found"** per matched product, plus (on some) a count of how many resellers that price was drawn from. There is no seller name, no per-offer list.

So `sellerName` is honestly **the comparison engine itself** (e.g. "Geizhals"), not a named reseller. `offerCount` tells you how many resellers that price was pulled from, where the engine exposes it. Reseller-level attribution would require scrapers built against individual retailer sites, not price-comparison aggregators — a different, much larger build.

### Honest limitation: EAN matching is architecturally ready, not yet live

Matching checks an exact EAN first, then falls back to fuzzy name matching. As of this actor's cloud verification (2026-07-20), **none of the 6 bundled engines return an EAN/barcode field in their own output**, so every match today is a `name` match — never hidden, always visible in `matchType`. If a child scraper adds EAN extraction later, this actor picks it up automatically with no code change on this end.

### Engines and reliability (method disclosure)

Cloud-verified 2026-07-20 — every engine below was actually run with a real query ("iphone 15") right before this actor shipped, not just read from its source code.

| Engine | Market | Currency | Method | Reliability | Notes |
|---|---|---|---|---|---|
| **Geizhals** | AT/DE | EUR | Residential proxy + parsing | High | Cloud-verified: real products, real prices, real matches. Default. |
| **Skroutz** | GR | EUR | Residential proxy + parsing | High | Cloud-verified: real products, real prices (4 iPhone 15 variants returned in the live test). Default. |
| **Idealo** | DE | EUR | Bright Data Scraping Browser | Broken | Live test hit "A global adaptive rate limit has been applied" then an anti-bot block — 0 products. Opt-in. |
| **Ceneo** | PL | PLN | Residential proxy + parsing | Broken | Live test TIMED OUT. Opt-in. |
| **Heureka** | CZ | CZK | Residential proxy + parsing | Broken | Live test returned only nav/category chrome ("Speciální nabídky", "Elektronika", "Letní akce") — a parser bug on the child, not real product data. Opt-in. |
| **Prisjakt** | SE | SEK | Residential proxy + parsing | Blocked | Live test returned 0 items — Cloudflare "Just a moment..." challenge even through a residential Swedish IP. Opt-in. |

All engines get a RESIDENTIAL proxy scoped to their market by default. The actor degrades gracefully: if an engine's run fails, the run continues with the others and no row is emitted for that failed check (see "failed vs. absent" below) — it never gets silently counted as a confirmed gap.

### Failed check vs. confirmed absence — why this matters

This is the core design decision in this actor, and it's worth being explicit about:

- **Confirmed absence (`present: false`, row emitted):** the child engine's run SUCCEEDED and returned data, but nothing in it matched your EAN/product. The check happened; the answer is "not found here."
- **Failed check (no row emitted at all):** the child engine's run errored, timed out, or hit its own zero-results guard and failed itself. We do not know whether the product is listed or not — we just couldn't check. Emitting a `present: false` row here would be a false negative dressed up as data.

Cloud-verified case (2026-07-20): checking a nonexistent product against Geizhals (AT/DE) and Skroutz (GR) — Geizhals's run succeeded, returned 5 unrelated products, none matched, so 2 confirmed-absence rows were pushed (AT, DE). Skroutz's own run failed with its own zero-results guard ("Zero results scraped... Check your search query"), so **zero rows were pushed for GR** — not a false absence claim. Check the run summary's `checksFailed` count if you need to know which product/engine pairs couldn't be checked.

A run with zero present rows because nothing matched is a **success**, not a failure — see the run summary. The actor only calls `Actor.fail()` when every single engine call across the whole run failed, meaning nothing could be checked at all.

### Documented limits

- **Query is by product name, not EAN.** None of the bundled engines support searching by barcode directly.
- **Fuzzy name matching can miss or mismatch.** A generic product name risks matching the wrong SKU or matching too many variants. Use a specific model name and tune `matchThreshold` as needed.
- **Country coverage is limited to what the default 2 engines serve** (AT, DE, GR) unless you opt in the broken/blocked engines and accept a real chance of failed checks until they're healed.
- **This is presence/absence on comparison ENGINES, not exhaustive retail coverage.** A product genuinely sold direct-to-consumer only, or through retailers this actor's 6 engines don't aggregate, will show as absent even if it's sold somewhere.

### Who uses this

- **Brand channel managers** verifying which markets and resellers actually carry a SKU before a launch or a distribution audit.
- **Distributors** proving coverage gaps to a brand, or proving a claimed distributor relationship is actually live in-market.
- **Pricing teams** building a coverage-by-country matrix without manually searching six sites per SKU.

### Pricing

Pay per run start and per row returned — present or absent, each row is one sku-country check, and a confirmed gap is exactly as much of a deliverable as a confirmed listing. See the pricing tab for current rates.

### FAQ

**Does an absence row mean the product definitely isn't sold in that country?**
It means the engines this actor checks didn't have a matching listing at the time of the run. See "Documented limits" — this is engine-level coverage, not exhaustive retail coverage.

**Why isn't every country in my request necessarily covered?**
Only Geizhals (AT/DE) and Skroutz (GR) are cloud-verified healthy by default. If you request PL/SE/CZ without opting in the matching engine, the run summary lists them as `countriesUnsupported` rather than silently returning nothing or faking an absence.

**Do I need a proxy?**
No — every engine defaults to a RESIDENTIAL proxy scoped to its market. Supply your own `proxyConfiguration` only if you want to override that.

**Is this legal?**
The actor collects publicly advertised listings from public comparison sites. You are responsible for how you use the output.

# Actor input Schema

## `products` (type: `array`):

List of products to check distribution for. Each product needs an ean (barcode) and a productName (used as the search query on every price-comparison engine, since none of the 6 engines support searching by EAN directly). brand is optional and passed through to the output for grouping.

## `countries` (type: `array`):

ISO country codes to check distribution in. AT/DE (Geizhals) and GR (Skroutz) are covered by the two engines that are cloud-verified healthy on a plain residential proxy. PL (Ceneo), SE (Prisjakt) and CZ (Heureka) are only checked if you also opt in that engine via `engines` below — otherwise they're silently skipped and listed as unsupported in the run summary, never silently faked as absent.

## `engines` (type: `array`):

Geizhals and Skroutz always run when their country is requested — they're cloud-verified working. Add Ceneo, Prisjakt, Idealo or Heureka here to attempt checking PL/SE/DE/CZ through them too, accepting a real chance the check comes back FAILED (no row) until that child is healed. See README for the reliability table.

## `matchThreshold` (type: `number`):

Minimum fuzzy name-similarity score (0-1) required to count an engine result as a match for your product, when no EAN match is available (which is every match today — see README). Lower catches more listings but risks matching the wrong SKU.

## `maxItemsPerSource` (type: `integer`):

Cap on search results pulled from each engine per product before matching.

## `timeoutPerSourceSecs` (type: `integer`):

How long to wait for each engine's child-actor run before treating that check as FAILED (no row emitted — see README on the difference between a failed check and a confirmed absence).

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

Passed through to the child price-comparison scrapers, overriding the actor's own per-engine default (a RESIDENTIAL proxy scoped to each engine's market — all 6 engines return 403/empty/blocked on a plain or datacenter proxy). Leave empty to use the built-in per-engine residential defaults.

## Actor input object example

```json
{
  "products": [
    {
      "ean": "4548736112212",
      "productName": "Sony WH-1000XM5",
      "brand": "Sony"
    }
  ],
  "countries": [
    "AT",
    "DE",
    "GR"
  ],
  "engines": [],
  "matchThreshold": 0.35,
  "maxItemsPerSource": 8,
  "timeoutPerSourceSecs": 150,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# 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 = {
    "products": [
        {
            "ean": "4548736112212",
            "productName": "Sony WH-1000XM5",
            "brand": "Sony"
        }
    ],
    "countries": [
        "AT",
        "DE",
        "GR"
    ],
    "engines": [],
    "matchThreshold": 0.35,
    "maxItemsPerSource": 8,
    "timeoutPerSourceSecs": 150,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("studio-amba/product-distribution-checker").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 = {
    "products": [{
            "ean": "4548736112212",
            "productName": "Sony WH-1000XM5",
            "brand": "Sony",
        }],
    "countries": [
        "AT",
        "DE",
        "GR",
    ],
    "engines": [],
    "matchThreshold": 0.35,
    "maxItemsPerSource": 8,
    "timeoutPerSourceSecs": 150,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("studio-amba/product-distribution-checker").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 '{
  "products": [
    {
      "ean": "4548736112212",
      "productName": "Sony WH-1000XM5",
      "brand": "Sony"
    }
  ],
  "countries": [
    "AT",
    "DE",
    "GR"
  ],
  "engines": [],
  "matchThreshold": 0.35,
  "maxItemsPerSource": 8,
  "timeoutPerSourceSecs": 150,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call studio-amba/product-distribution-checker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=studio-amba/product-distribution-checker",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Product Distribution Checker — EAN Coverage EU Sites",
        "description": "Give a brand's EAN/product list and target countries. Runs our Geizhals and Skroutz price-comparison scrapers (Ceneo, Prisjakt, Idealo, Heureka optional), returns every matched listing per EAN per country, plus an explicit absence row when nothing matched.",
        "version": "0.1",
        "x-build-id": "UMbwfAKWume3dVbJG"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/studio-amba~product-distribution-checker/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-studio-amba-product-distribution-checker",
                "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/studio-amba~product-distribution-checker/runs": {
            "post": {
                "operationId": "runs-sync-studio-amba-product-distribution-checker",
                "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/studio-amba~product-distribution-checker/run-sync": {
            "post": {
                "operationId": "run-sync-studio-amba-product-distribution-checker",
                "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": {
                    "products": {
                        "title": "Products to check",
                        "type": "array",
                        "description": "List of products to check distribution for. Each product needs an ean (barcode) and a productName (used as the search query on every price-comparison engine, since none of the 6 engines support searching by EAN directly). brand is optional and passed through to the output for grouping.",
                        "default": [
                            {
                                "ean": "4548736112212",
                                "productName": "Sony WH-1000XM5",
                                "brand": "Sony"
                            }
                        ]
                    },
                    "countries": {
                        "title": "Target countries",
                        "type": "array",
                        "description": "ISO country codes to check distribution in. AT/DE (Geizhals) and GR (Skroutz) are covered by the two engines that are cloud-verified healthy on a plain residential proxy. PL (Ceneo), SE (Prisjakt) and CZ (Heureka) are only checked if you also opt in that engine via `engines` below — otherwise they're silently skipped and listed as unsupported in the run summary, never silently faked as absent.",
                        "items": {
                            "type": "string",
                            "enum": [
                                "AT",
                                "DE",
                                "GR",
                                "PL",
                                "SE",
                                "CZ"
                            ],
                            "enumTitles": [
                                "Austria (Geizhals)",
                                "Germany (Geizhals, or Idealo opt-in)",
                                "Greece (Skroutz)",
                                "Poland (Ceneo — opt-in, currently broken)",
                                "Sweden (Prisjakt — opt-in, currently blocked)",
                                "Czechia (Heureka — opt-in, currently broken)"
                            ]
                        },
                        "default": [
                            "AT",
                            "DE",
                            "GR"
                        ]
                    },
                    "engines": {
                        "title": "Opt-in engines (beyond the healthy default)",
                        "type": "array",
                        "description": "Geizhals and Skroutz always run when their country is requested — they're cloud-verified working. Add Ceneo, Prisjakt, Idealo or Heureka here to attempt checking PL/SE/DE/CZ through them too, accepting a real chance the check comes back FAILED (no row) until that child is healed. See README for the reliability table.",
                        "items": {
                            "type": "string",
                            "enum": [
                                "ceneo",
                                "prisjakt",
                                "idealo",
                                "heureka"
                            ],
                            "enumTitles": [
                                "Ceneo (PL) — broken child parser, times out",
                                "Prisjakt (SE) — Cloudflare-blocked",
                                "Idealo (DE) — Bright Data rate-limited",
                                "Heureka (CZ) — parser returns nav chrome, not products"
                            ]
                        },
                        "default": []
                    },
                    "matchThreshold": {
                        "title": "Name match threshold",
                        "type": "number",
                        "description": "Minimum fuzzy name-similarity score (0-1) required to count an engine result as a match for your product, when no EAN match is available (which is every match today — see README). Lower catches more listings but risks matching the wrong SKU.",
                        "default": 0.35
                    },
                    "maxItemsPerSource": {
                        "title": "Max results per engine per product",
                        "type": "integer",
                        "description": "Cap on search results pulled from each engine per product before matching.",
                        "default": 8
                    },
                    "timeoutPerSourceSecs": {
                        "title": "Per-engine timeout (seconds)",
                        "type": "integer",
                        "description": "How long to wait for each engine's child-actor run before treating that check as FAILED (no row emitted — see README on the difference between a failed check and a confirmed absence).",
                        "default": 150
                    },
                    "proxyConfiguration": {
                        "title": "Proxy configuration",
                        "type": "object",
                        "description": "Passed through to the child price-comparison scrapers, overriding the actor's own per-engine default (a RESIDENTIAL proxy scoped to each engine's market — all 6 engines return 403/empty/blocked on a plain or datacenter proxy). Leave empty to use the built-in per-engine residential defaults."
                    }
                }
            },
            "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
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
