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

# Bulk Enroll Leads into a Leadterra Campaign via API

> Learn how to bulk-upsert and enroll contacts into a Leadterra campaign using the API, including personalization fields and deduplication semantics.

Leads are the contacts your campaign will reach out to. Leadterra's bulk upsert endpoint lets you enroll hundreds of leads in a single API call, automatically deduplicating by email address so you never create duplicate records. This guide covers preparing your leads payload, sending the request, and confirming enrollment before you start the campaign.

<Steps>
  <Step title="Prepare your leads array">
    Each lead object requires an `email` field. All other fields are optional but recommended — they power the `{{firstName}}`, `{{company}}`, and `{{jobTitle}}` personalization tokens in your sequence steps.

    | Field          | Required    | Description                                |
    | -------------- | ----------- | ------------------------------------------ |
    | `email`        | ✅           | Primary identifier; used for deduplication |
    | `firstName`    | Recommended | Maps to `{{firstName}}` token              |
    | `lastName`     | Optional    | Maps to `{{lastName}}` token               |
    | `company`      | Recommended | Maps to `{{company}}` token                |
    | `jobTitle`     | Optional    | Maps to `{{jobTitle}}` token               |
    | `customFields` | Optional    | Key-value object for custom tokens         |

    Here's an example leads array with three contacts ready for enrollment:

    ```json theme={null}
    {
      "leads": [
        {
          "email": "ava@acmecorp.com",
          "firstName": "Ava",
          "lastName": "Chen",
          "company": "Acme Corp",
          "jobTitle": "VP Sales"
        },
        {
          "email": "marcus@buildfast.io",
          "firstName": "Marcus",
          "lastName": "Webb",
          "company": "BuildFast",
          "jobTitle": "Head of Growth"
        },
        {
          "email": "priya@nexusai.com",
          "firstName": "Priya",
          "lastName": "Sharma",
          "company": "Nexus AI",
          "jobTitle": "Director of Revenue"
        }
      ]
    }
    ```
  </Step>

  <Step title="Send the bulk upsert">
    Post your leads array to `POST /v1/campaigns/:id/leads/bulk`, replacing `:id` with the campaign ID from your campaign creation step. Leadterra will enroll each lead and resolve any personalization tokens against their fields.

    ```bash theme={null}
    curl -X POST https://app.leadterra.co/v1/campaigns/camp_01HZA1TPNQ8RD4KF/leads/bulk \
      -H "Authorization: Bearer sk_live_YOUR_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "leads": [
          {
            "email": "ava@acmecorp.com",
            "firstName": "Ava",
            "lastName": "Chen",
            "company": "Acme Corp",
            "jobTitle": "VP Sales"
          },
          {
            "email": "marcus@buildfast.io",
            "firstName": "Marcus",
            "lastName": "Webb",
            "company": "BuildFast",
            "jobTitle": "Head of Growth"
          },
          {
            "email": "priya@nexusai.com",
            "firstName": "Priya",
            "lastName": "Sharma",
            "company": "Nexus AI",
            "jobTitle": "Director of Revenue"
          }
        ]
      }'
    ```
  </Step>

  <Step title="Verify enrollment">
    A successful response reports how many leads were enrolled and how many were updated rather than newly created.

    ```json theme={null}
    {
      "result": {
        "enrolled": 3,
        "updated": 0,
        "skipped": 0,
        "totalInCampaign": 3
      }
    }
    ```

    If a lead with the same email address already exists in the campaign, Leadterra updates their record with any new field values instead of creating a duplicate entry. The `updated` count reflects these upserts, and the lead's position in the sequence is preserved — they won't receive any step they've already been sent.

    The `skipped` count covers addresses that appear on your workspace [suppression list](/guides/suppression) and were silently excluded from enrollment.
  </Step>

  <Step title="Start the campaign">
    If the campaign isn't already running, start it now with `POST /v1/campaigns/:id/start`. Leadterra will immediately begin queueing the first sequence step for every enrolled lead.

    ```bash theme={null}
    curl -X POST https://app.leadterra.co/v1/campaigns/camp_01HZA1TPNQ8RD4KF/start \
      -H "Authorization: Bearer sk_live_YOUR_KEY"
    ```

    ```json theme={null}
    {
      "campaign": {
        "id": "camp_01HZA1TPNQ8RD4KF",
        "status": "running",
        "startedAt": "2025-06-10T15:00:00Z",
        "leadsEnrolled": 3,
        "queuedMessages": 3
      }
    }
    ```

    You can also add more leads to a campaign that's already `running` — they'll be enrolled and queued immediately.
  </Step>
</Steps>

<Note>
  Every personalization token in your sequence steps — like `{{firstName}}` or `{{company}}` — must have a matching field on the lead object. If a field is missing, the token renders as an empty string. Always verify your leads array includes the fields your copy depends on before enrolling.
</Note>

<Tip>
  For very large lists (10,000+ leads), split your array into batches of 500–1,000 leads per request. Smaller batches reduce timeout risk and make it easier to identify and retry any failed rows.
</Tip>
