# Provided List Contact Subtraction (`remarkable_whipscorpion/provided-list-contact-subtraction`) Actor

Expand authorized top-level emails\[] and export unique addresses absent from your explicitly supplied email list, with bounded provenance.

- **URL**: https://apify.com/remarkable_whipscorpion/provided-list-contact-subtraction.md
- **Developed by:** [Kagerou AP](https://apify.com/remarkable_whipscorpion) (community)
- **Categories:** Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.50 / 1,000 useful unique contact exporteds

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

## Provided List Contact Subtraction

Turn authorized business-site contact arrays into unique email addresses absent from your own explicitly supplied email list. This is one bounded comparison step for agencies that already hold an Apify Dataset and a CRM email export. It retains source provenance for human review.

### Try the original demo

The default input is:

```json
{
  "mode": "demo"
}
```

The original CC0 fixture contains `sales@client-owned.example`, a blank value, a duplicate sales address and `ops@client-owned.example`. The provided list contains the sales address. The result is one useful `ops@client-owned.example` contact, with `matchedProvided=1`, `sourceBlank=1`, `duplicateDropped=1` and `unmatchedUnique=1`. The domains are reserved examples, not customer data. The demo has **$0 custom contact fees**. A configured platform start fee may still apply.

Demo mode accepts only `mode`; `{}` is equivalent to the original demo. Remove custom fields when switching back to demo. Adding customer data to demo mode is rejected.

### Use your own inputs

Set `mode` to `subtract`, set `rightsConfirmed` to `true`, and provide exactly one source: a same-account `sourceDatasetId` selected in the read-only picker, or an inline `sourceRows` array. This example uses inline data:

```json
{
  "mode": "subtract",
  "sourceRows": [
    {
      "domain": "client-owned.example",
      "originalStartUrl": "https://client-owned.example",
      "emails": [
        "sales@client-owned.example",
        " ",
        "sales@client-owned.example",
        "ops@client-owned.example"
      ]
    }
  ],
  "providedEmailsCsv": "Email\nsales@client-owned.example\n",
  "providedEmailColumns": [
    "Email"
  ],
  "comparisonPolicy": "exact",
  "rightsConfirmed": true
}
```

The useful contact in this **custom mode** is eligible for the custom contact fee, even though this example uses fictional addresses. `sample-custom-input.json` contains this input. For a Dataset, replace `sourceRows` with `sourceDatasetId` containing the Dataset ID; do not include both. The Dataset must belong to the running Apify account. The Actor reads it in bounded pages and checks metadata before and after the read. Missing ownership, inaccessible resources, incomplete pages or a changed Dataset abort the run.

`providedEmailsCsv` must be present in custom mode. An explicitly empty string or whitespace-only string means an intentionally empty provided list. A header-only CSV also has no supplied addresses. For nonempty CSV, every header named in `providedEmailColumns` must exist exactly once; the runtime default is `["Email"]`. CSV headers are exact and case-sensitive. Duplicate selected headers, bad CSV or any unusable nonblank supplied email value abort before subtraction. An omitted list is never treated as an empty list.

Include all relevant email columns, including secondary addresses. Declare `multiValueSeparator` when one cell contains several addresses; for example, `";"`. Without it, each cell is one value. There is no automatic CRM header translation or delimiter inference. CSV quoting follows RFC 4180; text values stay strings.

### Comparison and interpretation

Only strings in top-level `emails[]` arrays are read from source rows. Blank, nonstring and unusable source values are counted rather than exported. Rows without an `emails` field, or with an incorrectly shaped `emails` field, are counted. Boundary whitespace is trimmed. The default `comparisonPolicy` is `exact`; `case_insensitive_full_address` lowercases the entire address when explicitly selected. Plus tags and dots are preserved.

Each comparison key is exported once. Repeated source keys count as `duplicateDropped`; matching supplied keys count as `matchedProvided`. The retained email is the first usable trimmed value. Original email strings, domains, originalStartUrls and zero-based sourceRowRefs are combined within the provenance caps. Source domains and URLs are unverified strings, not fetched or enriched.

`decision="not_in_provided_list"` and `scope="provided_only"` mean only that the declared matching key was absent from the addresses you supplied. This does not establish mailbox validity, deliverability, consent, a new person, complete suppression coverage or permission to contact someone. Missing columns or unlisted secondary addresses cannot be inferred. Source metadata timestamps are reported where available; they do not establish when a website was collected or whether an address is current.

### Outputs

The default Dataset contains one `kind="summary"` row and zero or more `kind="contact"` rows. Summary counts distinguish source values, rejected/blank values, unique keys, matched keys, duplicates, unmatched candidates and saved contacts. Contact rows include `email`, `comparisonKey`, `decision`, `originalEmails`, `domains`, `originalStartUrls`, `sourceRowRefs`, `provenanceTruncated`, `comparisonPolicy`, `scope` and a billing reference.

The default Key-value store also contains:

- `REPORT.json`: all funded saved contacts, counts, coverage, declared policy, schema profile, generation time, source metadata, limits, truncation and custom billing state.
- `REVIEW.csv`: the same funded contacts; provenance arrays are JSON-encoded in quoted cells. Potential spreadsheet formula cells receive an apostrophe guard, so spreadsheet display text can differ from raw JSON.

The Actor Output schema links the Dataset and both files. Read `REPORT.json` and the native event ledger for confirmed charges; Dataset rows point to these and do not assert a settled fee. A link alone does not prove that a run completed.

If budget capacity limits the result, status is `budget_truncated`, and `counts.budgetSkipped` identifies skipped unmatched contacts. If the contact output cap limits it, status is `output_limit_truncated`, and `counts.outputLimitSkipped` identifies the omitted candidates. The complete report means the complete **funded saved set**, not every unmatched candidate. Coverage remains `provided_only`. The supplied list is never silently truncated.

### Price and budget

The proposed Pay-per-event price is **$0.0005 per `contact_exported` event**, one unique useful unmatched contact stored across the Dataset, REPORT.json and REVIEW.csv. There is no default Dataset-item charge and no manual custom start event. One thousand useful custom contacts cost $0.50 in custom contact events, plus a platform-managed **$0.00005 Actor start fee if configured**. This local pricing specification has not yet been applied to a native Actor.

Original demo, matched addresses, duplicate addresses, unusable source values, and invalid or failed inputs incur no custom contact fee. A run with no useful unmatched contacts has no custom contact fee. The configured platform start fee is separate. Set the maximum run charge to bound custom output: the Actor determines whole funded contact capacity before writing useful output to any storage or checkpoint and reserves startup allocation separately. A partial contact fee cannot fund a contact. Local execution outside Pay-per-event does not demonstrate native billing.

Outputs are stored before custom charging. A completed checkpoint with the same input returns the saved report without repeating output or contact events. Any incomplete checkpoint refuses automatic replay because previous output or a charge may have completed before a lost response. `pending` or `ambiguous` billing states require review; they are not confirmed charges and are not retried blindly.

### Bounds and authorization

| Bound | Maximum |
| --- | ---: |
| Source records | 10,000 |
| Email values across source | 50,000 |
| Provided CSV data rows and selected email values | 50,000 each |
| UTF-8 bytes of each source/provided side | 10 MiB each |
| CSV columns / selected headers | 200 |
| Output contacts | 10,000, further bounded by charge capacity |
| Distinct values per provenance list per contact | 20 |

Input byte, row and value limits fail before useful output; selected results are limited only by the disclosed output/budget caps. `provenanceTruncated` is true when an email, domain, URL or row-reference list exceeds its cap.

Custom mode requires authorization for the source websites and records and for the supplied CRM/email export. No Google Maps, LinkedIn, social-source or person-enrichment preset is provided. Processing uses your Actor inputs and Apify storage. No scraping, upstream Actor execution, DNS/SMTP checks, message sending, webhook, LLM, database or external contact enrichment is performed. The sole optional upstream read is the explicitly selected same-account Apify Dataset.

### Implementation and status

Native JavaScript Set/Map comparison uses Apify SDK 3.7.2 and csv-parse 7.0.2 (MIT), with pinned package and lock files. The container uses Node.js 24.22.1. The original fixture is dedicated under CC0-1.0. No third-party scraper code or customer data is included. Shipping hashes and local test evidence are maintained separately in the implementation evidence directory. Native create, push, build, run and publication remain separate gated operations.

Resource-picker and output-link shapes were checked against the official [Apify input schema specification](https://docs.apify.com/actors/development/actor-definition/input-schema/specification/v1), [Actor permissions](https://docs.apify.com/actors/development/permissions), [Dataset schema specification](https://docs.apify.com/storage/dataset-schema), and [Actor Output schema](https://docs.apify.com/actors/development/actor-definition/output-schema) on 2026-10-12. Platform build and runtime recognition must still be verified at release.

# Actor input Schema

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

demo returns one useful original example contact with no custom contact fee. subtract processes your authorized input; each useful stored unmatched contact has a custom fee.

## `sourceDatasetId` (type: `string`):

Select one Dataset owned by the same Apify account. Read-only access. Use this field OR sourceRows, never both. Supply the Dataset ID, not a name. Maximum 10,000 records and 10 MiB. Incomplete reads, inaccessible resources, changed datasets or limit failures abort.

## `sourceRows` (type: `array`):

Alternative to sourceDatasetId. JSON object rows; only top-level emails\[] strings are compared. Optional domain and originalStartUrl are retained as bounded provenance. Maximum 10,000 rows, 50,000 email values and 10 MiB in UTF-8 serialized JSON.

## `providedEmailsCsv` (type: `string`):

Required in subtract mode, even for an empty list: send an explicit empty string, or a header-only CSV. Nonempty CSV requires every exact selected header. Invalid email values or parse/read/limit failures abort rather than becoming an empty list. Maximum 50,000 rows/values, 200 columns and 10 MiB UTF-8.

## `providedEmailColumns` (type: `array`):

Exact existing CSV headers to compare; defaults at runtime to \["Email"]. Selected headers must be unique and present once. Declare all relevant primary/secondary email columns; no translated or CRM-specific header guessing.

## `multiValueSeparator` (type: `string`):

Split email cells only by this explicit separator, such as a semicolon. Omit for one address per cell. No automatic CRM delimiter detection. One to ten characters, no newline or NUL.

## `comparisonPolicy` (type: `string`):

Runtime default is exact after trimming boundary whitespace. Optional case_insensitive_full_address lowercases the entire address. Plus tags and dots are preserved. Neither policy validates a mailbox or identifies a person.

## `rightsConfirmed` (type: `boolean`):

Custom mode requires true: you own or are authorized to process the source websites, contact records and provided CRM/email export. No Google Maps, LinkedIn, social-source or person-enrichment presets. Default original demo is exempt.

## Actor input object example

```json
{
  "mode": "demo"
}
```

# Actor output Schema

## `results` (type: `string`):

Default Dataset contains one summary row plus stored contacts (kind=contact). Use REPORT.json for custom fee confirmation. Scope is provided_only.

## `report` (type: `string`):

REPORT.json contains all funded saved contacts, counts, coverage, source metadata, truncation and custom billing state. pending/ambiguous fees require review; links alone do not prove completion.

## `reviewCsv` (type: `string`):

REVIEW.csv contains the same funded contacts as REPORT.json and Dataset contact rows. Arrays are JSON-encoded and cells are guarded against spreadsheet formula execution.

# 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 = {
    "mode": "demo"
};

// Run the Actor and wait for it to finish
const run = await client.actor("remarkable_whipscorpion/provided-list-contact-subtraction").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 = { "mode": "demo" }

# Run the Actor and wait for it to finish
run = client.actor("remarkable_whipscorpion/provided-list-contact-subtraction").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 '{
  "mode": "demo"
}' |
apify call remarkable_whipscorpion/provided-list-contact-subtraction --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,remarkable_whipscorpion/provided-list-contact-subtraction"
        }
    }
}
```

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/8gl3ynlew880jZ5HT/builds/jJmzFJa7T7FT0LLGe/openapi.json
