# CRM & Notion Field Filler — Enrich Records You Already Have (`deadwood_data_solutions/connector-field-filler`) Actor

Find records in Notion, HubSpot, Supabase or any MCP-connected app that are missing phone, website, owner or any other field — and fill only the blanks from an Apify dataset. Never overwrites what a human typed.

- **URL**: https://apify.com/deadwood\_data\_solutions/connector-field-filler.md
- **Developed by:** [K O](https://apify.com/deadwood_data_solutions) (community)
- **Categories:** Automation, Developer tools, Integrations
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.50 / 1,000 field filled on an existing records

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## CRM & Notion Field Filler — Enrich Records You Already Have

Your CRM is full of half-finished records. A company name and nothing else. A contact with no phone. A lead with no website.

This Actor finds those gaps and fills them — **only the blanks**, never the values a person typed in by hand.

Point it at the app your records live in (Notion, HubSpot, Supabase, Airtable, any MCP connector), point it at an Apify dataset that has the missing values, tell it which field joins the two, and it patches the difference.

### What makes it safe to run on a live CRM

- **Blank-only by default.** A field holding a value is left alone. `overwrite` exists, and is off.
- **Minimal patches.** It sends only the fields it actually filled, not the whole record.
- **No LLM.** Deterministic joins on a key you choose. Nothing is inferred, guessed, or hallucinated into your database.
- **`dryRun`** reports every record and field it *would* touch, for free, before anything is written.
- One rejected record never aborts the rest of the run.

### How it works

1. Connect your app under **Integrations** in Apify Console.
2. Run in **`list-tools`** mode to see the connector's tool names.
3. Run in **`fill`** mode.

```json
{
  "destination": "<your connector>",
  "mode": "fill",
  "readTool": "query_database",
  "readToolArgs": { "database_id": "abc123" },
  "updateTool": "update_page",
  "idField": "id",
  "idArgName": "page_id",
  "datasetId": "<dataset with the missing values>",
  "matchKey": "email",
  "fillFields": ["phone", "website", "owner"]
}
```

Credentials never reach this Actor. It talks to your app through Apify's MCP proxy, which holds the real tokens.

### Examples

**Fill missing phone numbers on CRM contacts** — `matchKey: "email"`, `fillFields: ["phone"]`, source dataset from any contact-enrichment Actor.

**Add owner names to a contractor list** — `matchKey: "licenseNumber"`, `fillFields: ["owner", "phone"]`, source from a licence-registry Actor.

**Backfill company websites in Notion** — `matchKey: "domain"`, `fillFields: ["website", "industry"]`.

### Input

| Field | Required | Description |
|---|---|---|
| `destination` | yes | MCP connector holding your records |
| `mode` | | `fill` (default) or `list-tools` |
| `readTool` | for fill | Tool that lists your existing records |
| `readToolArgs` | | JSON args for the read tool |
| `updateTool` | for fill | Tool that patches one record |
| `updateToolExtraArgs` | | Constant args merged into every update |
| `idField` | | Where the record ID lives in the read result. Default `id` |
| `idArgName` | | What the update tool calls that ID. Default `id` |
| `datasetId` | for fill | Apify dataset supplying the values |
| `matchKey` | for fill | Field joining both sides. Case/space insensitive |
| `sourceMatchKey` | | Only if the source names the join field differently |
| `fillFields` | for fill | Destination fields to complete |
| `fieldMap` | | Only if a source field is named differently or nested |
| `overwrite` | | Also replace non-blank values. Default off |
| `maxItems` | | Cap on records updated per run. Default 1000 |
| `dryRun` | | Plan only, write nothing |

### Output

One row per record handled:

```json
{ "updated": true, "key": "a@example.com", "id": "page_123",
  "filled": ["phone"], "patch": { "phone": "480-555-0100" } }
```

Failures are recorded rather than dropped:

```json
{ "updated": false, "key": "b@example.com", "id": "page_124", "error": "Tool \"update_page\" returned an error: ..." }
```

### What counts as blank

`null`, missing, `""`, whitespace, and `[]`. Note that `0` and `false` are **real values** and are not treated as gaps.

### Pricing

**$5 per 1,000 records updated.** Records that were already complete, records with no match in the source, and dry runs are all free. You pay when a record actually gained data.

### FAQ

**Does this see my Notion/HubSpot token?** No. Apify's MCP proxy holds credentials and attaches them to outbound calls.

**Will it overwrite my data?** Not unless you set `overwrite: true`. By default it only writes into empty fields.

**What if a record has no match in the source?** It's skipped and counted as `unmatched`. Nothing is written.

**Can an AI agent call this?** Yes — it's exposed through the Apify MCP server, and `list-tools` then `fill` is a natural two-step.

# Actor input Schema

## `destination` (type: `string`):

The MCP connector holding the records you want to complete. Connect the app under Integrations in Apify Console first. Credentials stay in Apify's proxy — this Actor never sees your tokens.

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

Use 'list-tools' first to discover what the connected app can do and what its tools are called. Then use 'fill'.

## `readTool` (type: `string`):

MCP tool that returns your existing records, e.g. 'query\_database' for Notion, 'select' for Supabase, 'search\_contacts' for a CRM.

## `readToolArgs` (type: `object`):

JSON arguments the read tool needs, e.g. {"database\_id": "abc123"} or {"table": "leads"}.

## `updateTool` (type: `string`):

MCP tool that patches an existing record, e.g. 'update\_page' for Notion, 'update' for Supabase. Run mode 'list-tools' if you don't know it.

## `updateToolExtraArgs` (type: `object`):

Fixed arguments merged into every update call, e.g. {"database\_id": "abc123"}.

## `idField` (type: `string`):

Where the record's own identifier lives in the read result, e.g. 'id' or 'page\_id'. Needed so updates hit the right row.

## `idArgName` (type: `string`):

What the update tool calls its identifier argument, e.g. 'page\_id' for Notion.

## `datasetId` (type: `string`):

Apify dataset supplying the values used to fill gaps — any Actor's output.

## `matchKey` (type: `string`):

Field present on both your records and the source rows, e.g. 'email', 'domain', 'licenseNumber'. Matching ignores case and surrounding spaces.

## `sourceMatchKey` (type: `string`):

Only needed when the source dataset calls the join field something else, e.g. 'workEmail' vs 'email'.

## `fillFields` (type: `array`):

Destination field names to complete, e.g. phone, website, owner. Fields that already hold a value are left alone.

## `fieldMap` (type: `object`):

Only needed when a source field has a different name or is nested, e.g. {"phone": "contact.tel"}.

## `overwrite` (type: `boolean`):

Off by default so an enrichment pass can never clobber a value someone entered by hand.

## `maxItems` (type: `integer`):

Stop after this many records have been updated.

## `dryRun` (type: `boolean`):

Report exactly which records and fields would be filled, without writing. Free, and the safest way to check your mapping.

## Actor input object example

```json
{
  "mode": "fill",
  "readToolArgs": {},
  "updateToolExtraArgs": {},
  "idField": "id",
  "idArgName": "id",
  "fillFields": [],
  "fieldMap": {},
  "overwrite": false,
  "maxItems": 1000,
  "dryRun": false
}
```

# 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 = {
    "readToolArgs": {},
    "updateToolExtraArgs": {},
    "fillFields": [],
    "fieldMap": {}
};

// Run the Actor and wait for it to finish
const run = await client.actor("deadwood_data_solutions/connector-field-filler").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 = {
    "readToolArgs": {},
    "updateToolExtraArgs": {},
    "fillFields": [],
    "fieldMap": {},
}

# Run the Actor and wait for it to finish
run = client.actor("deadwood_data_solutions/connector-field-filler").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 '{
  "readToolArgs": {},
  "updateToolExtraArgs": {},
  "fillFields": [],
  "fieldMap": {}
}' |
apify call deadwood_data_solutions/connector-field-filler --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,deadwood_data_solutions/connector-field-filler"
        }
    }
}

```

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/8FeI6aE7pbL4xjjI7/builds/3EcIvgG1YKlLAwgui/openapi.json
