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

# POST /v1/campaigns/:id/leads/bulk — Bulk Enroll Leads

> POST /v1/campaigns/:id/leads/bulk — Upserts and enrolls contacts into a campaign. Existing leads with matching emails are updated, not duplicated.

Use this endpoint to add or update a batch of leads and immediately enroll them in a specific campaign. Leadterra performs an upsert on each entry: if a lead with a matching email address already exists in your workspace, their record is updated with any new field values you provide and they are re-enrolled in the campaign. New leads are created and enrolled in a single operation, keeping your contact data clean without requiring a separate sync step.

## Endpoint

```bash theme={null}
POST /v1/campaigns/:id/leads/bulk
```

## Authorization

All requests must include a valid API key in the `Authorization` header as a Bearer token.

```bash theme={null}
Authorization: Bearer sk_live_YOUR_KEY
```

## Path Parameters

<ParamField path="id" type="string" required>
  The unique identifier of the campaign you want to enroll leads into.
</ParamField>

## Request Body

<ParamField body="leads" type="array" required>
  An array of lead objects to upsert and enroll. Each object must contain at least an `email` address. You may include any additional custom fields beyond the ones listed below — Leadterra will store them on the lead record and make them available as personalization variables in your campaign sequences.

  <Expandable title="leads item fields">
    <ParamField body="email" type="string" required>
      The lead's email address. Used as the unique key for upsert matching.
    </ParamField>

    <ParamField body="firstName" type="string">
      The lead's first name. Used for `{{firstName}}` personalization tokens in email copy.
    </ParamField>

    <ParamField body="company" type="string">
      The lead's company name. Used for `{{company}}` personalization tokens in email copy.
    </ParamField>

    <ParamField body="jobTitle" type="string">
      The lead's job title. Used for `{{jobTitle}}` personalization tokens in email copy.
    </ParamField>
  </Expandable>
</ParamField>

## Example Request

```bash theme={null}
curl -X POST https://app.leadterra.co/v1/campaigns/camp_123/leads/bulk \
  -H "Authorization: Bearer sk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "leads": [
      {
        "email": "ava@example.com",
        "firstName": "Ava",
        "company": "ExampleCo",
        "jobTitle": "VP Sales"
      },
      {
        "email": "ben@example.com",
        "firstName": "Ben",
        "company": "AcmeCorp",
        "jobTitle": "Head of Growth"
      },
      {
        "email": "cara@example.com",
        "firstName": "Cara",
        "company": "StartupHQ",
        "jobTitle": "Founder"
      },
      {
        "email": "dan@example.com",
        "firstName": "Dan",
        "company": "ScaleUp Inc",
        "jobTitle": "Director of Sales"
      }
    ]
  }'
```

## Response Fields

<ResponseField name="enrolled" type="integer">
  The number of net-new leads that were created and enrolled in this request.
</ResponseField>

<ResponseField name="updated" type="integer">
  The number of existing leads whose records were updated and re-enrolled in this request.
</ResponseField>

<ResponseField name="total" type="integer">
  The total number of leads processed in this request. Always equal to `enrolled + updated`.
</ResponseField>

## Example Response

```json theme={null}
{
  "enrolled": 3,
  "updated": 1,
  "total": 4
}
```

<Note>
  **Upsert semantics:** Leadterra matches leads by `email` address. If a lead with that email already exists in your workspace, their record is updated with the fields you provide in this request and they are added to the campaign — they will not appear as a duplicate entry. Only fields explicitly included in the request body are overwritten; omitted fields retain their existing values.
</Note>

## Error Codes

| Code  | Meaning                                                                                                                                                                 |
| ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400` | Bad Request — the request body is malformed or missing required fields.                                                                                                 |
| `401` | Unauthorized — your API key is missing, invalid, or revoked.                                                                                                            |
| `404` | Not Found — no campaign exists for the provided `:id` in your workspace.                                                                                                |
| `422` | Unprocessable Entity — one or more lead objects failed validation (e.g. invalid email format). The response body will include a `errors` array describing each failure. |
