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

# Bulk Create Leads

> Create multiple leads in a single request

<Note>
  **Endpoint Alias**: You can also use `/api/v1/records/bulk` instead of `/api/v1/leads/bulk`. Both endpoints are functionally identical - use whichever naming convention fits your integration.
</Note>

## Lead Import Modes

Each lead in the array can use either import mode:

<CardGroup cols={2}>
  <Card title="Standard Mode" icon="phone">
    **Phone number known at import time**

    Provide `phoneNumber` directly for each lead.
  </Card>

  <Card title="Prefetch Mode" icon="database">
    **Phone number fetched just-in-time**

    Provide `externalId` only. Phone fetched via predial function before calling.
  </Card>
</CardGroup>

<Info>
  **Each lead must have at least one of `phoneNumber` or `externalId`.** You can mix modes within the same bulk request.
</Info>

## Authentication

<ParamField header="x-api-key" type="string" required>
  Your API key with `write` scope
</ParamField>

## Request Body

<ParamField body="campaignId" type="string" required>
  The ID of the campaign these leads belong to
</ParamField>

<ParamField body="leads" type="array" required>
  Array of lead objects (max 1000 per request)

  <Expandable title="Lead Object Properties">
    <ParamField body="phoneNumber" type="string">
      Lead's phone number in E.164 format.

      **Required if `externalId` is not provided.**
    </ParamField>

    <ParamField body="externalId" type="string">
      Your CRM's internal ID.

      **Required if `phoneNumber` is not provided.**
    </ParamField>

    <ParamField body="metadata" type="object">
      Custom data about the lead
    </ParamField>

    <ParamField body="scheduledFor" type="string">
      ISO 8601 timestamp for when to call
    </ParamField>

    <ParamField body="priority" type="number" default="0">
      Lead priority (0-100)
    </ParamField>
  </Expandable>
</ParamField>

## Response

<ResponseField name="success" type="boolean">
  Whether the request was successful
</ResponseField>

<ResponseField name="data" type="object">
  <Expandable title="properties">
    <ResponseField name="created" type="number">
      Number of leads successfully created
    </ResponseField>

    <ResponseField name="skipped" type="number">
      Number of leads skipped (duplicates)
    </ResponseField>

    <ResponseField name="createdIds" type="array">
      Array of IDs for created leads
    </ResponseField>

    <ResponseField name="skippedIdentifiers" type="array">
      Array of externalIds or phone numbers that were skipped
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="error" type="string | null">
  Error message if request failed
</ResponseField>

## Examples

