Skip to main content

Lead Import API

Bulk import leads from CSV files with field mapping and deduplication. The flow is preview → start → check status:

  1. POST /leads/import/preview — Upload CSV, get file_id and column headers
  2. POST /leads/import/start — Start import with field mappings using file_id
  3. GET /leads/import/jobs and GET /leads/import/jobs/{jobId} — Track progress

POST /leads/import/preview

Upload a CSV file to preview headers and sample rows before importing.

Request

Multipart form data:

ParameterTypeRequiredDescription
filefileYesCSV file with header row
curl -X POST "https://api.kenbun.io/leads/import/preview" \
-H "Authorization: Bearer <token>" \
-F "file=@leads.csv"

Response

200 OK

{
"file_id": "preview_abc123",
"file_name": "leads.csv",
"headers": ["Email", "First Name", "Last Name", "Company", "Phone"],
"sample_rows": [
["jane@example.com", "Jane", "Doe", "Example Inc", "+1-555-0100"],
["john@example.com", "John", "Smith", "Acme Corp", "+1-555-0101"]
],
"row_count": 1000,
"auto_detected_identifiers": {
"email_columns": ["Email"],
"phone_columns": ["Phone"]
},
"expires_at": "2025-01-15T11:00:00Z"
}
FieldTypeDescription
file_idstringUnique identifier for cached file (valid for 1 hour)
file_namestringOriginal filename
headersarrayColumn names from CSV header row
sample_rowsarrayFirst 5 rows of data for preview
row_countintegerTotal rows excluding header
auto_detected_identifiersobjectColumns that look like identifiers
expires_attimestampWhen the cached file expires

Common Errors

StatusMeaningSolution
400Bad RequestFile missing or invalid CSV format
413Payload Too LargeFile exceeds 10 MB limit
415Unsupported Media TypeFile must be CSV

POST /leads/import/start

Start an import job with field mappings.

Request

ParameterTypeRequiredDescription
file_idstringYesFile ID from preview response
primary_identifier.columnstringYesCSV column name for identifier
primary_identifier.kindstringYesemail, phone, cookie, external_id, or social
field_mappingsobjectNoMap CSV columns to profile fields
alias_mappingsobjectNoMap CSV columns to additional aliases

Alias mapping keys use the _alias_ prefix: _alias_email, _alias_phone, _alias_cookie, _alias_external_id, _alias_social.

{
"file_id": "preview_abc123",
"primary_identifier": {
"column": "Email",
"kind": "email"
},
"field_mappings": {
"First Name": "first_name",
"Last Name": "last_name",
"Company": "company",
"Job Title": "title"
},
"alias_mappings": {
"Phone": "_alias_phone",
"LinkedIn": "_alias_social"
}
}

Response

202 Accepted

{
"job_id": "job_abc123",
"status": "queued",
"message": "Import job started",
"file_name": "leads.csv",
"total_rows": 1000
}

Common Errors

StatusMeaningSolution
400Bad RequestMissing required fields or invalid mappings
404Not FoundFile ID expired or not found (previews expire after 1 hour)

GET /leads/import/jobs

List all import jobs for the current OU.

Request

ParameterTypeRequiredDescription
pageintegerNoPage number (default: 1)
limitintegerNoResults per page (default: 25, max: 100)
curl -X GET "https://api.kenbun.io/leads/import/jobs?page=1&limit=25" \
-H "Authorization: Bearer <token>"

Response

200 OK

{
"jobs": [
{
"job_id": "job_abc123",
"status": "completed",
"file_name": "leads.csv",
"created_at": "2025-01-15T10:00:00Z",
"completed_at": "2025-01-15T10:05:00Z",
"total_rows": 1000,
"created_count": 750,
"updated_count": 230,
"failed_count": 20
}
],
"total": 1,
"page": 1,
"limit": 25
}

Job Status Values

StatusDescription
queuedJob is waiting to start
in_progressCurrently processing rows
completedAll rows processed
failedJob failed to complete
cancelledUser cancelled the job

GET /leads/import/jobs/{jobId}

Get detailed results for a specific import job.

Request

