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
}
]
}'
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();
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())
{
"success": true,
"data": {
"created": 3,
"skipped": 0,
"createdIds": [
"jh7abc123xyz",
"jh7abc456def",
"jh7abc789ghi"
],
"skippedIdentifiers": []
},
"error": null
}
{
"success": true,
"data": {
"created": 2,
"skipped": 1,
"createdIds": [
"jh7abc123xyz",
"jh7abc456def"
],
"skippedIdentifiers": [
"12347"
]
},
"error": null
}
{
"success": false,
"data": null,
"error": "leads[2]: At least one of phoneNumber or externalId is required"
}
Leads
Bulk Create Leads
Create multiple leads in a single request
POST
/
api
/
v1
/
leads
/
bulk
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
}
]
}'
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();
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())
{
"success": true,
"data": {
"created": 3,
"skipped": 0,
"createdIds": [
"jh7abc123xyz",
"jh7abc456def",
"jh7abc789ghi"
],
"skippedIdentifiers": []
},
"error": null
}
{
"success": true,
"data": {
"created": 2,
"skipped": 1,
"createdIds": [
"jh7abc123xyz",
"jh7abc456def"
],
"skippedIdentifiers": [
"12347"
]
},
"error": null
}
{
"success": false,
"data": null,
"error": "leads[2]: At least one of phoneNumber or externalId is required"
}
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.Lead Import Modes
Each lead in the array can use either import mode:Standard Mode
Phone number known at import timeProvide
phoneNumber directly for each lead.Prefetch Mode
Phone number fetched just-in-timeProvide
externalId only. Phone fetched via predial function before calling.Each lead must have at least one of
phoneNumber or externalId. You can mix modes within the same bulk request.Authentication
string
required
Your API key with
write scopeRequest Body
string
required
The ID of the campaign these leads belong to
array
required
Array of lead objects (max 1000 per request)
Response
boolean
Whether the request was successful
object
string | null
Error message if request failed
Examples
- Standard Mode
- Prefetch Mode
- Mixed Mode
All leads with phone numbers:
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
}
]
}'
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();
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())
External IDs only - phone numbers fetched via predial:
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" }
}
]
}'
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();
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())
Some leads with phone numbers, some prefetch-only:
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" }
}
]
}'
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();
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())
{
"success": true,
"data": {
"created": 3,
"skipped": 0,
"createdIds": [
"jh7abc123xyz",
"jh7abc456def",
"jh7abc789ghi"
],
"skippedIdentifiers": []
},
"error": null
}
{
"success": true,
"data": {
"created": 2,
"skipped": 1,
"createdIds": [
"jh7abc123xyz",
"jh7abc456def"
],
"skippedIdentifiers": [
"12347"
]
},
"error": null
}
{
"success": false,
"data": null,
"error": "leads[2]: At least one of phoneNumber or externalId is required"
}
Limits
Maximum Size: You can create up to 1000 leads per bulk request. For larger imports, make multiple requests.
Rate Limit: Bulk endpoint is limited to 10 requests per minute to prevent abuse.
Duplicate Handling
The bulk endpoint automatically handles duplicates:- Checks
externalIdfirst - if provided, checks for existing lead with same externalId in campaign - Then checks
phoneNumber- if provided, checks for existing lead with same phone in campaign - Skips duplicates without failing the entire request
- Returns details about what was created vs skipped
This makes bulk import idempotent - you can safely retry without creating duplicates!
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
Batch Size
Batch Size
- Use 100-500 leads per request for optimal performance
- Don’t max out at 1000 unless necessary
- Monitor response times and adjust
Scheduling
Scheduling
- Spread out bulk imports over time
- Don’t import 10,000 leads at once
- Respect rate limits (10/min)
Validation
Validation
- Validate phone numbers before sending (E.164 format)
- Ensure each lead has phoneNumber OR externalId
- Remove duplicates in your data first
Error Handling
Error Handling
- Check the
createdvsskippedcounts - Log
skippedIdentifiersfor investigation - Retry failed requests with exponential backoff
Prefetch Mode
Prefetch Mode
- Ensure your campaign has a predial function configured
- Include any metadata needed by the predial function
- Test with a small batch first
Example: CSV Import Script
Python
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'
)