# Product Variant Match Audit (`dapper_locket/product-variant-match-audit`) Actor

Audit product pairs before catalog merges and price comparisons. Flag variant conflicts, inspect field evidence and get optional TypeSafe JEV recommendations. Export JSON/CSV with review reasons. Your TypeSafe key; $3 per 1,000 audited pairs.

- **URL**: https://apify.com/dapper\_locket/product-variant-match-audit.md
- **Developed by:** [Marc Hammerschmidt](https://apify.com/dapper_locket) (community)
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 audited product pairs

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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

## Product Variant Match Audit

Check product pairs before merging catalogs or comparing competitor prices. Get a field-by-field audit, explicit conflict reasons, and optional TypeSafe JEV recommendations: **same**, **different**, or **review**.

### What you get

- Checks for mismatched identifiers, pack quantities, sizes, colors, capacities, conditions, brands and model numbers.
- A readable explanation and the original field evidence for every result.
- Optional semantic comparison through TypeSafe JEV using your own API key.
- JSON, CSV or Excel export through Apify, plus an aggregate run summary.
- A pair fingerprint and release version for tracing results.

This Actor audits **candidate pairs you supply**. It does not discover candidates, scrape websites, update product listings or change prices. It is decision support: `review` is a useful outcome when the supplied evidence is insufficient. Review recommendations before applying catalog changes.

### Quick start

1. Paste product pairs into **Product pairs**. The supplied examples work without external setup.
2. Choose **Rules only** to inspect explicit differences, or **TypeSafe JEV** for semantic recommendations.
3. In JEV mode, enter **your TypeSafe API key** in the encrypted field. Create a key in your own [TypeSafe account](https://console.typesafe.ai/keys).
4. Set the maximum JEV calls and Apify run cost, then start the Actor.
5. Open **Audit results**, **Field evidence** or **Usage and traceability**. Export the dataset or inspect **Run summary**.

#### Example input

```json
{
  "mode": "rules-only",
  "pairs": [
    {
      "id": "filter-pack-check",
      "left": { "title": "Filter F20 single pack", "brand": "ACME", "mpn": "F20", "packQuantity": "1" },
      "right": { "title": "Filter F20 six pack", "brand": "ACME", "mpn": "F20", "packQuantity": "6" }
    }
  ]
}
```

This returns `review` with `attribute_conflict`, identifies `packQuantity`, and makes no JEV call. Different strings can be synonyms or source errors, so the rule audit does not automatically declare a non-match.

For semantic matching, set `mode` to `jev` and supply `typesafeApiKey`. Put condition, accessories and pack contents in product titles when no dedicated field exists. Missing details are not assumed.

### Input reference

| Field | Default | Meaning |
|---|---|---|
| `pairs` | required | 1–1,000 objects containing `id`, `left`, `right` |
| `mode` | `rules-only` | `rules-only` or `jev` |
| `typesafeApiKey` | none | Your encrypted TypeSafe key, required for JEV |
| `threshold` | `0.95` | Both model confidence and choice probability must meet this value; range 0.8–1 |
| `maxJevCalls` | `1000` | Maximum TypeSafe requests in this run, range 0–1,000 |

Product fields: `title`, `brand`, `mpn`, `gtin`, `size`, `color`, `packQuantity`, `condition`, `capacity`. Values must be strings, up to 1,500 characters each. Each side needs a title, MPN or GTIN. GTINs must have a valid check digit and 8, 12, 13 or 14 digits; keep leading zeros. Pair IDs must be unique. Each pair is limited to 12 KB. Undeclared product fields are omitted.

### How decisions work

1. Explicit attribute or identifier disagreements are flagged for review with the differing fields. No model call is needed.
2. In rules-only mode, all other pairs remain reviewable. This mode does not certify equivalence.
3. JEV evaluates unresolved pairs using only supplied product data. Low confidence or ambiguous evidence becomes `review`.
4. When the JEV call limit is exhausted, unresolved pairs have `status: not_evaluated` and `reason: jev_call_limit`. They are not charged as audited pairs.

`evidence.comparison: different_text` means the supplied values differ after basic text normalization, not that the physical products are necessarily different. A checksum-valid GTIN is not proof of authentic identifier assignment. Model confidence is not a measured accuracy guarantee. Model updates and different input detail may change recommendations.

### Output

Each row contains `id`, `left`, `right`, `decision`, `status`, `reason`, `explanation`, `evidence`, `conflictingFields`, `reviewRequired`, `method`, model usage where applicable, `pairFingerprint`, `release`, `elapsedMs` and `evaluatedAt`.

`SUMMARY` reports requested, stored, audited and skipped pairs, decision counts, review rate, model calls, input tokens, estimated TypeSafe cost, completion and stop reason. Estimates exclude Apify charges, tax and manual work. Exported data retains user-supplied strings; treat spreadsheet cells as untrusted data and import them as text when needed.

### Pricing

**$3 per 1,000 audited product pairs** ($0.003 each), plus **$0.00005 per Actor start**. Apify platform usage is included. A completed audit that recommends `review` is charged. Technical failures without a stored audited result and pairs skipped because of the JEV call limit are not charged as audited pairs. The start event can still apply to failed runs.

In JEV mode, **TypeSafe bills your own API account separately**. The Actor never supplies a shared seller key. At the published rate of $0.042 per million input tokens, 500 input tokens cost about $0.000021; actual token use depends on your input. Check [current TypeSafe documentation](https://docs.typesafe.ai/api) and the Actor's Pricing tab before running.

The Actor checks Apify spending capacity before processing each pair, saves results with the audit event, and stops when the limit is reached. Set an appropriate run timeout for large batches. TypeSafe requests are sequential, with a 20-second timeout and no automatic retries.

### Failures and reruns

Invalid input fails before pair processing. A provider or storage error stops the run; completed dataset rows remain available. An incomplete run may not have a summary. Inspect the log and dataset before retrying. Resuming a completed or partial run is not automatic: submit only the remaining IDs in a new run to avoid paying for repeat work. Failed TypeSafe requests may still incur provider charges. The configured TypeSafe call limit and Apify spending limit are separate controls.

### Privacy and permissions

JEV mode sends the declared product fields and pair IDs to `api.typesafe.ai`. Arbitrary input URLs are never fetched. API keys are encrypted by Apify input handling and are not written to results or source. Only submit data you are authorized to process; do not put secrets or personal information in product fields. Input and results are stored by Apify according to your account settings. The Actor uses limited permissions and its own run storage.

### Validation and support

Release 1.0 includes behavior and billing regression tests. A previous live integration test processed four synthetic pairs: one `different` and three `review`, including a same-product example held below threshold. These are integration checks, not a representative accuracy benchmark. No state-of-the-art or universal product-matching accuracy is claimed.

Report reproducible problems through the Actor's **Issues** tab, with the run ID and a redacted example. Never post API keys or private customer data. There is no guaranteed response-time SLA.

# Actor input Schema

## `pairs` (type: `array`):

Supply 1–1000 candidate pairs with id, left and right. Product fields must be strings. This Actor audits provided data; it does not scrape listings or find candidates.

## `mode` (type: `string`):

Rules-only flags conflicts and unresolved pairs without model calls. JEV compares unresolved pairs using your TypeSafe key.

## `typesafeApiKey` (type: `string`):

Required for JEV mode unless TYPESAFE\_API\_KEY is configured. Stored encrypted by Apify; sent only to api.typesafe.ai.

## `threshold` (type: `number`):

Both choice probability and confidence must meet this threshold. This is not a measured accuracy guarantee.

## `maxJevCalls` (type: `integer`):

Hard limit on TypeSafe requests. Skipped pairs are marked not\_evaluated and do not incur the audited-pair event charge. TypeSafe API costs are separate.

## Actor input object example

```json
{
  "pairs": [
    {
      "id": "same-model",
      "left": {
        "title": "ACME A100 electric drill 18V blue tool only",
        "brand": "ACME",
        "mpn": "A100",
        "color": "blue",
        "packQuantity": "1",
        "condition": "new"
      },
      "right": {
        "title": "ACME A100 18 volt drill blue without battery",
        "brand": "ACME",
        "mpn": "A100",
        "color": "blue",
        "packQuantity": "1",
        "condition": "new"
      }
    },
    {
      "id": "pack-conflict",
      "left": {
        "title": "Filter F20 1 pack",
        "packQuantity": "1"
      },
      "right": {
        "title": "Filter F20 6 pack",
        "packQuantity": "6"
      }
    },
    {
      "id": "capacity-conflict",
      "left": {
        "title": "Phone Z 128 GB",
        "capacity": "128 GB"
      },
      "right": {
        "title": "Phone Z 256 GB",
        "capacity": "256 GB"
      }
    }
  ],
  "mode": "rules-only",
  "threshold": 0.95,
  "maxJevCalls": 1000
}
```

# Actor output Schema

## `results` (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 = {
    "pairs": [
        {
            "id": "same-model",
            "left": {
                "title": "ACME A100 electric drill 18V blue tool only",
                "brand": "ACME",
                "mpn": "A100",
                "color": "blue",
                "packQuantity": "1",
                "condition": "new"
            },
            "right": {
                "title": "ACME A100 18 volt drill blue without battery",
                "brand": "ACME",
                "mpn": "A100",
                "color": "blue",
                "packQuantity": "1",
                "condition": "new"
            }
        },
        {
            "id": "pack-conflict",
            "left": {
                "title": "Filter F20 1 pack",
                "packQuantity": "1"
            },
            "right": {
                "title": "Filter F20 6 pack",
                "packQuantity": "6"
            }
        },
        {
            "id": "capacity-conflict",
            "left": {
                "title": "Phone Z 128 GB",
                "capacity": "128 GB"
            },
            "right": {
                "title": "Phone Z 256 GB",
                "capacity": "256 GB"
            }
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("dapper_locket/product-variant-match-audit").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 = { "pairs": [
        {
            "id": "same-model",
            "left": {
                "title": "ACME A100 electric drill 18V blue tool only",
                "brand": "ACME",
                "mpn": "A100",
                "color": "blue",
                "packQuantity": "1",
                "condition": "new",
            },
            "right": {
                "title": "ACME A100 18 volt drill blue without battery",
                "brand": "ACME",
                "mpn": "A100",
                "color": "blue",
                "packQuantity": "1",
                "condition": "new",
            },
        },
        {
            "id": "pack-conflict",
            "left": {
                "title": "Filter F20 1 pack",
                "packQuantity": "1",
            },
            "right": {
                "title": "Filter F20 6 pack",
                "packQuantity": "6",
            },
        },
        {
            "id": "capacity-conflict",
            "left": {
                "title": "Phone Z 128 GB",
                "capacity": "128 GB",
            },
            "right": {
                "title": "Phone Z 256 GB",
                "capacity": "256 GB",
            },
        },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("dapper_locket/product-variant-match-audit").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 '{
  "pairs": [
    {
      "id": "same-model",
      "left": {
        "title": "ACME A100 electric drill 18V blue tool only",
        "brand": "ACME",
        "mpn": "A100",
        "color": "blue",
        "packQuantity": "1",
        "condition": "new"
      },
      "right": {
        "title": "ACME A100 18 volt drill blue without battery",
        "brand": "ACME",
        "mpn": "A100",
        "color": "blue",
        "packQuantity": "1",
        "condition": "new"
      }
    },
    {
      "id": "pack-conflict",
      "left": {
        "title": "Filter F20 1 pack",
        "packQuantity": "1"
      },
      "right": {
        "title": "Filter F20 6 pack",
        "packQuantity": "6"
      }
    },
    {
      "id": "capacity-conflict",
      "left": {
        "title": "Phone Z 128 GB",
        "capacity": "128 GB"
      },
      "right": {
        "title": "Phone Z 256 GB",
        "capacity": "256 GB"
      }
    }
  ]
}' |
apify call dapper_locket/product-variant-match-audit --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,dapper_locket/product-variant-match-audit"
        }
    }
}
```

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/pTbwqHpg5Nvsh1MXS/builds/r8IgukAJelhT8vmBC/openapi.json
