# EORI Bulk Validator & Monitor (`bene123/eori-bulk-validator-monitor`) Actor

Bulk validate user-supplied EORI numbers against the official European Commission service, with normalized results, conservative retries, and optional prior-status change detection. Names and addresses are never stored or returned.

- **URL**: https://apify.com/bene123/eori-bulk-validator-monitor.md
- **Developed by:** [Ben E](https://apify.com/bene123) (community)
- **Categories:** Automation, Developer tools, E-commerce
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 conclusive eori validations

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are a software tools running on the Apify platform, for all kinds of web data extraction and automation use cases.
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.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js/docs.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python/docs.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/integrations/mcp.md).

If your project is in a different language, use 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

## EORI Bulk Validator & Monitor

An Apify Actor that checks **only EORI identifiers supplied by the user** against the official European Commission EORI validation service. It normalizes identifiers, performs privacy-conscious bulk checks, and compares current results with optional prior statuses.

This is an MVP for operational validation and monitoring workflows. It is not affiliated with or endorsed by the European Commission.

### What it returns

One dataset row per unique normalized input:

| Field | Meaning |
| --- | --- |
| `eori` | Trimmed, uppercased identifier with whitespace removed |
| `country_prefix` | First two letters, or `null` if unavailable |
| `status` | `valid`, `invalid`, or `unknown` |
| `checked_at` | ISO 8601 time when the result was produced |
| `source` | Official EC SOAP validation endpoint |
| `previous_status` | Optional normalized prior status |
| `status_change` | `new`, `unchanged`, `changed`, or `not_comparable` |
| `input_occurrences` | How often the normalized identifier appeared in this run |
| `attempts` | Number of official HTTP attempts; malformed local input has `0` |
| `error_code`, `error_message` | Safe diagnostic fields for inconclusive results |

`changed` is deliberately limited to a conclusive `valid` ↔ `invalid` transition. Any comparison involving `unknown` is `not_comparable`, which avoids false alerts during an EC outage.

### Privacy by design

The EC response contract can include registrant name and address fields. This Actor **intentionally discards those fields**. They are not part of the TypeScript result type, dataset schema, summary, or logs—even when the upstream response contains them.

- Submit only EORI numbers you are authorized to process.
- Do not submit names, addresses, notes, or other personal data in the EORI input.
- Configure Apify retention and deletion to match your own legal obligations.
- Do not use a result as the sole basis for a legal, customs, credit, employment, or other consequential decision.

### Input