ParameterTypeRequiredDescription
jobIdstringYesImport job ID (path parameter)
curl -X GET "https://api.kenbun.io/leads/import/jobs/job_abc123" \
-H "Authorization: Bearer <token>"

Response

200 OK

{
"job_id": "job_abc123",
"status": "completed",
"created_at": "2025-01-15T10:00:00Z",
"started_at": "2025-01-15T10:00:05Z",
"completed_at": "2025-01-15T10:05:00Z",
"file_name": "leads.csv",
"total_rows": 1000,
"created_count": 750,
"updated_count": 230,
"failed_count": 20,
"primary_identifier": {
"column": "Email",
"kind": "email"
},
"field_mappings": {
"First Name": "first_name",
"Last Name": "last_name",
"Company": "company"
},
"errors": [
{"row": 45, "error": "Invalid email format", "value": "not-an-email"},
{"row": 102, "error": "Missing required identifier", "value": ""}
]
}
FieldTypeDescription
job_idstringJob identifier
statusstringCurrent status
created_attimestampWhen job was created
started_attimestampWhen processing began
completed_attimestampWhen processing finished (null if in progress)
total_rowsintegerRows excluding header
created_countintegerNew leads created
updated_countintegerExisting leads updated
failed_countintegerRows that failed
errorsarrayDetails for failed rows (row number, error, value)

Common Errors

StatusMeaningSolution
404Not FoundJob ID does not exist or not accessible

Import Behavior

Deduplication

For each row, kenbun looks up the lead by primary identifier:

  • No match → Creates a new lead
  • Match → Updates the existing lead's profile with new values
  • Aliases → Added to existing leads (not replaced)

Example: CSV row has email=jane@example.com, phone=555-0100. Existing lead has email=jane@example.com, phone=555-0200. Result: lead now has both phone numbers as aliases.

Field Mapping Rules

  • Empty CSV cells are skipped (won't overwrite existing data)
  • Non-empty values update the profile field
  • Unknown field names are ignored
  • Aliases are added (not replaced); duplicates are ignored
  • All profile fields stored as strings (no automatic type conversion)
  • Identifier validation runs (e.g., email format)

Error Handling

Failed rows don't stop the import — other rows continue processing. Errors are recorded with row number and reason.

ErrorCause
Invalid email formatEmail doesn't match pattern
Invalid phone formatPhone contains invalid characters
Missing required identifierIdentifier column is empty
Server errorUnexpected storage failure

Use Cases

Migrate Contacts from Another System

# 1. Preview the CSV
curl -X POST "https://api.kenbun.io/leads/import/preview" \
-H "Authorization: Bearer <token>" \
-F "file=@contacts.csv"

# 2. Start import with mappings
curl -X POST "https://api.kenbun.io/leads/import/start" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"file_id": "preview_abc123",
"primary_identifier": {"column": "Email", "kind": "email"},
"field_mappings": {
"FirstName": "first_name",
"LastName": "last_name",
"Company": "company",
"Title": "title"
}
}'

# 3. Monitor progress
curl -X GET "https://api.kenbun.io/leads/import/jobs/job_abc123" \
-H "Authorization: Bearer <token>"

Import Trade Show Leads with Multiple Identifiers

curl -X POST "https://api.kenbun.io/leads/import/start" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"file_id": "preview_xyz789",
"primary_identifier": {"column": "Email", "kind": "email"},
"field_mappings": {
"Name": "first_name",
"Company": "company",
"Booth Notes": "notes"
},
"alias_mappings": {
"Mobile": "_alias_phone",
"Badge ID": "_alias_external_id"
}
}'

Bulk Update Existing Leads

When the primary identifier matches existing leads, their profiles are updated with new field values. Empty cells preserve existing data.


Notes

  • CSV files must have a header row with column names
  • Maximum file size: 10 MB
  • File previews are cached for 1 hour (then must be re-uploaded)
  • Imports run as background jobs (non-blocking)
  • Large imports (10,000+ rows) may take several minutes
  • Import jobs are OU-scoped
  • Empty CSV cells don't overwrite existing lead data
  • UTF-8 encoding recommended for special characters
  • Profile field names are case-insensitive in mappings