# CSV Doctor | Repair Broken CSV Files, With a Fix Report (`broomwagon/csv-doctor`) Actor

Repair the CSV that will not load: wrong delimiters, unquoted values, ragged rows, blank and duplicate headers, mojibake. Get back rectangular typed records plus a report of exactly what changed. Nothing is invented and nothing is dropped, so ragged rows are named, never truncated.

- **URL**: https://apify.com/broomwagon/csv-doctor.md
- **Developed by:** [Brandon Mensing](https://apify.com/broomwagon) (community)
- **Categories:** Developer tools, Automation, Agents
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 row repaireds

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 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

## CSV Doctor

<img align="right" src="https://broomwagon.vercel.app/icons/brand-mark.png" width="64" alt="broomwagon.">

> Repair the CSV files that break every parser, and get a report of exactly what changed and why.

Every data pipeline has the file that will not load. The delimiter is a semicolon except
on the rows someone pasted in. A revenue column lost its quotes and split `7,500,000`
across three fields. One row is short, one is long, the header has a blank in it, and
something in the encoding turned every apostrophe into `â€™`. This actor takes that file
and hands back rectangular, typed records, plus a fix report you can read before you
trust it.

Part of the **Broomwagon** family: deterministic tools that watch scraped and AI data
change. Same file in, same rows out, every time. Nothing is guessed by a model.

### What it repairs

| Problem | What the actor does |
| --- | --- |
| Wrong or unknown delimiter | Picks the one that makes the file rectangular, not the most common character |
| Values containing the delimiter | Honours quotes, doubled quotes, and newlines inside fields |
| A row pasted in with another delimiter | Re-splits that row, but only when it lands on the right width |
| An unquoted `7,500,000` split across fields | Rejoins it, but only when that makes the row the right width |
| Short rows | Pads with nulls, so values never shift into the wrong columns |
| Long rows | Keeps the surplus in `_overflow` instead of discarding it |
| Blank headers, repeated headers | Names them `column_2`, suffixes repeats `_2` |
| Blank rows, all-blank columns | Removes them |
| `â€™`, `Ã©` and friends | Repairs UTF-8 that was read as Latin-1, and strips byte-order marks |
| Everything is a string | Converts numbers, currency, booleans, and the usual spellings of null |

Two rules run through all of it. **Nothing is invented and nothing is dropped**: a row
that cannot be made rectangular is padded or overflowed, never truncated. And **every
repair is counted**, by class, in the `FIXES` record, with the row numbers that stayed
ragged.

Identifiers with leading zeros stay text. A zip code is not the number 742.

### Try it in two minutes

Leave the sample in **Paste CSV text** and run. It is a deliberately broken file: five
rows, four different problems. Read the `FIXES` record to see which rule caught which,
then look at the dataset to see `Globex` rescued from its semicolons and `7,500,000` put
back together.

For real work, point `fileUrl` at the file instead.

### Input

- `fileUrl` | `rawText` | `datasetId` | `items`: the data. A file URL is the usual path;
  a dataset of already-parsed records skips parsing but still gets header and type
  normalization, which is most of the value when a scraper emitted everything as strings.
- `delimiter`: force one instead of detecting it.
- `headerStyle`: tidy the existing names, or snake\_case them for a database.
- `coerceTypes`: on by default; turn it off to keep every value a string.

### Output

- Dataset: the repaired records.
- `FIXES`: a count per fix class and the rows that stayed ragged. Read this first.
- `OUTPUT`: run report, including the detected delimiter and the columns found.

### Pricing (pay-per-event)

| Event | What you pay for | Price |
| --- | --- | --- |
| `record-processed` | Per repaired row written | $0.001 |
| `apify-actor-start` | Run start, per GB of run memory | $0.005 |

**Worked example:** a 20,000-row export: 20,000 × $0.001 = **$20** (plus a penny to
start at the default 2 GB), once, versus an
afternoon of somebody hand-editing the file and guessing at the ragged rows. A nightly
2,000-row feed: $2 a night, and the fix report tells you the day the upstream format
changed.

Set a max charge on any run (`Maximum cost per run` in Console, or
`ACTOR_MAX_TOTAL_CHARGE_USD` via API) and the actor stops cleanly at your budget.

### Integrations

- **API:** `POST https://api.apify.com/v2/acts/broomwagon~csv-doctor/runs` with
  `{"fileUrl": "https://example.com/export.csv"}`, then read the dataset.
- **Apify Schedule:** put it between a nightly export and whatever loads it, and check
  `FIXES` when the loader complains.
- **n8n / Make:** HTTP node fetches the file → this actor → your warehouse. The branch
  that used to fail on malformed rows now gets rectangular records.
- **MCP / AI agents:** callable as a tool via the Apify MCP server. An agent handed a
  broken CSV can repair it deterministically instead of hallucinating the missing
  columns, which is the failure mode when an LLM is asked to "fix this file".

### Roadmap

Date parsing with an explicit format hint, per-column type locking (so one bad row
cannot flip a column to text), and an option to emit the repaired file as CSV rather
than as records.

### The Broomwagon family

This actor is one of nine deterministic post-processing tools from [Broomwagon](https://apify.com/broomwagon): the layer that follows your scrapers and agents, cleaning and watching what they produce. Same input, same output, every time.

- [Scraper Output Monitor](https://apify.com/broomwagon/scraper-output-monitor) — catch the day your scrape silently breaks.
- [Only New Items](https://apify.com/broomwagon/only-new-items) — deliver only records you have never delivered before.
- [Dataset Deduper](https://apify.com/broomwagon/dataset-deduper) — exact and fuzzy dedupe for any dataset, with an audit trail.
- [LLM Output Guard](https://apify.com/broomwagon/llm-output-guard) — validate LLM and agent JSON against your schema.
- [PII Redactor](https://apify.com/broomwagon/pii-redactor) — strip emails, phones, SSNs, cards, and addresses, deterministically.
- [Record Linker](https://apify.com/broomwagon/record-linker) — fuzzy join two datasets that share no key.
- [Google Maps Scraper Deduper](https://apify.com/broomwagon/google-maps-deduper) — merge duplicate places across Google Maps scrapes.
- [AI Search Visibility Monitor](https://apify.com/broomwagon/ai-search-visibility-monitor) — track your brand's share of voice in AI answers.

# Actor input Schema

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

URL of the CSV or TSV file to repair. This is the usual way in: point it at an export, a bucket object, or anything else reachable over HTTP.

## `rawText` (type: `string`):

Paste the file contents instead of giving a URL. Handy for a quick look at what the repairs would do.

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

A dataset of already-parsed records. Parsing is skipped, but headers are still normalized and values still coerced, which is most of the value when a scraper emitted everything as strings.

## `items` (type: `array`):

Already-parsed records provided inline as a JSON array.

## `delimiter` (type: `string`):

Leave empty and the actor works it out by finding the delimiter that makes the file rectangular, which beats counting characters when values contain commas.

## `headerStyle` (type: `string`):

Blank headers are named column\_1, column\_2 and repeats get a \_2 suffix either way. Choose snake\_case when the output feeds a database that dislikes spaces.

## `coerceTypes` (type: `boolean`):

Turn "1,200" into 1200, "yes" into true, and "N/A" into null. Identifiers with leading zeros (zip codes, SKUs) are left as text on purpose. Turn this off to keep every value a string.

## Actor input object example

```json
{
  "rawText": "name,city,revenue\n\"Acme, Inc.\",Chicago,1200000\nGlobex;Springfield;450000\nInitech,Austin\nUmbrella Corp,Raccoon City,\"2,300,000\",extra\nHooli,Palo Alto,7,500,000",
  "headerStyle": "trim",
  "coerceTypes": true
}
```

# Actor output Schema

## `repaired` (type: `string`):

The clean records: rectangular, typed, with tidy header names.

## `fixes` (type: `string`):

What changed and why: a count per fix class, plus the row numbers that could not be made rectangular.

## `report` (type: `string`):

Rows repaired, delimiter detected, columns found, and encoding problems corrected.

# 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 = {
    "rawText": `name,city,revenue
"Acme, Inc.",Chicago,1200000
Globex;Springfield;450000
Initech,Austin
Umbrella Corp,Raccoon City,"2,300,000",extra
Hooli,Palo Alto,7,500,000`
};

// Run the Actor and wait for it to finish
const run = await client.actor("broomwagon/csv-doctor").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 = { "rawText": """name,city,revenue
\"Acme, Inc.\",Chicago,1200000
Globex;Springfield;450000
Initech,Austin
Umbrella Corp,Raccoon City,\"2,300,000\",extra
Hooli,Palo Alto,7,500,000""" }

# Run the Actor and wait for it to finish
run = client.actor("broomwagon/csv-doctor").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 '{
  "rawText": "name,city,revenue\\n\\"Acme, Inc.\\",Chicago,1200000\\nGlobex;Springfield;450000\\nInitech,Austin\\nUmbrella Corp,Raccoon City,\\"2,300,000\\",extra\\nHooli,Palo Alto,7,500,000"
}' |
apify call broomwagon/csv-doctor --silent --output-dataset

```

## MCP server setup

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

```

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/ttiz2KfgRiO78rmjW/builds/E9o7a76FLDaPaoeUf/openapi.json
