# Suppression List Import Conflict Report (`h_murdock/suppression-import-conflicts`) Actor

Compare audience-import keys with supplied global and audience suppression records. Trace supplied blocks, duplicate identities and no-match scope coverage. Mapped CSV or JSON; downloadable evidence reports; no live import or consent inference.

- **URL**: https://apify.com/h_murdock/suppression-import-conflicts.md
- **Developed by:** [Gilad Ronen](https://apify.com/h_murdock) (community)
- **Categories:** Marketing, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.25 / completed report

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

### What does the Suppression List Import Conflict Report do?

Compare intended audience-import rows with **supplied global and audience suppression evidence** before an import or cross-CRM handoff. Paste JSON arrays or mapped CSV exports and receive a source-linked report for human review. The Actor uses exact caller-defined contact keys and audience IDs. Already pseudonymized, consistent keys are recommended.

A result called `no-supplied-match` means only that no applicable record appeared in the supplied evidence. It does not establish consent, sendability, eligibility, lawfulness, or verified permission. Missing, partial, or ambiguous scope coverage remains unknown. Every candidate result requires review before an operator makes an import decision.

### Why use a suppression import conflict report?

An operations team may hold a candidate export, a shared suppression export, and separate audience exclusion files. This report connects those supplied records while preserving each declared scope. A global record applies to every audience for the same exact key. An audience record applies only to its exact audience ID. It also exposes repeated candidate row IDs, repeated contact/audience identities, repeated suppression IDs, and contradictory scope coverage declarations. Nothing is silently overwritten.

Mailchimp’s [suppression import documentation](https://mailchimp.com/help/import-suppression-lists/) describes separate audience scopes and an existing native workflow. This Actor is useful for comparing exports across systems; it does not operate that workflow, change contact status, or perform a live import. It does not send email, scrape websites, verify addresses, normalize actual email addresses, infer consent, or provide legal compliance advice.

### How to prepare an import review

1. Create a consistent opaque `contactKey` across the candidate and suppression exports. Preserve exact strings, including leading zeros and case.
2. Supply candidate rows with `rowId`, `contactKey`, and `audienceId`.
3. Supply suppression rows with `recordId`, `contactKey`, `scope`, `audienceId`, `reason`, and `source`. Use `scope:"global"` with `audienceId:""`, or `scope:"audience"` with a nonempty exact audience ID.
4. Declare coverage separately for the global suppression scope and each relevant audience scope. Coverage rows require `coverageId`, `scope`, `audienceId`, `status`, and `source`. Choose `complete`, `partial`, or `unknown` based on the supplied export. An empty coverage table is valid and yields unknown coverage.
5. Run once and review the candidate table, findings, identity groups, and scope coverage before deciding what to do in your CRM.

### Input and CSV mapping

See the input tab for the full configuration. `snapshotId`, `matchingPolicy:"exact-keys"`, and `noMatchPolicy:"evidence-only"` are explicit required choices. Each of the three tables must be supplied as either an array or nonempty CSV. Use `[]` for a legitimate empty table. Remove its array when using CSV. A header-only CSV represents an empty table.

CSV accepts UTF-8 BOMs, quoted commas, and quoted multiline text. Canonical headers match the field names above. Optional `candidatesColumns`, `suppressionsColumns`, and `coverageColumns` map every canonical field to a distinct export header. Unmapped columns, duplicate headers, irregular rows, unknown JSON fields, invalid scopes, blank IDs, and surrounding ID whitespace are rejected. Source references use one-based array positions or logical CSV record numbers, including the header.

Capacity is **5,000 combined input rows**, **4 MB input JSON**, and **20,000 candidate/suppression evidence links**. Excessive evidence amplification is rejected without truncation. Split larger exports into separate reviews, retaining the relevant suppression and coverage evidence for each batch.

### Output and interpretation

| Field | Meaning |
| --- | --- |
| `status` | `blocked-by-supplied-evidence` or `no-supplied-match` |
| `coverageStatus` | Complete only when both relevant scopes have a single complete caller declaration; otherwise unknown |
| `globalCoverage`, `audienceCoverage` | Each supplied scope’s complete, partial, or unknown coverage |
| `identityStatus` | Unique, duplicate, or conflicting candidate identity |
| `suppressionEvidenceIds`, `sourceRefs` | Links back to retained supplied evidence |

One dataset item contains the full report and its `rows` array. Download full JSON (`OUTPUT`), flat candidate CSV (`evidence.csv`), findings CSV (`findings.csv`), and standalone HTML (`report.html`) from the output links. CSV cells neutralize spreadsheet formulas; HTML escapes supplied text and includes no external scripts. The full JSON stays below 8 MB and each export below 9 MB, checked before charging.

### Price, storage, and repeat runs

The price is **$0.25 per completed report**, with platform usage included and no startup or dataset-item fee. Invalid input, insufficient budget, or exceeded output limits fail before the report event is charged. Each new run is a new report and may be billed again. Resuming the same run reuses an intact saved report and one report charge. A failed run can leave partial uncharged export files; resuming rewrites them before completing delivery.

Only process data you are authorized to use. Input and result files remain in Apify run storage and may include identifiers if you supply them. Review your run storage and access settings before uploading exports. Use the API tab for programmatic runs and the Issues tab for questions or reproducible problems. This report cannot determine whether your exports are complete, current, accurate, or lawful to use; coverage remains your declaration.

# Actor input Schema

## `snapshotId` (type: `string`):

Caller-defined snapshot identifier. Prefer opaque strings; keys preserve leading zeros and exact case.

## `matchingPolicy` (type: `string`):

Explicit exact literal contactKey and audienceId comparisons; no normalization, inferred email aliases or case folding.

## `noMatchPolicy` (type: `string`):

No supplied match is only absence in supplied evidence; never consent, sendable, eligible, lawful or verified permission. Missing/partial scope coverage remains unknown.

## `candidates` (type: `array`):

Records: rowId, contactKey, audienceId, all literal nonempty strings. Duplicate row IDs and duplicate contact/audience identities are preserved and reported. Use already pseudonymized consistent keys. Supply this array, including \[] when empty, OR CSV. Combined maximum 5,000 rows and 4 MB input JSON; at most 20,000 candidate/suppression links.

## `candidatesCsv` (type: `string`):

Alternative to candidates: remove the array when providing nonempty CSV. Exact canonical headers unless mapped. UTF-8 BOM and quoted/multiline fields accepted; irregular rows, duplicate/empty/unmapped headers rejected. Header-only CSV represents an empty table.

## `candidatesColumns` (type: `object`):

Optional mapping of every canonical field (rowId, contactKey, audienceId) to a distinct header. Canonical mapping may remain with arrays; a custom mapping requires CSV.

## `suppressions` (type: `array`):

Records: recordId, contactKey, scope (global or audience), audienceId, reason, source. Global requires audienceId:""; audience requires an exact nonempty audienceId. All supplied valid matches remain blocking. Duplicate/conflicting evidence is retained. Supply this array, including \[] when empty, OR CSV. Combined maximum 5,000 rows and 4 MB input JSON; at most 20,000 candidate/suppression links.

## `suppressionsCsv` (type: `string`):

Alternative to suppressions: remove the array when providing nonempty CSV. Exact canonical headers unless mapped. UTF-8 BOM and quoted/multiline fields accepted; irregular rows, duplicate/empty/unmapped headers rejected. Header-only CSV represents an empty table.

## `suppressionsColumns` (type: `object`):

Optional mapping of every canonical field (recordId, contactKey, scope, audienceId, reason, source) to a distinct header. Canonical mapping may remain with arrays; a custom mapping requires CSV.

## `coverage` (type: `array`):

Records: coverageId, scope (global or audience), audienceId, status (complete, partial, unknown), source. Global requires audienceId:""; audience requires exact nonempty audienceId. Declare global and each relevant audience separately. \[] is accepted and means coverage unknown. Repeated declarations for a scope remain unknown. Supply this array, including \[] when empty, OR CSV. Combined maximum 5,000 rows and 4 MB input JSON; at most 20,000 candidate/suppression links.

## `coverageCsv` (type: `string`):

Alternative to coverage: remove the array when providing nonempty CSV. Exact canonical headers unless mapped. UTF-8 BOM and quoted/multiline fields accepted; irregular rows, duplicate/empty/unmapped headers rejected. Header-only CSV represents an empty table.

## `coverageColumns` (type: `object`):

Optional mapping of every canonical field (coverageId, scope, audienceId, status, source) to a distinct header. Canonical mapping may remain with arrays; a custom mapping requires CSV.

## Actor input object example

```json
{
  "snapshotId": "import-review-001",
  "matchingPolicy": "exact-keys",
  "noMatchPolicy": "evidence-only",
  "candidates": [
    {
      "rowId": "0001",
      "contactKey": "opaque-alpha",
      "audienceId": "audience-A"
    },
    {
      "rowId": "0002",
      "contactKey": "opaque-beta",
      "audienceId": "audience-B"
    },
    {
      "rowId": "0003",
      "contactKey": "opaque-gamma",
      "audienceId": "audience-A"
    },
    {
      "rowId": "0003",
      "contactKey": "opaque-delta",
      "audienceId": "audience-B"
    },
    {
      "rowId": "0005",
      "contactKey": "opaque-gamma",
      "audienceId": "audience-A"
    }
  ],
  "candidatesColumns": {
    "rowId": "rowId",
    "contactKey": "contactKey",
    "audienceId": "audienceId"
  },
  "suppressions": [
    {
      "recordId": "supp-001",
      "contactKey": "opaque-alpha",
      "scope": "global",
      "audienceId": "",
      "reason": "Caller supplied unsubscribe evidence",
      "source": "CRM export row 12"
    },
    {
      "recordId": "supp-001",
      "contactKey": "opaque-alpha",
      "scope": "audience",
      "audienceId": "audience-A",
      "reason": "Audience exclusion evidence",
      "source": "Audience export row 7"
    },
    {
      "recordId": "supp-003",
      "contactKey": "opaque-beta",
      "scope": "audience",
      "audienceId": "audience-A",
      "reason": "Audience-specific exclusion",
      "source": "Audience export row 8"
    }
  ],
  "suppressionsColumns": {
    "recordId": "recordId",
    "contactKey": "contactKey",
    "scope": "scope",
    "audienceId": "audienceId",
    "reason": "reason",
    "source": "source"
  },
  "coverage": [
    {
      "coverageId": "cov-global",
      "scope": "global",
      "audienceId": "",
      "status": "complete",
      "source": "Operator declares entire global suppression export"
    },
    {
      "coverageId": "cov-A",
      "scope": "audience",
      "audienceId": "audience-A",
      "status": "complete",
      "source": "Operator declares entire audience-A suppression export"
    },
    {
      "coverageId": "cov-B",
      "scope": "audience",
      "audienceId": "audience-B",
      "status": "partial",
      "source": "Only one of two audience-B suppression sources exported"
    }
  ],
  "coverageColumns": {
    "coverageId": "coverageId",
    "scope": "scope",
    "audienceId": "audienceId",
    "status": "status",
    "source": "source"
  }
}
```

# Actor output Schema

## `dataset` (type: `string`):

No description

## `json` (type: `string`):

No description

## `evidence` (type: `string`):

No description

## `findings` (type: `string`):

No description

## `html` (type: `string`):

Escaped standalone report; download and open locally.

# 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 = {
    "snapshotId": "import-review-001",
    "matchingPolicy": "exact-keys",
    "noMatchPolicy": "evidence-only",
    "candidates": [
        {
            "rowId": "0001",
            "contactKey": "opaque-alpha",
            "audienceId": "audience-A"
        },
        {
            "rowId": "0002",
            "contactKey": "opaque-beta",
            "audienceId": "audience-B"
        },
        {
            "rowId": "0003",
            "contactKey": "opaque-gamma",
            "audienceId": "audience-A"
        },
        {
            "rowId": "0003",
            "contactKey": "opaque-delta",
            "audienceId": "audience-B"
        },
        {
            "rowId": "0005",
            "contactKey": "opaque-gamma",
            "audienceId": "audience-A"
        }
    ],
    "candidatesCsv": "",
    "candidatesColumns": {
        "rowId": "rowId",
        "contactKey": "contactKey",
        "audienceId": "audienceId"
    },
    "suppressions": [
        {
            "recordId": "supp-001",
            "contactKey": "opaque-alpha",
            "scope": "global",
            "audienceId": "",
            "reason": "Caller supplied unsubscribe evidence",
            "source": "CRM export row 12"
        },
        {
            "recordId": "supp-001",
            "contactKey": "opaque-alpha",
            "scope": "audience",
            "audienceId": "audience-A",
            "reason": "Audience exclusion evidence",
            "source": "Audience export row 7"
        },
        {
            "recordId": "supp-003",
            "contactKey": "opaque-beta",
            "scope": "audience",
            "audienceId": "audience-A",
            "reason": "Audience-specific exclusion",
            "source": "Audience export row 8"
        }
    ],
    "suppressionsCsv": "",
    "suppressionsColumns": {
        "recordId": "recordId",
        "contactKey": "contactKey",
        "scope": "scope",
        "audienceId": "audienceId",
        "reason": "reason",
        "source": "source"
    },
    "coverage": [
        {
            "coverageId": "cov-global",
            "scope": "global",
            "audienceId": "",
            "status": "complete",
            "source": "Operator declares entire global suppression export"
        },
        {
            "coverageId": "cov-A",
            "scope": "audience",
            "audienceId": "audience-A",
            "status": "complete",
            "source": "Operator declares entire audience-A suppression export"
        },
        {
            "coverageId": "cov-B",
            "scope": "audience",
            "audienceId": "audience-B",
            "status": "partial",
            "source": "Only one of two audience-B suppression sources exported"
        }
    ],
    "coverageCsv": "",
    "coverageColumns": {
        "coverageId": "coverageId",
        "scope": "scope",
        "audienceId": "audienceId",
        "status": "status",
        "source": "source"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("h_murdock/suppression-import-conflicts").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 = {
    "snapshotId": "import-review-001",
    "matchingPolicy": "exact-keys",
    "noMatchPolicy": "evidence-only",
    "candidates": [
        {
            "rowId": "0001",
            "contactKey": "opaque-alpha",
            "audienceId": "audience-A",
        },
        {
            "rowId": "0002",
            "contactKey": "opaque-beta",
            "audienceId": "audience-B",
        },
        {
            "rowId": "0003",
            "contactKey": "opaque-gamma",
            "audienceId": "audience-A",
        },
        {
            "rowId": "0003",
            "contactKey": "opaque-delta",
            "audienceId": "audience-B",
        },
        {
            "rowId": "0005",
            "contactKey": "opaque-gamma",
            "audienceId": "audience-A",
        },
    ],
    "candidatesCsv": "",
    "candidatesColumns": {
        "rowId": "rowId",
        "contactKey": "contactKey",
        "audienceId": "audienceId",
    },
    "suppressions": [
        {
            "recordId": "supp-001",
            "contactKey": "opaque-alpha",
            "scope": "global",
            "audienceId": "",
            "reason": "Caller supplied unsubscribe evidence",
            "source": "CRM export row 12",
        },
        {
            "recordId": "supp-001",
            "contactKey": "opaque-alpha",
            "scope": "audience",
            "audienceId": "audience-A",
            "reason": "Audience exclusion evidence",
            "source": "Audience export row 7",
        },
        {
            "recordId": "supp-003",
            "contactKey": "opaque-beta",
            "scope": "audience",
            "audienceId": "audience-A",
            "reason": "Audience-specific exclusion",
            "source": "Audience export row 8",
        },
    ],
    "suppressionsCsv": "",
    "suppressionsColumns": {
        "recordId": "recordId",
        "contactKey": "contactKey",
        "scope": "scope",
        "audienceId": "audienceId",
        "reason": "reason",
        "source": "source",
    },
    "coverage": [
        {
            "coverageId": "cov-global",
            "scope": "global",
            "audienceId": "",
            "status": "complete",
            "source": "Operator declares entire global suppression export",
        },
        {
            "coverageId": "cov-A",
            "scope": "audience",
            "audienceId": "audience-A",
            "status": "complete",
            "source": "Operator declares entire audience-A suppression export",
        },
        {
            "coverageId": "cov-B",
            "scope": "audience",
            "audienceId": "audience-B",
            "status": "partial",
            "source": "Only one of two audience-B suppression sources exported",
        },
    ],
    "coverageCsv": "",
    "coverageColumns": {
        "coverageId": "coverageId",
        "scope": "scope",
        "audienceId": "audienceId",
        "status": "status",
        "source": "source",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("h_murdock/suppression-import-conflicts").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 '{
  "snapshotId": "import-review-001",
  "matchingPolicy": "exact-keys",
  "noMatchPolicy": "evidence-only",
  "candidates": [
    {
      "rowId": "0001",
      "contactKey": "opaque-alpha",
      "audienceId": "audience-A"
    },
    {
      "rowId": "0002",
      "contactKey": "opaque-beta",
      "audienceId": "audience-B"
    },
    {
      "rowId": "0003",
      "contactKey": "opaque-gamma",
      "audienceId": "audience-A"
    },
    {
      "rowId": "0003",
      "contactKey": "opaque-delta",
      "audienceId": "audience-B"
    },
    {
      "rowId": "0005",
      "contactKey": "opaque-gamma",
      "audienceId": "audience-A"
    }
  ],
  "candidatesCsv": "",
  "candidatesColumns": {
    "rowId": "rowId",
    "contactKey": "contactKey",
    "audienceId": "audienceId"
  },
  "suppressions": [
    {
      "recordId": "supp-001",
      "contactKey": "opaque-alpha",
      "scope": "global",
      "audienceId": "",
      "reason": "Caller supplied unsubscribe evidence",
      "source": "CRM export row 12"
    },
    {
      "recordId": "supp-001",
      "contactKey": "opaque-alpha",
      "scope": "audience",
      "audienceId": "audience-A",
      "reason": "Audience exclusion evidence",
      "source": "Audience export row 7"
    },
    {
      "recordId": "supp-003",
      "contactKey": "opaque-beta",
      "scope": "audience",
      "audienceId": "audience-A",
      "reason": "Audience-specific exclusion",
      "source": "Audience export row 8"
    }
  ],
  "suppressionsCsv": "",
  "suppressionsColumns": {
    "recordId": "recordId",
    "contactKey": "contactKey",
    "scope": "scope",
    "audienceId": "audienceId",
    "reason": "reason",
    "source": "source"
  },
  "coverage": [
    {
      "coverageId": "cov-global",
      "scope": "global",
      "audienceId": "",
      "status": "complete",
      "source": "Operator declares entire global suppression export"
    },
    {
      "coverageId": "cov-A",
      "scope": "audience",
      "audienceId": "audience-A",
      "status": "complete",
      "source": "Operator declares entire audience-A suppression export"
    },
    {
      "coverageId": "cov-B",
      "scope": "audience",
      "audienceId": "audience-B",
      "status": "partial",
      "source": "Only one of two audience-B suppression sources exported"
    }
  ],
  "coverageCsv": "",
  "coverageColumns": {
    "coverageId": "coverageId",
    "scope": "scope",
    "audienceId": "audienceId",
    "status": "status",
    "source": "source"
  }
}' |
apify call h_murdock/suppression-import-conflicts --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,h_murdock/suppression-import-conflicts"
        }
    }
}
```

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/WXfFCUCoSyaeFj1qK/builds/USkkm7Q7NrdQYFb4M/openapi.json