<Tabs>
  <Tab title="Standard Mode">
    All leads with phone numbers:

    <RequestExample>
      ```bash cURL theme={null}
      curl -X POST https://YOUR_DEPLOYMENT.lupitor.com/api/v1/leads/bulk \
        -H "x-api-key: YOUR_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{
          "campaignId": "YOUR_CAMPAIGN_ID",
          "leads": [
            {
              "phoneNumber": "+15555551234",
              "externalId": "CRM_001",
              "metadata": { "name": "John Doe" },
              "priority": 10
            },
            {
              "phoneNumber": "+15555555678",
              "externalId": "CRM_002",
              "metadata": { "name": "Jane Smith" },
              "priority": 5
            }
          ]
        }'
      ```

      ```javascript JavaScript theme={null}
      const response = await fetch('https://YOUR_DEPLOYMENT.lupitor.com/api/v1/leads/bulk', {
        method: 'POST',
        headers: {
          'x-api-key': 'YOUR_API_KEY',
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          campaignId: 'YOUR_CAMPAIGN_ID',
          leads: [
            {
              phoneNumber: '+15555551234',
              externalId: 'CRM_001',
              metadata: { name: 'John Doe' },
              priority: 10
            },
            {
              phoneNumber: '+15555555678',
              externalId: 'CRM_002',
              metadata: { name: 'Jane Smith' },
              priority: 5
            }
          ]
        })
      });

      const data = await response.json();
      ```

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

      response = requests.post(
          'https://YOUR_DEPLOYMENT.lupitor.com/api/v1/leads/bulk',
          headers={
              'x-api-key': 'YOUR_API_KEY',
              'Content-Type': 'application/json'
          },
          json={
              'campaignId': 'YOUR_CAMPAIGN_ID',
              'leads': [
                  {
                      'phoneNumber': '+15555551234',
                      'externalId': 'CRM_001',
                      'metadata': {'name': 'John Doe'},
                      'priority': 10
                  },
                  {
                      'phoneNumber': '+15555555678',
                      'externalId': 'CRM_002',
                      'metadata': {'name': 'Jane Smith'},
                      'priority': 5
                  }
              ]
          }
      )

      print(response.json())
      ```
    </RequestExample>
  </Tab>

  <Tab title="Prefetch Mode">
    External IDs only - phone numbers fetched via predial:

    <RequestExample>
      ```bash cURL theme={null}
      curl -X POST https://YOUR_DEPLOYMENT.lupitor.com/api/v1/leads/bulk \
        -H "x-api-key: YOUR_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{
          "campaignId": "YOUR_CAMPAIGN_ID",
          "leads": [
            {
              "externalId": "12345",
              "metadata": { "customer_type": "SF_LEAD" }
            },
            {
              "externalId": "12346",
              "metadata": { "customer_type": "SF_LEAD" }
            },
            {
              "externalId": "12347",
              "metadata": { "customer_type": "SF_LEAD" }
            }
          ]
        }'
      ```

      ```javascript JavaScript theme={null}
      const response = await fetch('https://YOUR_DEPLOYMENT.lupitor.com/api/v1/leads/bulk', {
        method: 'POST',
        headers: {
          'x-api-key': 'YOUR_API_KEY',
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          campaignId: 'YOUR_CAMPAIGN_ID',
          leads: [
            {
              externalId: '12345',
              metadata: { customer_type: 'SF_LEAD' }
            },
            {
              externalId: '12346',
              metadata: { customer_type: 'SF_LEAD' }
            },
            {
              externalId: '12347',
              metadata: { customer_type: 'SF_LEAD' }
            }
          ]
        })
      });

      const data = await response.json();
      ```

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

      response = requests.post(
          'https://YOUR_DEPLOYMENT.lupitor.com/api/v1/leads/bulk',
          headers={
              'x-api-key': 'YOUR_API_KEY',
              'Content-Type': 'application/json'
          },
          json={
              'campaignId': 'YOUR_CAMPAIGN_ID',
              'leads': [
                  {
                      'externalId': '12345',
                      'metadata': {'customer_type': 'SF_LEAD'}
                  },
                  {
                      'externalId': '12346',
                      'metadata': {'customer_type': 'SF_LEAD'}
                  },
                  {
                      'externalId': '12347',
                      'metadata': {'customer_type': 'SF_LEAD'}
                  }
              ]
          }
      )

      print(response.json())
      ```
    </RequestExample>
  </Tab>

  <Tab title="Mixed Mode">
    Some leads with phone numbers, some prefetch-only:

    <RequestExample>
      ```bash cURL theme={null}
      curl -X POST https://YOUR_DEPLOYMENT.lupitor.com/api/v1/leads/bulk \
        -H "x-api-key: YOUR_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{
          "campaignId": "YOUR_CAMPAIGN_ID",
          "leads": [
            {
              "phoneNumber": "+15555551234",
              "externalId": "CRM_001",
              "metadata": { "name": "John Doe" }
            },
            {
              "externalId": "12345",
              "metadata": { "customer_type": "SF_LEAD" }
            }
          ]
        }'
      ```

      ```javascript JavaScript theme={null}
      const response = await fetch('https://YOUR_DEPLOYMENT.lupitor.com/api/v1/leads/bulk', {
        method: 'POST',
        headers: {
          'x-api-key': 'YOUR_API_KEY',
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          campaignId: 'YOUR_CAMPAIGN_ID',
          leads: [
            {
              phoneNumber: '+15555551234',
              externalId: 'CRM_001',
              metadata: { name: 'John Doe' }
            },
            {
              externalId: '12345',
              metadata: { customer_type: 'SF_LEAD' }
            }
          ]
        })
      });

      const data = await response.json();
      ```

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

      response = requests.post(
          'https://YOUR_DEPLOYMENT.lupitor.com/api/v1/leads/bulk',
          headers={
              'x-api-key': 'YOUR_API_KEY',
              'Content-Type': 'application/json'
          },
          json={
              'campaignId': 'YOUR_CAMPAIGN_ID',
              'leads': [
                  {
                      'phoneNumber': '+15555551234',
                      'externalId': 'CRM_001',
                      'metadata': {'name': 'John Doe'}
                  },
                  {
                      'externalId': '12345',
                      'metadata': {'customer_type': 'SF_LEAD'}
                  }
              ]
          }
      )

      print(response.json())
      ```
    </RequestExample>
  </Tab>
</Tabs>

<ResponseExample>
  ```json Success Response - All Created theme={null}
  {
    "success": true,
    "data": {
      "created": 3,
      "skipped": 0,
      "createdIds": [
        "jh7abc123xyz",
        "jh7abc456def",
        "jh7abc789ghi"
      ],
      "skippedIdentifiers": []
    },
    "error": null
  }
  ```

  ```json Success Response - Some Duplicates theme={null}
  {
    "success": true,
    "data": {
      "created": 2,
      "skipped": 1,
      "createdIds": [
        "jh7abc123xyz",
        "jh7abc456def"
      ],
      "skippedIdentifiers": [
        "12347"
      ]
    },
    "error": null
  }
  ```

  ```json Error Response theme={null}
  {
    "success": false,
    "data": null,
    "error": "leads[2]: At least one of phoneNumber or externalId is required"
  }
  ```
</ResponseExample>

## Limits

<Warning>
  **Maximum Size**: You can create up to **1000 leads** per bulk request. For larger imports, make multiple requests.
</Warning>

<Info>
  **Rate Limit**: Bulk endpoint is limited to **10 requests per minute** to prevent abuse.
</Info>

## Duplicate Handling

The bulk endpoint automatically handles duplicates:

1. **Checks `externalId` first** - if provided, checks for existing lead with same externalId in campaign
2. **Then checks `phoneNumber`** - if provided, checks for existing lead with same phone in campaign
3. **Skips duplicates** without failing the entire request
4. **Returns details** about what was created vs skipped

<Tip>
  This makes bulk import **idempotent** - you can safely retry without creating duplicates!
</Tip>

## Common Errors

| Error                                                             | Cause                         | Solution                                  |
| ----------------------------------------------------------------- | ----------------------------- | ----------------------------------------- |
| `campaignId is required`                                          | Missing campaignId            | Include campaignId in request body        |
| `leads array is required`                                         | Missing or invalid leads      | Include leads array in request body       |
| `leads array cannot be empty`                                     | Empty leads array             | Add at least one lead to the array        |
| `Maximum 1000 leads per bulk request`                             | Too many leads                | Split into multiple requests              |
| `leads[N]: At least one of phoneNumber or externalId is required` | Lead missing both identifiers | Each lead needs phoneNumber or externalId |

## Best Practices

<AccordionGroup>
  <Accordion icon="list-check" title="Batch Size">
    * Use 100-500 leads per request for optimal performance
    * Don't max out at 1000 unless necessary
    * Monitor response times and adjust
  </Accordion>

  <Accordion icon="clock" title="Scheduling">
    * Spread out bulk imports over time
    * Don't import 10,000 leads at once
    * Respect rate limits (10/min)
  </Accordion>

  <Accordion icon="shield-check" title="Validation">
    * Validate phone numbers before sending (E.164 format)
    * Ensure each lead has phoneNumber OR externalId
    * Remove duplicates in your data first
  </Accordion>

  <Accordion icon="rotate" title="Error Handling">
    * Check the `created` vs `skipped` counts
    * Log `skippedIdentifiers` for investigation
    * Retry failed requests with exponential backoff
  </Accordion>

  <Accordion icon="database" title="Prefetch Mode">
    * Ensure your campaign has a predial function configured
    * Include any metadata needed by the predial function
    * Test with a small batch first
  </Accordion>
</AccordionGroup>

## Example: CSV Import Script

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

def import_leads_from_csv(file_path, campaign_id, api_key, mode='standard'):
    """
    Import leads from CSV file in batches

    Args:
        file_path: Path to CSV file
        campaign_id: Campaign ID to import to
        api_key: Your API key
        mode: 'standard' (phone required) or 'prefetch' (externalId only)
    """

    batch_size = 500
    leads = []

    with open(file_path, 'r') as file:
        reader = csv.DictReader(file)

        for row in reader:
            lead = {}

            # Always include externalId if available
            if row.get('id') or row.get('external_id'):
                lead['externalId'] = row.get('id') or row.get('external_id')

            # Include phone number if available (required for standard mode)
            if row.get('phone'):
                lead['phoneNumber'] = row['phone']

            # Add metadata
            lead['metadata'] = {
                'name': row.get('name', ''),
                'company': row.get('company', '')
            }

            if row.get('priority'):
                lead['priority'] = int(row['priority'])

            leads.append(lead)

            # Send batch when we hit the limit
            if len(leads) >= batch_size:
                send_batch(leads, campaign_id, api_key)
                leads = []
                time.sleep(6)  # Rate limit: 10/min = 1 every 6 sec

        # Send remaining leads
        if leads:
            send_batch(leads, campaign_id, api_key)

def send_batch(leads, campaign_id, api_key):
    """Send a batch of leads to the API"""

    response = requests.post(
        'https://YOUR_DEPLOYMENT.lupitor.com/api/v1/leads/bulk',
        headers={
            'x-api-key': api_key,
            'Content-Type': 'application/json'
        },
        json={
            'campaignId': campaign_id,
            'leads': leads
        }
    )

    result = response.json()
    if result['success']:
        print(f"Created: {result['data']['created']}, Skipped: {result['data']['skipped']}")
    else:
        print(f"Error: {result['error']}")

# Usage - Standard mode (CSV has phone numbers)
import_leads_from_csv(
    'leads_with_phones.csv',
    'YOUR_CAMPAIGN_ID',
    'YOUR_API_KEY',
    mode='standard'
)

# Usage - Prefetch mode (CSV has only external IDs)
import_leads_from_csv(
    'leads_external_ids.csv',
    'YOUR_CAMPAIGN_ID',
    'YOUR_API_KEY',
    mode='prefetch'
)
```
