# B2B Company Identity & CRM Enrichment (`afkdigital/api25-b2b-company-identity-crm-enrichment`) Actor

Match company-only CRM records to official GLEIF LEIs, with confidence scores, source links, and a manual-review queue for ambiguous identities. Independent tool; not affiliated with or endorsed by GLEIF. Not credit-risk or full ownership verification.

- **URL**: https://apify.com/afkdigital/api25-b2b-company-identity-crm-enrichment.md
- **Developed by:** [AFK Digital](https://apify.com/afkdigital) (community)
- **Categories:** Lead generation
- **Stats:** 2 total users, 1 monthly users, 81.8% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$4.00 / 1,000 verified crm-to-lei matches

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

## B2B Company Identity & CRM Enrichment

**Reconcile company-only CRM rows with official LEI identities.** Built for B2B sales, finance, vendor onboarding, and procurement data quality.

This is an **independent software service** using public GLEIF LEI data. It is NOT provided, authorized, supported, sponsored, or endorsed by GLEIF.

### Features

- Accept a customer-authorized CSV/JSON file uploaded to Apify, or company-only JSON rows.
- Resolve existing LEIs or search official GLEIF legal entity data by company name and country.
- Compare legal names, common suffix variants, legal-address country, and optional business-address evidence.
- Send uncertain cases to a separate manual review queue instead of claiming an exact match.
- Return CRM row ID, candidate LEI, official legal name, country, jurisdiction, registration/entity status, confidence score, match method, and official source URL.
- Optionally include GLEIF-reported direct/ultimate consolidated parent LEIs and direct child LEIs.
- Never claim a credit-risk assessment or complete beneficial-ownership determination.

### Input

Supply exactly one source: **fileUrl** OR **records**.

For fileUrl, upload a UTF-8 CSV or JSON using Apify's file-upload input widget. The Actor accepts only an authorized direct HTTPS Apify key-value-store record URL under api.apify.com, not arbitrary download URLs. Max file size: 2 MB.

For records, pass a JSON array of company-only objects. Examples are in the examples directory.

Allowed company-only columns: rowId, companyName, country, address, lei. Common capitalization and CRM column aliases are accepted in uploads. Company name or LEI is mandatory; row ID is generated when absent.

Do NOT include email, phone, personal contact names, tax IDs, or unrelated CRM fields. Unexpected columns cause a validation error. Country uses ISO-3166 alpha-2; UK is normalized to GB.

**matchThreshold:** 0.80–0.99, default 0.93. This heuristic score is not a statistical probability.

**resultLimit:** process 1–250 rows, default 100; submitted file max 500 rows.

**includeRelationships:** false by default; enabling it requests additional official GLEIF relationship resources. The direct-child list is capped at 10.

### Results

The default dataset produces exactly one result for each processed CRM row:

- matched: unique, above-threshold LEI identity accepted, with no detected conflicting evidence.
- review: plausible but ambiguous; competitor within the confidence gap, truncated search, weak name evidence, or address/country conflict.
- not_found: no LEI candidate, no official LEI found, or insufficient name evidence.
- invalid_row: invalid LEI check digits (no API call).
- api_error: official API/network error (no successful match).

The REVIEW_QUEUE key-value record includes review rows with their top candidate LEIs, names, scores, and reasons. The SUMMARY key-value record contains only aggregate counts. Link outputs are declared in the Apify output schema.

#### Verified sample output

These sample entries were taken from a real successful Apify cloud run (dataset HiiXnW7E0WNpqZb3R). All input rows are preserved, whether matched or not.

| CRM row | Result | Official candidate | LEI | Confidence |
|---|---|---|---|---|
| DEMO-001 | matched | Apple Inc. | HWUPKR0MPOU8FGXBT394 | 0.995 |
| DEMO-002 | review (ambiguous) | BLUE HORIZON | 969500JULJOR90MKFC51 | 0.970 |
| DEMO-003 | not_found | none | none | 0.000 |
| DEMO-004 | matched | Apple Inc. | HWUPKR0MPOU8FGXBT394 | 1.000 |

For the complete machine-readable dataset example, see `examples/sample_output.json` in the Actor source package. The review candidate is **not** a verified match and must be checked by a person. Source records may change as GLEIF updates its public LEI data.

The confidence score is a transparent rule-based matching aid, not proof that the entity is your contracting counterparty. Even matching LEIs can be expired/lapsed or refer to inactive entities; always inspect registrationStatus and entityStatus.

Country filtering uses the official GLEIF legal-address-country field, while jurisdiction is reported separately. Address comparison occurs locally; only company name/country or a given LEI are transmitted to GLEIF.

### GLEIF limits and source integrity

Official GLEIF LEI API: https://www.gleif.org/en/lei-data/gleif-api

GLEIF data terms / CC0: https://www.gleif.org/en/meta/lei-data-terms-of-use

GLEIF provides free public LEI data under CC0, but its service and trademarks have separate conditions. Do not imply GLEIF endorsement or association. The data is provided AS IS and can be incomplete, stale, or inaccurate.

The Actor sends requests only to the official api.gleif.org /api/v1/lei-records resources. It performs sequential requests at most approximately once per second, with bounded retries on HTTP 429 and 5xx. Retry-After is respected; for excessive back-off it fails safely instead of evading restrictions. Search checks at most two pages of 50 records per row; a truncated search is always routed to review. No scraping, proxy rotation, or authentication bypass.

Relationships are specifically reported LEI accounting-consolidation relationships; they neither verify all beneficial owners nor establish the entire corporate structure. Missing relationship endpoints may mean unavailable reporting, not no ownership.

### Privacy

This Actor needs only the company identity fields listed above. It does not intentionally read, log, return, or transmit person-level CRM fields. Files uploaded into Apify storage and output datasets remain subject to the user's Apify storage settings and retention policies. Do not place secret tokens or personally identifying contact data in company columns.

### Fair pay-per-event billing

The only custom billable event is **successful-match**. The code calls Actor.charge once per matched input row, and ONLY AFTER its result has been saved to the dataset. Review, not_found, invalid_row and api_error rows do not invoke Actor.charge.

On free/private runs the SDK detects that pay-per-event is not active and creates no custom charges. As soon as Apify reports the correct sole PPE event, charging activates automatically; any unexpected event or price exceeding $0.004 fails closed.

**Mandatory Apify Console setup before paid Store publication:**

1. Choose Pay per event; create the custom event named successful-match and set its price and primary-event designation.
2. REMOVE or DISABLE BOTH synthetic events: apify-actor-start and apify-default-dataset-item. Their automatic charges would violate the promise of no fees on failed/unmatched rows.
3. Keep Pay per event + usage switched OFF. Otherwise runtime usage can produce fees even without matching rows.
4. Confirm marketplace permissions, publisher payout setup and current Store terms.
5. The SDK automatically enables custom-event billing only if the platform's event list contains exactly successful-match, at a positive effective event price not exceeding $0.004; no environment toggle is necessary.
6. Verify with live Apify runs (matched, review, no result, API failure, and zero-result run) and confirm itemized charges in the Console.

This is a charge for **our matching/processing service**, not sale of exclusive rights in the underlying CC0 GLEIF records. The proposed competitive price is $0.004 per accepted CRM row (USD $4.00 per 1,000 matches). The Store price remains unconfigured until the monetization settings are saved in Apify.

Official monetization documentation:
https://docs.apify.com/actors/publishing/monetize/pay-per-event
https://docs.apify.com/actors/monetize/set-up-monetization

### Reproducible local tests

From /opt/APIFY/API25, use:

```
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
.venv/bin/python -m unittest discover -s tests -p 'test_*.py' -v
.venv/bin/python -m tests.live_checks
```

The first command sequence runs offline unit tests; the second touches only the official GLEIF API and writes reports/live_test_report.json.

See examples/run_input.json for an Apify-compatible example input and examples/demo_companies.csv for an upload sample. Container entrypoint: python -m src.main. Dockerfile and Apify Actor input, dataset and output schemas are in .actor/.

### Intended use boundaries

Results support CRM identity reconciliation, not credit risk, sanctions screening, guaranteed KYC, verification of all subsidiaries, or ownership proof. Human review of ambiguous company names is required. This service is independent of GLEIF.

# Actor input Schema

## `fileUrl` (type: `string`):

Upload a UTF-8 CSV or JSON file to Apify key-value storage. Only direct api.apify.com key-value-store record URLs are accepted; max 2 MB; one source only.

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

Use instead of file upload. Objects may contain ONLY rowId, companyName, country, address, lei; max 500 company rows.

## `matchThreshold` (type: `number`):

Only a unique candidate at or above this confidence score can be an accepted match; competing candidates/address conflict always require review.

## `resultLimit` (type: `integer`):

Maximum number of customer CRM rows processed per run; never more than 250.

## `includeRelationships` (type: `boolean`):

Optionally include available direct/ultimate consolidated parent LEIs and direct child LEIs (up to 10). These are NOT all beneficial owners.

## Actor input object example

```json
{
  "records": [
    {
      "rowId": "DEMO-001",
      "companyName": "Apple Inc.",
      "country": "US"
    },
    {
      "rowId": "DEMO-002",
      "companyName": "Blue Horizon"
    }
  ],
  "matchThreshold": 0.93,
  "resultLimit": 100,
  "includeRelationships": false
}
```

# Actor output Schema

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

No description

## `reviewQueue` (type: `string`):

No description

## `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 = {
    "records": [
        {
            "rowId": "DEMO-001",
            "companyName": "Apple Inc.",
            "country": "US",
            "lei": "HWUPKR0MPOU8FGXBT394"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("afkdigital/api25-b2b-company-identity-crm-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 = { "records": [{
            "rowId": "DEMO-001",
            "companyName": "Apple Inc.",
            "country": "US",
            "lei": "HWUPKR0MPOU8FGXBT394",
        }] }

# Run the Actor and wait for it to finish
run = client.actor("afkdigital/api25-b2b-company-identity-crm-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 '{
  "records": [
    {
      "rowId": "DEMO-001",
      "companyName": "Apple Inc.",
      "country": "US",
      "lei": "HWUPKR0MPOU8FGXBT394"
    }
  ]
}' |
apify call afkdigital/api25-b2b-company-identity-crm-enrichment --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,afkdigital/api25-b2b-company-identity-crm-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/yCiGhKBL2UUz0Mhl2/builds/0gBjioPlLoKFjkPAQ/openapi.json