```json
{
  "eoriNumbers": ["IE6388047V", "DE000000000000000"],
  "priorStatuses": [
    { "eori": "IE6388047V", "status": "valid" }
  ],
  "maxConcurrency": 1,
  "minDelayMs": 1000,
  "maxRetries": 2,
  "requestTimeoutSecs": 20
}
````

Limits:

- 1–1,000 supplied values per run
- 1–4 concurrent tasks
- at least 500 ms between all official request starts
- up to 5 retries, only for timeout/network errors, HTTP 408/429, and 5xx responses

The conservative defaults are intentional. The official WSDL states that EORI count, message size, and request frequency are limited, but does not publish numeric limits. This Actor therefore sends one normalized identifier per SOAP request and globally spaces every attempt.

The EC's service announcement publishes a ceiling of **10 EORI numbers per request and 100 requests per second**. This MVP intentionally stays far below it: one EORI per request and, by default, one request start per second.

### Normalization and status rules

Inputs are Unicode NFKC-normalized, trimmed, stripped of whitespace, and uppercased. A value must then match two ASCII letters followed by 1–15 alphanumeric characters. A malformed value is returned as `unknown` with `INVALID_FORMAT`; it is never mislabeled `invalid` because no official lookup occurred.

For official responses:

- EC status `0` / description `Valid` → `valid`
- EC status `1` / description `Not valid` → `invalid`
- service failure, timeout, SOAP fault, malformed response, or unrecognized status → `unknown`

Duplicate normalized values are checked once within a run. One output row is returned with `input_occurrences` showing the duplicate count.

### Monitoring pattern

Run the Actor on an Apify schedule and feed the previous run's `eori` + `status` pairs into `priorStatuses`. Alert only when `status_change` is `changed`; treat `unknown` as a service-health condition to retry later.

The MVP does not send email, webhook, or social messages by itself. That avoids hidden outbound side effects and lets the caller choose its own alert destination.

### Pay-per-event monetization

The Actor includes native Apify PPE event definitions and hooks:

- `apify-actor-start`: **$0.00005** once per run, charged automatically by Apify
- `eori-validation`: **$0.002** for each conclusive `valid` or `invalid` dataset row

The code does not manually charge the automatic Actor-start event. Malformed identifiers and `unknown` results caused by upstream failures are returned to the dataset but do **not** trigger `eori-validation`. Consecutive rows with the same charging treatment are pushed in batches while preserving normalized input order. Apify's SDK treats the hooks as no-ops for billing when the Actor is run locally or under a non-PPE pricing model, while still storing all rows.

### Local development

Requirements: Node.js 22+ and npm.

```bash
npm ci
npm run check
```

To run locally with Apify storage:

```bash
mkdir -p storage/key_value_stores/default
cp examples/INPUT.json storage/key_value_stores/default/INPUT.json
npm run start:dev
```

The default test suite is fully mocked and makes no network requests. A single opt-in live-safe check uses a syntactically valid dummy identifier that the EC service reports as invalid:

```bash
npm run test:live
```

Do not run the live test repeatedly or in a tight CI loop.

### Official sources

- [European Commission EORI overview and format](https://taxation-customs.ec.europa.eu/customs/customs-procedures-import-and-export/customs-operations/economic-operators-registration-and-identification-number-eori_en)
- [Official public EORI validator](https://ec.europa.eu/taxation_customs/dds2/eos/eori_validation.jsp)
- [Official EORI SOAP WSDL](https://ec.europa.eu/taxation_customs/dds2/eos/validation/services/validation?wsdl)
- [EC validation-service limits announcement](https://ec.europa.eu/taxation_customs/dds2/eos/news/newstar.jsp?Lang=en)
- [Apify Actor definition](https://docs.apify.com/actors/development/actor-definition/actor-json)
- [Apify input schema](https://docs.apify.com/actors/development/actor-definition/input-schema/specification/v1)
- [Apify dataset schema](https://docs.apify.com/actors/development/actor-definition/dataset-schema)
- [Apify output schema](https://docs.apify.com/actors/development/actor-definition/output-schema)

### Legal and reliability disclaimer

This software provides a point-in-time technical lookup of the official EC service. It does not certify an entity, guarantee customs clearance, establish identity, or provide legal, tax, sanctions, or customs advice. The EC service can be delayed, unavailable, rate-limited, or return incomplete information; such cases are reported as `unknown`. Verify consequential decisions with the appropriate national customs authority and qualified advisers.

Users are responsible for complying with applicable law, the European Commission's legal notice and privacy rules, Apify's terms, and any contractual or confidentiality duties governing submitted identifiers.

# Actor input Schema

## `eoriNumbers` (type: `array`):

One EORI identifier per line. Values are trimmed, uppercased, and stripped of whitespace before validation.

## `priorStatuses` (type: `array`):

Optional earlier results used to calculate status\_change. Identifiers are normalized using the same rules as the main input.

## `maxConcurrency` (type: `integer`):

Requests still pass through one global rate limiter. Keep the default unless the EC service is responding slowly.

## `minDelayMs` (type: `integer`):

Polite global pacing applied to initial requests and retries.

## `maxRetries` (type: `integer`):

Retries apply only to timeouts, network failures, HTTP 408/429, and 5xx responses.

## `requestTimeoutSecs` (type: `integer`):

Maximum time to wait for one official EC response before treating the attempt as transiently inconclusive.

## Actor input object example

```json
{
  "priorStatuses": [],
  "maxConcurrency": 1,
  "minDelayMs": 1000,
  "maxRetries": 2,
  "requestTimeoutSecs": 20
}
```

# Actor output Schema

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

Normalized validity and status-change records.

## `summary` (type: `string`):

Counts, timing, duplicate suppression, and source information.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("bene123/eori-bulk-validator-monitor").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("bene123/eori-bulk-validator-monitor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{}' |
apify call bene123/eori-bulk-validator-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=bene123/eori-bulk-validator-monitor",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "EORI Bulk Validator & Monitor",
        "description": "Bulk validate user-supplied EORI numbers against the official European Commission service, with normalized results, conservative retries, and optional prior-status change detection. Names and addresses are never stored or returned.",
        "version": "0.0",
        "x-build-id": "KPHfXgkKjOfrCFOYc"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/bene123~eori-bulk-validator-monitor/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-bene123-eori-bulk-validator-monitor",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/bene123~eori-bulk-validator-monitor/runs": {
            "post": {
                "operationId": "runs-sync-bene123-eori-bulk-validator-monitor",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/bene123~eori-bulk-validator-monitor/run-sync": {
            "post": {
                "operationId": "run-sync-bene123-eori-bulk-validator-monitor",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "required": [
                    "eoriNumbers"
                ],
                "properties": {
                    "eoriNumbers": {
                        "title": "EORI numbers",
                        "minItems": 1,
                        "maxItems": 1000,
                        "type": "array",
                        "description": "One EORI identifier per line. Values are trimmed, uppercased, and stripped of whitespace before validation.",
                        "items": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 64
                        }
                    },
                    "priorStatuses": {
                        "title": "Prior statuses (optional)",
                        "maxItems": 1000,
                        "type": "array",
                        "description": "Optional earlier results used to calculate status_change. Identifiers are normalized using the same rules as the main input.",
                        "items": {
                            "type": "object",
                            "additionalProperties": false,
                            "properties": {
                                "eori": {
                                    "title": "EORI number",
                                    "description": "An earlier EORI identifier whose status should be compared with the current lookup.",
                                    "type": "string",
                                    "minLength": 1,
                                    "maxLength": 64,
                                    "editor": "textfield"
                                },
                                "status": {
                                    "title": "Previous status",
                                    "description": "The conclusive or inconclusive status recorded by an earlier run.",
                                    "type": "string",
                                    "editor": "select",
                                    "enum": [
                                        "valid",
                                        "invalid",
                                        "unknown"
                                    ],
                                    "enumTitles": [
                                        "Valid",
                                        "Invalid",
                                        "Unknown"
                                    ]
                                }
                            },
                            "required": [
                                "eori",
                                "status"
                            ]
                        },
                        "default": []
                    },
                    "maxConcurrency": {
                        "title": "Maximum concurrent checks",
                        "minimum": 1,
                        "maximum": 4,
                        "type": "integer",
                        "description": "Requests still pass through one global rate limiter. Keep the default unless the EC service is responding slowly.",
                        "default": 1
                    },
                    "minDelayMs": {
                        "title": "Minimum delay between request starts (ms)",
                        "minimum": 500,
                        "maximum": 10000,
                        "type": "integer",
                        "description": "Polite global pacing applied to initial requests and retries.",
                        "default": 1000
                    },
                    "maxRetries": {
                        "title": "Maximum retries",
                        "minimum": 0,
                        "maximum": 5,
                        "type": "integer",
                        "description": "Retries apply only to timeouts, network failures, HTTP 408/429, and 5xx responses.",
                        "default": 2
                    },
                    "requestTimeoutSecs": {
                        "title": "Request timeout (seconds)",
                        "minimum": 5,
                        "maximum": 60,
                        "type": "integer",
                        "description": "Maximum time to wait for one official EC response before treating the attempt as transiently inconclusive.",
                        "default": 20
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
