# Data Cleaner: Normalize Phone, URL, Price & Date Fields (`tamatama/data-normalizer`) Actor

Data cleaner from $0.50 per 1,000 rows ($0.80 on the Free plan): normalizes 6 field types in datasets, CSV, JSON or Excel: phone to E.164, email, URL, price and currency, date to ISO, company names. Works on nested fields and flags new, changed and removed rows since the last run.

- **URL**: https://apify.com/tamatama/data-normalizer.md
- **Developed by:** [Otama Labs](https://apify.com/tamatama) (community)
- **Categories:** Developer tools, Agents
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.50 / 1,000 cleaned rows

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Data Cleaner: Normalize Phone, URL, Price & Date Fields

**Data cleaner for scraped and exported data**: give it a JSON array, an Apify dataset or a CSV / JSON / Excel file, and it normalizes **6 field types** in every row: **phone numbers to E.164**, **emails**, **URLs** (canonical form, no tracking parameters), **prices** (amount + ISO currency), **dates** (ISO 8601) and **company names** (legal suffixes removed, near-duplicates grouped). It works on **nested fields** (`contact.phone`) and can **compare with the last run** to flag new, changed and removed rows.

**From $0.50 per 1,000 rows** ($0.80 on the Free plan): 10,000 rows cost $5 to $8. Rows that are left out (unchanged rows with *Only new and changed rows*) and removed-row markers are free.

### What you can use it for

- **Clean scraper output before it goes into a CRM, Google Sheets or a database**: one phone format, one date format, one URL per site.
- **Merge lists from several sources**: `company_key` / `company_group` match "Acme Corp." with "ACME Corporation"; `contact_website_domain` matches `www.acme.com/?utm_source=x` with `https://acme.com`.
- **Price monitoring**: `$1,299.99`, `1.299,99 €`, `¥12,000` and `2.5k USD` become numbers with a currency code you can sort and convert.
- **Track a feed or catalog over time**: set a key field (`id`, `sku`, `url`) and get `_change: new / changed / unchanged / removed` for every row.
- **AI agents**: hand messy rows to the Actor and get typed values back, with a reason for every value it could not read.

### Output example

Your row stays as it is; the cleaned values are added next to it (dots in nested names become `_`). Real output of the default input:

```json
{
  "id": "A-1",
  "company": "Acme Corp.",
  "contact": { "phone": "(415) 555-0132", "website": "www.acme.com/?utm_source=list" },
  "price": "$1,299.99",
  "updatedAt": "03/04/2026",
  "company_clean": "Acme",
  "company_key": "acme",
  "company_group": "acme",
  "contact_phone_e164": "+14155550132",
  "contact_phone_country": "US",
  "contact_phone_national": "(415) 555-0132",
  "contact_phone_extension": null,
  "contact_website_canonical": "https://acme.com/",
  "contact_website_domain": "acme.com",
  "price_amount": 1299.99,
  "price_currency": "USD",
  "updatedAt_iso": "2026-03-04",
  "_issues": [
    { "field": "updatedAt", "value": "03/04/2026", "issue": "day and month could be swapped; read as MDY" }
  ]
}
```

Values that cannot be read become `null` and are listed in `_issues` with the reason, so nothing is silently guessed.

### Field types

| Type | Added columns | Examples |
|---|---|---|
| `phone` | `_e164`, `_country`, `_national`, `_extension` | `(415) 555-0132` → `+14155550132`; `03-1234-5678` with country JP → `+81312345678` |
| `email` | `_normalized`, `_domain` | `mailto:Ann@Example.COM` → `Ann@example.com` |
| `url` | `_canonical`, `_domain` | `HTTPS://Shop.Acme.co.uk/products/?utm_source=x` → `https://shop.acme.co.uk/products`, domain `acme.co.uk` |
| `price` | `_amount`, `_currency` | `1.299,00 €` → `1299.0` `EUR`; `R$ 10,00` → `10.0` `BRL`; `2.5k USD` → `2500.0` `USD` |
| `date` | `_iso` | `Oct 5, 2026` → `2026-10-05`; `2026年10月1日` → `2026-10-01`; unix timestamps; `yesterday` |
| `company` | `_clean`, `_key`, `_group` | `Globex, Inc` → `Globex`, key `globex`; `株式会社サンプル` → `サンプル` |

### How to use

1. Paste your rows into **Rows (JSON array)**, or enter an **Apify dataset ID** (for example the output of a scraper run), or a **File URL** of a CSV, JSON, JSON Lines or XLSX file.
2. Leave **Fields and their types** empty to detect fields by name (`phone`, `tel`, `email`, `website`, `url`, `price`, `cost`, `salary`, `createdAt`, `date`, `company`, `employer`...), or name them yourself: `{"contact.tel": "phone", "cost": "price", "vendor": "company"}`.
3. Set the **country for phone numbers** without a `+`, the **order of numeric dates** (`03/04/2026` is March 4 in MDY, 3 April in DMY) and the **currency for `$` / `¥` / plain numbers**.
4. Click **Start**. The default input cleans 5 sample rows in a few seconds. Download the result as JSON, CSV or Excel, or read it through the API.

To compare with the last run, set **Key field** (e.g. `id`) and keep the same **Compare name** across runs. Turn on **Output only new and changed rows** to get just the changes (unchanged rows are not output and not charged).

### Input

| Field | What it does |
|---|---|
| Rows (JSON array) | Your rows. Nested objects are fine. |
| Apify dataset ID | Clean the items of a dataset instead. |
| File URL | CSV (comma, semicolon, tab or pipe), JSON, JSON Lines or XLSX (first sheet), up to 50 MB. |
| Fields and their types | `{"field": "type"}`; types `phone`, `email`, `url`, `price`, `date`, `company`. Dots for nested fields. |
| Detect fields by name | On by default. Fields you name win. |
| Country for phone numbers | Two-letter code, default `US`. |
| Order of numeric dates | `MDY` (default), `DMY` or `YMD`. Day/month values that could be swapped get a note in `_issues`. |
| Currency for $, ¥, kr | Default `USD`. `$` follows it when it is a dollar currency (CAD, AUD...), `¥` when it is JPY or CNY. |
| Decimal separator | Auto, dot or comma. |
| URL options | Remove tracking parameters (utm\_\*, gclid, fbclid...) and `www.` (both on by default). |
| Company name match | 0-100, default 90: how similar cleaned names must be to share a `_group`. |
| Key field / Compare name / Only new and changed rows | Compare with the last run. |
| Max rows | 0 = all, up to 100,000 rows per run. |

### Compare with the last run

With a key field, each row gets `_key` and `_change`:

- `new`: the key was not in the last run with this compare name.
- `changed`: the original row differs from the last run (the comparison uses your original values, so a relative date like `yesterday` does not mark every row as changed).
- `unchanged`: same as last time.
- `removed`: a key of the last run that is not in this run. These rows contain only `_key` and `_change` and are free.

The state is saved in your account only when every row was delivered, so a run that hit your cost limit does not hide changes next time.

### Pricing

Pay per event:

| Event | Price |
|---|---|
| Cleaned row | $0.80 per 1,000 on the Free plan, $0.70 Bronze, $0.60 Silver, $0.50 Gold and up |
| Actor start | $0.00005 per GB of memory per run (default memory 512 MB = one start) |

Examples: the default input (5 rows) costs under $0.01; 10,000 rows cost $5 to $8. Platform usage is included. Set a maximum cost per run in the run options and the Actor stops at that limit.

### Limits

- It checks formats, not existence: a phone number with a valid area code and length is accepted even if nobody uses it.
- A date without a year (`Oct 5`) gets the current year and a note in `_issues`. Text without any digit is only read as a date when it is a relative word (`yesterday`, `today`, `3 hours ago`).
- Company names: legal forms (Inc, Ltd, LLC, GmbH, 株式会社...) are removed; brand words are kept. Fuzzy grouping is used for the first 5,000 different names in a run, exact keys after that.
- Files: UTF-8 is best; Shift_JIS and Windows-1252 CSV files are also read. Old `.xls` files are not supported (save as `.xlsx` or CSV). Only the first sheet of an XLSX file is read.
- Up to 100,000 rows per run. If a run is cut by Max rows or your cost limit, the comparison state is not saved.
- Running it again on its own output does not clean the added columns again (`_e164`, `_key`... are skipped by the field detection).

### For AI Agents

Call the Actor through the Apify API or the Apify MCP server (`https://mcp.apify.com/?tools=tamatama/data-normalizer`). Example input:

```json
{
  "data": [{ "name": "Acme Corp.", "tel": "+1 (415) 555-0132", "web": "acme.com", "cost": "€19,90", "date": "23.10.2026" }],
  "fields": { "name": "company", "tel": "phone", "web": "url", "cost": "price", "date": "date" },
  "dateOrder": "DMY"
}
```

Read the cleaned values from the dataset and the reasons for unread values from `_issues`.

### FAQ

**Does it change my original values?** No. The original row is kept and the cleaned values are added as new columns.

**Does it guess when a value is ambiguous?** It follows your settings (date order, country, currency) and writes a note in `_issues` for values that could be read two ways, such as `03/04/2026`.

**Does it check that a phone number or email exists?** No. It checks the format and the numbering plan (a valid area code and length), not whether the line or mailbox is in use. It does not contact anyone.

**Does it call other services?** No. All parsing runs inside the Actor. Only the file URL you give is downloaded.

**Can I use it from Make, n8n, Zapier or LangChain?** Yes: use the Apify app or integration of the tool and pick this Actor, or call the REST API (`POST https://api.apify.com/v2/acts/tamatama~data-normalizer/run-sync-get-dataset-items`). To clean a scraper's output automatically, add this Actor as an integration of the scraper's task and pass the dataset ID.

**Which countries and currencies?** Phone numbers of all countries (Google's libphonenumber data). Currency codes in ISO form and the common symbols (€ £ ₹ ₩ R$ zł and others).

### Related Actors

| Actor | What it does |
|---|---|
| [PDF to Markdown](https://apify.com/tamatama/doc-to-markdown) | PDF, Word, Excel and PowerPoint to Markdown and RAG chunks. |
| [PDF OCR](https://apify.com/tamatama/image-pdf-ocr) | Scanned PDFs and images to text. |
| [Website Change Monitor](https://apify.com/tamatama/website-change-monitor) | Text diff of web pages between runs. |
| [ATS Jobs](https://apify.com/tamatama/ats-jobs) | Company job boards (Greenhouse, Lever, Ashby and more) in one format. |
| [Remote Jobs](https://apify.com/tamatama/remote-jobs) | Remote OK and Jobicy remote jobs. |

### Libraries

phonenumbers (Apache-2.0, a port of Google's libphonenumber), dateparser (BSD-3-Clause), price-parser (BSD-3-Clause), rapidfuzz (MIT), tldextract (BSD-3-Clause, with its bundled public suffix list), openpyxl (MIT).

### Data sources

The Actor reads only the data you give it (rows, a dataset in your account, or a file URL). Use it on data you are allowed to process.

# Actor input Schema

## `data` (type: `array`):

The rows to clean, as a JSON array of objects. Nested objects are fine (e.g. contact.phone). Use this, or a dataset ID, or a file URL below.

## `datasetId` (type: `string`):

Clean the items of an Apify dataset, e.g. the output of a scraper run. Overrides the rows above.

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

A public URL of a CSV, JSON, JSON Lines or Excel (.xlsx, first sheet) file, up to 50 MB. Overrides the rows above.

## `fields` (type: `object`):

Which fields to clean and how: {"field name": "type"}. Types: phone, email, url, price, date, company. Use dots for nested fields, e.g. {"contact.tel": "phone", "cost": "price"}. Leave empty to detect by field name.

## `autoDetect` (type: `boolean`):

Also clean fields whose names say what they are (phone, tel, email, url, website, price, cost, salary, date, createdAt, company, employer...). Fields you name above win.

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

Two-letter country code used for numbers written without a country code, e.g. US, GB, DE, JP. Numbers starting with + are read as written.

## `dateOrder` (type: `string`):

How to read dates like 03/04/2026: MDY = March 4 (US), DMY = 3 April (UK, EU), YMD. Values where day and month could be swapped get a note in \_issues.

## `defaultCurrency` (type: `string`):

Three-letter code used when the symbol is shared by several currencies ($, ¥, kr) or missing, e.g. USD, CAD, JPY, CNY. Empty = leave the currency null and note it in \_issues.

## `decimalSeparator` (type: `string`):

Leave on auto unless your prices are all written one way: "." (1,299.50) or "," (1.299,50).

## `removeTrackingParams` (type: `boolean`):

Drop utm\_\*, gclid, fbclid and similar parameters.

## `removeWww` (type: `boolean`):

Treat www.example.com and example.com as the same site.

## `companyMatchThreshold` (type: `integer`):

Company names whose cleaned keys are at least this similar get the same <field>\_group (e.g. "Acme Corp." and "ACME Corporation"). 100 = exact keys only, 0 = off.

## `keyField` (type: `string`):

A field that identifies a row (e.g. id, sku, url). Each row then gets \_change: new, changed or unchanged against the last run with the same compare name, and rows that are gone are added with \_change: removed (not charged).

## `compareName` (type: `string`):

Separate comparisons by name, e.g. one per supplier feed. Runs with the same name are compared.

## `onlyChanges` (type: `boolean`):

With a key field: leave out unchanged rows (they are not charged).

## `maxRows` (type: `integer`):

Stop after this many rows. 0 = all (up to 100,000 per run).

## Actor input object example

```json
{
  "data": [
    {
      "id": "A-1",
      "company": "Acme Corp.",
      "contact": {
        "phone": "(415) 555-0132",
        "website": "www.acme.com/?utm_source=list"
      },
      "price": "$1,299.99",
      "updatedAt": "03/04/2026"
    },
    {
      "id": "A-2",
      "company": "ACME Corporation",
      "contact": {
        "phone": "+44 20 7946 0958",
        "website": "HTTPS://Shop.Acme.co.uk/products/"
      },
      "price": "1.299,00 €",
      "updatedAt": "2026-10-23T09:30:00Z"
    },
    {
      "id": "A-3",
      "company": "Globex, Inc",
      "contact": {
        "phone": "415.555.0198 ext. 12",
        "website": "globex.com"
      },
      "price": "GBP 45",
      "updatedAt": "Oct 5, 2026"
    },
    {
      "id": "A-4",
      "company": "株式会社サンプル",
      "contact": {
        "phone": "03-1234-5678",
        "website": "http://example.jp:80/about"
      },
      "price": "¥12,000",
      "updatedAt": "2026年10月1日"
    },
    {
      "id": "A-5",
      "company": "Initech LLC",
      "contact": {
        "phone": "not given",
        "website": "initech"
      },
      "price": "2.5k USD",
      "updatedAt": "yesterday"
    }
  ],
  "autoDetect": true,
  "defaultCountry": "US",
  "dateOrder": "MDY",
  "defaultCurrency": "USD",
  "decimalSeparator": "",
  "removeTrackingParams": true,
  "removeWww": true,
  "companyMatchThreshold": 90,
  "compareName": "default",
  "onlyChanges": false,
  "maxRows": 0
}
```

# Actor output Schema

## `rows` (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 = {
    "data": [
        {
            "id": "A-1",
            "company": "Acme Corp.",
            "contact": {
                "phone": "(415) 555-0132",
                "website": "www.acme.com/?utm_source=list"
            },
            "price": "$1,299.99",
            "updatedAt": "03/04/2026"
        },
        {
            "id": "A-2",
            "company": "ACME Corporation",
            "contact": {
                "phone": "+44 20 7946 0958",
                "website": "HTTPS://Shop.Acme.co.uk/products/"
            },
            "price": "1.299,00 €",
            "updatedAt": "2026-10-23T09:30:00Z"
        },
        {
            "id": "A-3",
            "company": "Globex, Inc",
            "contact": {
                "phone": "415.555.0198 ext. 12",
                "website": "globex.com"
            },
            "price": "GBP 45",
            "updatedAt": "Oct 5, 2026"
        },
        {
            "id": "A-4",
            "company": "株式会社サンプル",
            "contact": {
                "phone": "03-1234-5678",
                "website": "http://example.jp:80/about"
            },
            "price": "¥12,000",
            "updatedAt": "2026年10月1日"
        },
        {
            "id": "A-5",
            "company": "Initech LLC",
            "contact": {
                "phone": "not given",
                "website": "initech"
            },
            "price": "2.5k USD",
            "updatedAt": "yesterday"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("tamatama/data-normalizer").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 = { "data": [
        {
            "id": "A-1",
            "company": "Acme Corp.",
            "contact": {
                "phone": "(415) 555-0132",
                "website": "www.acme.com/?utm_source=list",
            },
            "price": "$1,299.99",
            "updatedAt": "03/04/2026",
        },
        {
            "id": "A-2",
            "company": "ACME Corporation",
            "contact": {
                "phone": "+44 20 7946 0958",
                "website": "HTTPS://Shop.Acme.co.uk/products/",
            },
            "price": "1.299,00 €",
            "updatedAt": "2026-10-23T09:30:00Z",
        },
        {
            "id": "A-3",
            "company": "Globex, Inc",
            "contact": {
                "phone": "415.555.0198 ext. 12",
                "website": "globex.com",
            },
            "price": "GBP 45",
            "updatedAt": "Oct 5, 2026",
        },
        {
            "id": "A-4",
            "company": "株式会社サンプル",
            "contact": {
                "phone": "03-1234-5678",
                "website": "http://example.jp:80/about",
            },
            "price": "¥12,000",
            "updatedAt": "2026年10月1日",
        },
        {
            "id": "A-5",
            "company": "Initech LLC",
            "contact": {
                "phone": "not given",
                "website": "initech",
            },
            "price": "2.5k USD",
            "updatedAt": "yesterday",
        },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("tamatama/data-normalizer").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 '{
  "data": [
    {
      "id": "A-1",
      "company": "Acme Corp.",
      "contact": {
        "phone": "(415) 555-0132",
        "website": "www.acme.com/?utm_source=list"
      },
      "price": "$1,299.99",
      "updatedAt": "03/04/2026"
    },
    {
      "id": "A-2",
      "company": "ACME Corporation",
      "contact": {
        "phone": "+44 20 7946 0958",
        "website": "HTTPS://Shop.Acme.co.uk/products/"
      },
      "price": "1.299,00 €",
      "updatedAt": "2026-10-23T09:30:00Z"
    },
    {
      "id": "A-3",
      "company": "Globex, Inc",
      "contact": {
        "phone": "415.555.0198 ext. 12",
        "website": "globex.com"
      },
      "price": "GBP 45",
      "updatedAt": "Oct 5, 2026"
    },
    {
      "id": "A-4",
      "company": "株式会社サンプル",
      "contact": {
        "phone": "03-1234-5678",
        "website": "http://example.jp:80/about"
      },
      "price": "¥12,000",
      "updatedAt": "2026年10月1日"
    },
    {
      "id": "A-5",
      "company": "Initech LLC",
      "contact": {
        "phone": "not given",
        "website": "initech"
      },
      "price": "2.5k USD",
      "updatedAt": "yesterday"
    }
  ]
}' |
apify call tamatama/data-normalizer --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,tamatama/data-normalizer"
        }
    }
}
```

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/BvYMN17xqaIMETqg6/builds/GHK1PGv3AO45j6dO2/openapi.json
