# Fuzzy List Reconciler - VLOOKUP That Tolerates Typos (`nibble/list-fuzzy-reconciler`) Actor

Fuzzy-join two lists (CSV/JSON) on a key column with a similarity threshold. Returns matched pairs with scores, plus unmatched and near-match rows.

- **URL**: https://apify.com/nibble/list-fuzzy-reconciler.md
- **Developed by:** [Simon Fletcher](https://apify.com/nibble) (community)
- **Categories:** Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 reconciled rows

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 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

Fuzzy List Reconciler is a **VLOOKUP that tolerates typos**. Give it **two lists** (CSV or
JSON), tell it which **key column(s)** to join on, and it returns the **matched pairs with a
similarity score**, plus everything that **didn't** match and the **near-misses** just below
your threshold — so you can reconcile messy data from two sources without writing any code.

It runs on the [Apify platform](https://apify.com/actors): call it from the **API**, an
**AI agent** (via Apify MCP), **Make/Zapier** integrations, or on a **schedule**. No accounts,
no scraping, no personal-data harvesting — it only processes the two lists **you** supply.

### What does Fuzzy List Reconciler do?

Real-world lists never line up exactly. `Acme Corp` in one export is `Acme Corporation` in
another; `José` becomes `Jose`; `John Smith` becomes `Smith, John`. A plain join drops all of
those. This Actor uses **string-similarity matching** (Jaro-Winkler, Levenshtein, token-sort,
token-set) with **normalization** (accent folding, punctuation stripping, whitespace/case) to
match records that a human would call "the same", and gives every pair a **0–1 score** so you
stay in control of how strict the match is.

Typical jobs:

- **Reconcile two data exports** — CRM vs billing, vendor list vs accounting system.
- **Enrich a list** — attach revenue/IDs from list B onto the closest name in list A.
- **De-duplicate across sources** — find the same entity spelled differently in two files.
- **QA an import** — see which rows would fail to match *before* you load them.

### Why use Fuzzy List Reconciler?

- **No setup, API-callable.** Desktop options (Excel Fuzzy Lookup, OpenRefine, Python
  `rapidfuzz`) need installs and scripting. This runs in the cloud and returns clean JSON.
- **Agent-friendly output.** Concise, flat, structured records — no HTML blobs — ready for
  LLM agents through Apify MCP.
- **You tune the strictness.** Pick the algorithm, threshold, and whether it's 1:1, VLOOKUP-
  style, or many-to-many.
- **See the near-misses.** Rows that *almost* matched are reported separately so you can lower
  the threshold with confidence instead of guessing.

### How to use Fuzzy List Reconciler

1. Paste or link your **left list** and **right list** (JSON array, CSV text, an http(s) URL to
   a file, or `base64:`-prefixed content — format is auto-detected).
2. Set **Left key column(s)** and **Right key column(s)** — the fields to match on. Use a
   comma-separated list for composite keys (e.g. `first_name,last_name`).
3. Pick an **algorithm** and a **threshold** (0.85 is a good start).
4. Choose a **match strategy** (1:1 reconcile, VLOOKUP-style, or all pairs above threshold).
5. Run it, then download the results as **JSON, CSV, Excel, or HTML**.

### Input

| Field | Type | Description |
|-------|------|-------------|
| `leftData` | string | First list: JSON array, CSV text, http(s) URL, or `base64:` content. |
| `rightData` | string | Second list, same accepted formats. |
| `leftKey` | string | Column(s) in the left list to match on (comma-separated for composite keys). |
| `rightKey` | string | Column(s) in the right list — must have the same number of columns. |
| `algorithm` | enum | `jaro_winkler` (names), `levenshtein` (edit distance), `token_sort` (word-order agnostic), `token_set` (subset/superset). |
| `threshold` | number | Minimum similarity (0–1) to count as a match. Default `0.85`. |
| `matchStrategy` | enum | `best_one_to_one`, `best_per_left` (VLOOKUP), or `all_above_threshold`. |
| `normalize` | boolean | Fold accents, strip punctuation, collapse whitespace, lowercase. Default `true`. |
| `nearMatchMargin` | number | How far below the threshold to still report a "near match". Default `0.15`. |
| `includeUnmatched` / `includeNearMatches` | boolean | Toggle unmatched / near-match records in the output. |

#### Input example

```json
{
  "leftData": "[{\"company\":\"Acme Corp\"},{\"company\":\"Globex Inc.\"},{\"company\":\"Initech\"}]",
  "rightData": "name,cik\nAcme Corporation,111\nGlobex,222\nUmbrella LLC,333",
  "leftKey": "company",
  "rightKey": "name",
  "algorithm": "jaro_winkler",
  "threshold": 0.85,
  "matchStrategy": "best_one_to_one"
}
```

### Output

Each dataset record has a `type` of `matched`, `near_match`, `unmatched_left`, or
`unmatched_right`. You can download the dataset in various formats such as JSON, HTML, CSV, or
Excel.

```json
[
  {
    "type": "matched",
    "similarity": 0.9125,
    "algorithm": "jaro_winkler",
    "left_index": 0, "right_index": 0,
    "left_key": "acme corp", "right_key": "acme corporation",
    "left":  { "company": "Acme Corp" },
    "right": { "name": "Acme Corporation", "cik": "111" }
  },
  {
    "type": "unmatched_left",
    "left_index": 2,
    "left_key": "initech",
    "left": { "company": "Initech" },
    "best_candidate_index": 1,
    "best_candidate_key": "globex",
    "best_similarity": 0.4365
  },
  {
    "type": "unmatched_right",
    "right_index": 2,
    "right_key": "umbrella llc",
    "right": { "name": "Umbrella LLC", "cik": "333" }
  }
]
```

#### Output fields

| Field | Description |
|-------|-------------|
| `type` | `matched` | `near_match` | `unmatched_left` | `unmatched_right`. |
| `similarity` | Similarity score (0–1) for matched / near-match pairs. |
| `algorithm` | Algorithm used for scoring. |
| `left` / `right` | The full original records from each list. |
| `left_key` / `right_key` | Normalized key strings that were compared. |
| `left_index` / `right_index` | Zero-based positions in the input lists. |
| `best_candidate_key` / `best_similarity` | For unmatched-left rows: the closest right row and its score. |

### Pricing / cost estimation

This Actor is billed **pay-per-event**: one event per non-empty output record (matched pair,
near-match, or unmatched row). Empty rows are never charged, and the run honors your max-charge
cap. Reconciling two ~500-row lists produces roughly 500–1,000 records. Want only the hits? Set
`includeUnmatched` and `includeNearMatches` to `false` to reduce billed records.

### Tips and advanced options

- **Names:** start with `jaro_winkler` at `0.85`. **Reordered words** (`John Smith` /
  `Smith John`): use `token_sort`. **Subset/superset** (`IBM` / `IBM Corporation`): use
  `token_set`.
- Too many false matches? Raise the threshold. Missing obvious matches? Lower it and inspect
  the `near_match` records first.
- Use `best_per_left` when list B is a lookup table that may match many rows in list A
  (VLOOKUP). Use `best_one_to_one` for a true reconciliation where each row is used once.

### FAQ and support

- **Does it scrape anything?** No. It only processes the two lists you provide — no websites,
  no logins, no personal-data collection.
- **How big can the lists be?** Matching is pairwise (list A × list B), so very large pairs
  grow in cost/time; split huge jobs into batches.
- **Found a bug or want a feature?** Use the **Issues** tab on the Actor page.

# Actor input Schema

## `leftData` (type: `string`):

The FIRST list to reconcile. Paste a JSON array of objects, CSV text (with a header row), an http(s) URL to a JSON/CSV file, or base64 content prefixed with 'base64:'. Format is auto-detected.

## `rightData` (type: `string`):

The SECOND list to reconcile against the first. Same accepted formats as the left list (JSON array, CSV text, http(s) URL, or 'base64:' content). Format is auto-detected.

## `leftKey` (type: `string`):

Column name in the left list to match on. For a composite key, list several columns comma-separated (e.g. 'first\_name,last\_name'). Values are joined before comparison.

## `rightKey` (type: `string`):

Column name in the right list to match on. Must have the same NUMBER of columns as the left key (composite keys are compared position-by-position).

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

String-similarity metric. jaro\_winkler favours matching prefixes (best for names). levenshtein is classic edit distance. token\_sort / token\_set ignore word order (good when tokens are reordered or one string is a subset).

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

Minimum similarity (0.0-1.0) for a pair to count as a MATCH. 0.85 is a good default; lower it to catch looser matches, raise it to be stricter.

## `matchStrategy` (type: `string`):

best\_one\_to\_one: each row on either side is used at most once (true reconciliation). best\_per\_left: each left row takes its best right match (right rows may repeat, like VLOOKUP). all\_above\_threshold: emit every pair at/above the threshold (many-to-many).

## `normalize` (type: `boolean`):

Fold accents, strip punctuation, collapse whitespace and lowercase key values before comparing. Strongly recommended.

## `caseSensitive` (type: `boolean`):

Keep letter case when comparing. Ignored effect when Normalize is on (normalization lowercases). Default off.

## `nearMatchMargin` (type: `number`):

How far BELOW the threshold a left row's best candidate can be and still be reported as a 'near\_match' for manual review. E.g. threshold 0.85 and margin 0.15 reports best candidates scoring 0.70-0.849.

## `includeUnmatched` (type: `boolean`):

Emit unmatched\_left and unmatched\_right records (rows with no match). Turn off to output only matched pairs.

## `includeNearMatches` (type: `boolean`):

Emit near\_match records (best candidate just below the threshold) to help you tune the threshold.

## `leftFormat` (type: `string`):

Override auto-detection of the left list format.

## `rightFormat` (type: `string`):

Override auto-detection of the right list format.

## Actor input object example

```json
{
  "leftData": "[\n  {\"company\": \"Acme Corp\", \"country\": \"USA\"},\n  {\"company\": \"Globex Inc.\", \"country\": \"USA\"},\n  {\"company\": \"Initech\", \"country\": \"USA\"}\n]",
  "rightData": "[\n  {\"name\": \"Acme Corporation\", \"revenue\": 1200000},\n  {\"name\": \"Globex\", \"revenue\": 890000},\n  {\"name\": \"Umbrella LLC\", \"revenue\": 450000}\n]",
  "leftKey": "company",
  "rightKey": "name",
  "algorithm": "jaro_winkler",
  "threshold": 0.85,
  "matchStrategy": "best_one_to_one",
  "normalize": true,
  "caseSensitive": false,
  "nearMatchMargin": 0.15,
  "includeUnmatched": true,
  "includeNearMatches": true,
  "leftFormat": "auto",
  "rightFormat": "auto"
}
```

# 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 = {
    "leftData": `[
  {"company": "Acme Corp", "country": "USA"},
  {"company": "Globex Inc.", "country": "USA"},
  {"company": "Initech", "country": "USA"}
]`,
    "rightData": `[
  {"name": "Acme Corporation", "revenue": 1200000},
  {"name": "Globex", "revenue": 890000},
  {"name": "Umbrella LLC", "revenue": 450000}
]`,
    "leftKey": "company",
    "rightKey": "name",
    "threshold": 0.85,
    "nearMatchMargin": 0.15
};

// Run the Actor and wait for it to finish
const run = await client.actor("nibble/list-fuzzy-reconciler").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 = {
    "leftData": """[
  {\"company\": \"Acme Corp\", \"country\": \"USA\"},
  {\"company\": \"Globex Inc.\", \"country\": \"USA\"},
  {\"company\": \"Initech\", \"country\": \"USA\"}
]""",
    "rightData": """[
  {\"name\": \"Acme Corporation\", \"revenue\": 1200000},
  {\"name\": \"Globex\", \"revenue\": 890000},
  {\"name\": \"Umbrella LLC\", \"revenue\": 450000}
]""",
    "leftKey": "company",
    "rightKey": "name",
    "threshold": 0.85,
    "nearMatchMargin": 0.15,
}

# Run the Actor and wait for it to finish
run = client.actor("nibble/list-fuzzy-reconciler").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 '{
  "leftData": "[\\n  {\\"company\\": \\"Acme Corp\\", \\"country\\": \\"USA\\"},\\n  {\\"company\\": \\"Globex Inc.\\", \\"country\\": \\"USA\\"},\\n  {\\"company\\": \\"Initech\\", \\"country\\": \\"USA\\"}\\n]",
  "rightData": "[\\n  {\\"name\\": \\"Acme Corporation\\", \\"revenue\\": 1200000},\\n  {\\"name\\": \\"Globex\\", \\"revenue\\": 890000},\\n  {\\"name\\": \\"Umbrella LLC\\", \\"revenue\\": 450000}\\n]",
  "leftKey": "company",
  "rightKey": "name",
  "threshold": 0.85,
  "nearMatchMargin": 0.15
}' |
apify call nibble/list-fuzzy-reconciler --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=nibble/list-fuzzy-reconciler",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/qapOsOzVRuBhH3mIP/builds/WbOQ3ulN1O3VLQtIU/openapi.json
