The duplicate email errors you’re experiencing stem from how the bulk API handles duplicate detection and the incorrect endpoint usage for campaign member management.
Duplicate Check Against CRM: The bulk upsert API checks for duplicates across the entire Contacts module in your CRM, not just within the current batch. The duplicate_check_fields parameter determines which fields are used for matching. When you specify ["Email"], the API searches for any existing Contact record with a matching email address.
However, the duplicate check also considers:
- Soft-deleted records (in the Recycle Bin) that still exist in the database
- Records in other modules if your duplicate detection rules span multiple modules
- Case-insensitive email matching (“email@example.com” matches “EMAIL@EXAMPLE.COM”)
To diagnose which records are causing duplicates, use the search API before your bulk insert: GET /crm/v2/Contacts/search?email=new.contact@example.com to verify if records exist that aren’t visible in your UI search.
Bulk API Error Handling: The bulk API’s error responses are limited in detail for performance reasons. When processing 100 records, individual failures don’t include full diagnostic information. To get better error details:
- Reduce batch size to 10-20 records when troubleshooting
- Enable detailed error responses by adding
trigger parameter: `“trigger”: [“approval”, “workflow”, “blueprint”]
- Check the response’s
details array which contains per-record status
The upsert endpoint should update existing records, but it will fail if:
- The existing record has field-level security preventing updates
- Required fields are missing in your payload that weren’t required during initial creation
- Workflow validation rules fail on the existing record
Update Existing Option: For proper campaign member management, you’re using the wrong endpoint. The Contacts upsert endpoint with Campaign_ID field doesn’t establish campaign membership correctly. Instead, use the Campaign Members API:
POST /crm/v2/Campaigns/{campaign_id}/Actions/add_members
{
"Members": [
{"Email": "contact@example.com"},
{"Email": "another@example.com"}
]
}
This endpoint:
- Automatically handles duplicates (won’t fail if contact already is a member)
- Links existing contacts by email without requiring Contact ID
- Creates new contacts if they don’t exist (depending on settings)
- Properly establishes campaign-contact relationships
If you need to bulk import contacts AND add them to campaigns simultaneously, use a two-step process:
- First, bulk upsert contacts with trigger workflows disabled to avoid duplicate errors from validation rules
- Then, use the campaign members API to associate them with campaigns
For the upsert operation specifically, modify your approach to handle duplicates gracefully:
POST /crm/v2/Contacts/upsert
{
"data": [...],
"duplicate_check_fields": ["Email"],
"skip_mandatory": false,
"trigger": []
}
Set trigger to empty array to bypass workflow rules during bulk import, which often cause unexpected duplicate detection. After importing, run a separate API call to add the contacts to campaigns using their newly created IDs or email addresses via the Campaign Members endpoint.
This separation of concerns - contact creation vs. campaign membership - aligns with Zoho’s API design and prevents the duplicate errors you’re experiencing.