> ## Documentation Index
> Fetch the complete documentation index at: https://docs.naturalead.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# CSV Import Workflow

> Import leads from CSV files using the two-step preview-and-confirm workflow.

## Overview

Naturalead provides a two-step CSV import process that lets you preview and map columns before committing leads to your account. This prevents data errors and gives you full control over how CSV columns map to lead fields.

<Steps>
  <Step title="Upload CSV for Preview">
    Upload your CSV file to get a preview of parsed rows and detected columns.
  </Step>

  <Step title="Map Columns and Confirm">
    Define how CSV columns map to lead fields, then confirm the import.
  </Step>
</Steps>

<Info>
  All imported leads are automatically deduplicated by phone number. If a lead with the same phone already exists in your account, it will be updated rather than duplicated.
</Info>

## Step 1: Upload CSV for Preview

Send your CSV file as a multipart form upload. The API parses the file and returns detected columns along with a sample of rows so you can verify the data before importing.

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://api.naturalead.ai/api/leads/upload/preview \
    -H "X-API-Key: nl_live_YOUR_API_KEY" \
    -F "file=@leads.csv"
  ```

  ```python Python theme={null}
  import requests

  url = "https://api.naturalead.ai/api/leads/upload/preview"
  headers = {"X-API-Key": "nl_live_YOUR_API_KEY"}
  files = {"file": open("leads.csv", "rb")}

  response = requests.post(url, headers=headers, files=files)
  preview = response.json()

  print(f"Detected columns: {preview['columns']}")
  print(f"Sample rows: {len(preview['rows'])}")
  ```

  ```javascript Node.js theme={null}
  const fs = require("fs");
  const FormData = require("form-data");

  const form = new FormData();
  form.append("file", fs.createReadStream("leads.csv"));

  const response = await fetch(
    "https://api.naturalead.ai/api/leads/upload/preview",
    {
      method: "POST",
      headers: {
        "X-API-Key": "nl_live_YOUR_API_KEY",
        ...form.getHeaders(),
      },
      body: form,
    }
  );

  const preview = await response.json();
  console.log("Detected columns:", preview.columns);
  console.log("Sample rows:", preview.rows.length);
  ```
</CodeGroup>

The response includes:

* `columns` -- an array of detected column names from the CSV header row
* `rows` -- a sample of parsed rows for verification
* `uploadId` -- a temporary identifier to reference this upload in the confirm step

## Step 2: Map Columns and Confirm Import

Once you have reviewed the preview, send a column mapping and the `uploadId` to confirm the import. The mapping tells Naturalead which CSV column corresponds to each lead field.

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://api.naturalead.ai/api/leads/upload/confirm \
    -H "X-API-Key: nl_live_YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "uploadId": "preview_abc123",
      "mapping": {
        "Phone Number": "phone",
        "Full Name": "name",
        "Email Address": "email",
        "Company": "company",
        "Lead Source": "source"
      },
      "tags": ["csv-import", "q1-2026"]
    }'
  ```

  ```python Python theme={null}
  import requests

  url = "https://api.naturalead.ai/api/leads/upload/confirm"
  headers = {
      "X-API-Key": "nl_live_YOUR_API_KEY",
      "Content-Type": "application/json",
  }
  payload = {
      "uploadId": "preview_abc123",
      "mapping": {
          "Phone Number": "phone",
          "Full Name": "name",
          "Email Address": "email",
          "Company": "company",
          "Lead Source": "source",
      },
      "tags": ["csv-import", "q1-2026"],
  }

  response = requests.post(url, headers=headers, json=payload)
  result = response.json()

  print(f"Imported: {result['imported']}")
  print(f"Duplicates skipped: {result['duplicates']}")
  print(f"Batch ID: {result['importBatchId']}")
  ```

  ```javascript Node.js theme={null}
  const response = await fetch(
    "https://api.naturalead.ai/api/leads/upload/confirm",
    {
      method: "POST",
      headers: {
        "X-API-Key": "nl_live_YOUR_API_KEY",
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        uploadId: "preview_abc123",
        mapping: {
          "Phone Number": "phone",
          "Full Name": "name",
          "Email Address": "email",
          Company: "company",
          "Lead Source": "source",
        },
        tags: ["csv-import", "q1-2026"],
      }),
    }
  );

  const result = await response.json();
  console.log("Imported:", result.imported);
  console.log("Duplicates skipped:", result.duplicates);
  console.log("Batch ID:", result.importBatchId);
  ```
</CodeGroup>

## Field Mapping Rules

The mapping object maps CSV column names (keys) to Naturalead lead fields (values).

| Lead Field | Required | Description                                                     |
| ---------- | -------- | --------------------------------------------------------------- |
| `phone`    | Yes      | Phone number -- used as the unique identifier for deduplication |
| `name`     | No       | Lead's full name                                                |
| `email`    | No       | Email address                                                   |
| `source`   | No       | Where the lead came from (e.g., "website", "referral")          |

<Warning>
  The `phone` field is required. Every row in your CSV must have a valid phone number mapped. Rows without a phone number will be skipped during import.
</Warning>

Any CSV column mapped to a value that is not a recognized lead field is automatically stored as a **custom field** on the lead. For example, mapping `"Company"` to `"company"` will create a custom field called `company` on each lead.

## Deduplication

Naturalead deduplicates leads by phone number within your account. During import:

* If a lead with the same phone number already exists, the existing lead is **updated** with the new data.
* If no match is found, a new lead is created.

This means you can safely re-import updated CSV files without creating duplicate records.

## Tracking Imports with importBatchId

Every confirmed import generates a unique `importBatchId`. Use this ID to:

* **Filter leads** by import batch using the List Leads API
* **Track import history** to see which leads came from which file
* **Roll back imports** by identifying and deleting all leads from a specific batch

<Note>
  Store the `importBatchId` returned from the confirm response. It is the only way to identify which leads were part of a specific import.
</Note>

## Error Handling

| Status Code | Meaning                                                                     |
| ----------- | --------------------------------------------------------------------------- |
| `400`       | Invalid CSV format, missing required `phone` mapping, or expired `uploadId` |
| `401`       | Missing or invalid authentication                                           |
| `413`       | CSV file exceeds the maximum upload size                                    |
| `422`       | Column mapping references columns not found in the CSV                      |
| `429`       | Rate limit exceeded -- wait and retry                                       |

When some rows fail validation but others succeed, the API performs a **partial import** and returns both the count of successfully imported leads and details about skipped rows in the response.
