# Phone Validator, Carrier & Compliance Decisions (`fetchfinch/phone-compliance-enrichment`) Actor

Validate and enrich phone lists with live carrier, activity, portability, DNC/TCPA, spam, fraud, and reassigned-number signals, then produce CRM-ready outreach decisions.

- **URL**: https://apify.com/fetchfinch/phone-compliance-enrichment.md
- **Developed by:** [Fetch Finch](https://apify.com/fetchfinch) (community)
- **Categories:** Lead generation, Automation, Integrations
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 open-risk phones

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?

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

## Bulk Phone Validator & FCC Complaint Risk Checker

Normalize bulk phone lists, check public government unwanted-call complaint history without API keys, optionally add live carrier data, and produce CRM-ready `ALLOW`, `REVIEW`, or `SUPPRESS` decisions for SMS and voice.

The default `open-risk` mode is immediately usable. It combines:

- E.164, national, and international formatting
- Numbering-plan validity and approximate line type
- Public FCC unwanted-call complaint counts, recency, call types, and a transparent risk score
- Consent, internal opt-out, and campaign-context policy
- Duplicate-aware lookups with one output row per CRM record
- Flat CSV-ready exports with stable reason and warning codes

Government complaint records are consumer-submitted and unverified. Caller ID may have been spoofed. They are not National Do Not Call Registry registrations and never trigger automatic suppression by themselves.

### Enrichment modes

| Mode | Credentials | Coverage |
|---|---|---|
| `open-risk` | None | Formatting, numbering metadata, and FCC complaint history for NANP numbers |
| `live-carrier` | Telnyx key | Open-risk coverage plus current carrier, portability, LRN, OCN, and provider line type |
| `offline` | None | Formatting and numbering-plan metadata only |

Optional bring-your-own-provider integrations are available. IPQS adds provider-specific fraud, activity, reputation, DNC, and TCPA-risk fields; those fields are provider signals, not direct access to the official National Do Not Call Registry. Twilio can check US number reassignment when the account is approved and the source row contains a consent verification date.

### Quick start

No credentials are required for the default mode:

```json
{
  "phoneNumbers": [
    "+14155552671",
    "+442079460958",
    "bad-number"
  ],
  "campaignType": "manual",
  "intendedChannel": "both",
  "enrichmentMode": "open-risk"
}
```

### Input

Choose exactly one source:

1. `phoneNumbers`: a quick list of strings.
2. `records`: JSON CRM records.
3. `sourceDatasetId`: an existing Apify dataset.
4. `sourceUrl`: CSV, JSON, or JSONL over HTTPS.

Example no-key CRM run:

```json
{
  "records": [
    {
      "lead_id": "crm-1042",
      "mobile": "(415) 555-2671",
      "sms_consent": "granted",
      "consent_verified_at": "2026-02-14",
      "unsubscribed": false,
      "owner": "west-team"
    }
  ],
  "phoneField": "mobile",
  "idField": "lead_id",
  "consentStatusField": "sms_consent",
  "consentDateField": "consent_verified_at",
  "optOutField": "unsubscribed",
  "passThroughFields": ["owner"],
  "campaignType": "marketing",
  "intendedChannel": "both",
  "enrichmentMode": "open-risk"
}
```

Dotted mappings such as `contact.phone` and `compliance.consent.status` are supported.

### Output

Every input row is retained, including duplicates. Paid or remote lookups are deduplicated by E.164 number within a run.

| Group | Important fields |
|---|---|
| Identity | `source_id`, `phone_raw`, `phone_e164`, `country_code` |
| Format | `format_valid`, `format_possible`, formatted variants, `metadata_line_type` |
| Complaint risk | `government_complaint_match`, `government_complaint_count`, `government_complaint_last_date`, `government_complaint_call_types`, `government_complaint_risk_score` |
| Carrier | `carrier_name`, `carrier_line_type`, `carrier_source`, `carrier_data_kind`, portability fields |
| Reachability | `active`, `active_status`, `sms_capability_estimate`, `sms_deliverable` |
| Optional provider risk | `fraud_score`, `provider_spam_flag`, `provider_dnc_match`, `provider_tcpa_risk_match` |
| Workflow | `decision`, `sms_decision`, `voice_decision`, `recommended_channel`, reason and warning codes |
| Audit | `providers_checked`, `lookup_status`, `checked_at`, `provider_errors` |

`sms_capability_estimate=likely` means the numbering metadata or live provider classified the line as mobile. It is not proof that an SMS will be delivered. The Actor does not send a test message, so `sms_deliverable` is currently always `null`.

### Complaint risk score

The Actor aggregates exact-number FCC complaint history and calculates a 0-100 score from complaint volume and recency. The method is deterministic and intentionally conservative:

- No complaint match produces score `0`.
- Recent and repeated reports increase the score.
- Older reports decay substantially.
- Scores at or above `complaintReviewThreshold` default to `REVIEW`.
- Complaint history alone never produces `SUPPRESS`.

The output includes `UNVERIFIED_GOVERNMENT_COMPLAINT_HISTORY` and `CALLER_ID_MAY_HAVE_BEEN_SPOOFED` whenever a match is found.

### Decision policy

- Invalid numbers, internal opt-outs, denied consent, confirmed inactive lines, provider DNC/TCPA-risk matches, and confirmed reassignment can suppress a row.
- Missing marketing consent, elevated complaint risk, provider failures, VoIP SMS, landline SMS, and elevated provider fraud risk go to review.
- Unknown live activity is a warning by default, because the open-risk tier does not claim to test reachability.
- SMS and voice decisions are evaluated separately.

`ALLOW` means no configured rule blocked or escalated the row. It is not a determination that contacting the number is lawful. Users remain responsible for consent, purpose, jurisdiction, calling method, official DNC access, and their own suppression lists.

### Data sources and limitations

- [FCC Consumer Complaints - Unwanted Calls](https://opendata.fcc.gov/Consumer/Consumer-Complaints-Data-Unwanted-Calls/vakf-fz8e): public, consumer-submitted complaint history. The FCC does not verify the allegations.
- [libphonenumber-js](https://github.com/catamphetamine/libphonenumber-js): formatting and numbering-plan metadata derived from Google's libphonenumber metadata. It does not confirm assignment, reachability, or the current carrier after porting.
- [Telnyx Number Lookup](https://developers.telnyx.com/docs/identity/number-lookup/quickstart): optional current carrier and portability enrichment.
- [IPQualityScore](https://www.ipqualityscore.com/documentation/phone-number-validation-api/overview): optional BYO provider risk signals.
- [Twilio Reassigned Number](https://www.twilio.com/docs/lookup/v2-api/reassigned-number): optional BYO US reassignment check requiring approval.

FCC coverage is currently queried for `+1` NANP numbers. Other countries still receive global formatting and numbering-plan metadata, with complaint coverage marked unavailable or not applicable.

In `open-risk` and `live-carrier` modes, eligible numbers are sent to the FCC Socrata open-data endpoint in batched exact-match queries. Use `offline` mode or disable `checkGovernmentComplaints` when that external disclosure is not appropriate for the input data.

### Pricing

- Actor start: $0.005 per run.
- Open-risk mode: $1.00 per 1,000 unique valid phone numbers.
- Live-carrier mode: $2.00 per 1,000 unique valid phone numbers.
- Offline mode: no per-number charge.

Invalid numbers are not charged per phone. Duplicate numbers share one lookup and per-phone charge while every source row remains in the output. BYO providers may bill their API usage separately. The pricing panel on the Actor page is the authoritative current price.

### Limits and privacy

- `sourceUrl` downloads are capped at 50 MB.
- `maxRecords` defaults to 100,000.
- Provider requests use bounded retries.
- Phone numbers and source records are personal data. Configure retention and access accordingly.

# Actor input Schema

## `phoneNumbers` (type: `array`):

Quick input for a phone-only list. For CRM fields and consent evidence, use JSON records, a dataset, or a source URL instead.

## `records` (type: `array`):

Array of CRM records. Set the field mappings below. Every source row is preserved in the output, including duplicate phone numbers.

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

Read CRM records from an existing dataset.

## `sourceUrl` (type: `string`):

HTTPS URL for a CSV with headers, a JSON array, or line-delimited JSON. Maximum response size is 50 MB.

## `phoneField` (type: `string`):

Phone property name or dotted path, for example phone, mobile\_phone, or contact.phone.

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

Optional property or dotted path copied to source\_id for lossless joins back to your CRM.

## `consentStatusField` (type: `string`):

Optional property containing granted/denied, opted\_in/opted\_out, yes/no, or equivalent values.

## `consentDateField` (type: `string`):

Optional date used for the US FCC Reassigned Number check. It should represent when consent or ownership was last verified.

## `optOutField` (type: `string`):

Optional boolean-like field. True/yes/1/opted\_out/unsubscribed always produces SUPPRESS.

## `passThroughFields` (type: `array`):

Optional fields copied into flat source\_\* output columns for CSV-ready exports.

## `preserveSource` (type: `boolean`):

Adds source\_record to every output row. Useful for JSON workflows but increases dataset size.

## `defaultCountry` (type: `string`):

ISO 3166-1 alpha-2 code used only when a number has no international calling code.

## `maxRecords` (type: `integer`):

Safety limit applied to dataset and remote-file sources.

## `intendedChannel` (type: `string`):

Controls which channel decision becomes the overall decision. Separate SMS and voice decisions are always returned.

## `campaignType` (type: `string`):

Marketing requires affirmative consent evidence under the default policy. Transactional and manual contexts still retain internal opt-out and configured risk checks.

## `missingConsentAction` (type: `string`):

Default review avoids claiming permission when a source row does not prove affirmative marketing consent.

## `unknownActivityAction` (type: `string`):

Controls the decision when the live provider cannot confirm whether the line is active.

## `voipSmsAction` (type: `string`):

VoIP lines may be text-capable but often need separate fraud or deliverability review.

## `fraudReviewThreshold` (type: `integer`):

Scores at or above this value are sent to review unless a stronger suppression rule applies.

## `fraudSuppressThreshold` (type: `integer`):

Scores at or above this value are suppressed. Must be greater than the review threshold.

## `complaintReviewThreshold` (type: `integer`):

Rows at or above this transparent complaint-risk score are sent to review. Complaint data is unverified and never causes automatic suppression by itself.

## `enrichmentMode` (type: `string`):

Open risk checks public FCC unwanted-call complaint history without credentials. Live carrier adds Telnyx current-carrier and portability data. Offline performs format and numbering-plan validation only.

## `checkGovernmentComplaints` (type: `boolean`):

Check public FCC unwanted-call complaints for NANP numbers. Numbers are sent to the FCC open-data endpoint in batched queries. Reports are unverified and are not National Do Not Call Registry registrations.

## `checkReassignedNumber` (type: `boolean`):

With live-carrier mode, Twilio credentials, a US number, and a mapped consent date, query the FCC-backed Reassigned Numbers Database.

## `ipqsStrictness` (type: `integer`):

Start at 0. Higher strictness can increase false positives.

## `concurrency` (type: `integer`):

Reduce this if your provider account has a low requests-per-second limit.

## `ipqsApiKey` (type: `string`):

Optional fraud and provider-reputation enrichment using the caller's own IPQS account.

## `telnyxApiKey` (type: `string`):

Required for live-carrier mode. Enter your own Telnyx API key.

## `twilioAccountSid` (type: `string`):

Optional Twilio account with Reassigned Number access. Used only for eligible US rows in live-carrier mode.

## `twilioAuthToken` (type: `string`):

Authentication token paired with the Twilio Account SID.

## Actor input object example

```json
{
  "phoneNumbers": [
    "+14155552671",
    "(212) 555-0187",
    "+44 20 7946 0958"
  ],
  "phoneField": "phone",
  "preserveSource": false,
  "defaultCountry": "US",
  "maxRecords": 100000,
  "intendedChannel": "both",
  "campaignType": "marketing",
  "missingConsentAction": "review",
  "unknownActivityAction": "allow",
  "voipSmsAction": "review",
  "fraudReviewThreshold": 75,
  "fraudSuppressThreshold": 90,
  "complaintReviewThreshold": 30,
  "enrichmentMode": "open-risk",
  "checkGovernmentComplaints": true,
  "checkReassignedNumber": false,
  "ipqsStrictness": 0,
  "concurrency": 20
}
```

# Actor output Schema

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

One output row per source row. Export as CSV, JSON, Excel, XML, or RSS.

## `riskReview` (type: `string`):

Records requiring human review or suppression.

## `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 = {
    "phoneNumbers": [
        "+14155552671",
        "(212) 555-0187",
        "+44 20 7946 0958"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("fetchfinch/phone-compliance-enrichment").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 = { "phoneNumbers": [
        "+14155552671",
        "(212) 555-0187",
        "+44 20 7946 0958",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("fetchfinch/phone-compliance-enrichment").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 '{
  "phoneNumbers": [
    "+14155552671",
    "(212) 555-0187",
    "+44 20 7946 0958"
  ]
}' |
apify call fetchfinch/phone-compliance-enrichment --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,fetchfinch/phone-compliance-enrichment"
        }
    }
}

```

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/Zl6MSQR1BTnVe9UlC/builds/GUgJcxstdeKkk1mng/openapi.json
