Bulk operations
LeadX has three distinct bulk mechanisms. Picking the right one matters — they have different limits and different failure behaviour.
| You want to | Use | Limit |
|---|---|---|
| Create up to a few hundred records now, and know which ones failed | POST /lead/bulk, POST /contact/bulk | 500 per request |
| Update or delete a selection, possibly "everything matching a filter" | PUT / DELETE on the collection | none |
| Load a spreadsheet of tens of thousands of rows | Import | streamed, resumable |
Bulk create
POST /lead/bulk and POST /contact/bulk take a batch and create each item independently and in order.
The body may be a bare array, or an object under any of items, leads, contacts, records:
curl -X POST https://api.leadx.in/api/key/protected/contact/bulk \
-H "apikey: $KEY" -H "Content-Type: application/json" \
-d '{
"items": [
{ "formData": { "name": "Ada Lovelace", "email": "ada@example.com" } },
{ "formData": { "name": "Alan Turing", "email": "alan@example.com" } }
]
}'
Each item takes the same shape as the single-create body, so anything you can POST /contact you can put in the array.
Partial success is the contract
A bad row fails on its own; the rest still land. The response tells you exactly which:
{
"ok": true,
"success": true,
"message": "1 of 2 contact(s) created.",
"data": {
"requested": 2,
"created": 1,
"failed": 1,
"items": [ { "index": 0, "ok": true, "_id": "665f..." } ],
"errors": [
{ "index": 1, "message": "Contact already exists.", "status": 400 }
]
}
}
index is the position in the array you sent, so you can fix and resend just the failures. The HTTP status is 200 even when some rows failed — check failed, not the status code.
:::warning The cap is 500
Over 500 items returns 400. The limit exists because each item costs a duplicate lookup, a write and a workflow event inside one HTTP request. For bigger loads, use the import pipeline.
:::
Items are created sequentially on purpose: the create path deduplicates against what is already stored, so two identical rows in one payload would both pass that check if they ran concurrently. Running in order means the second one sees the first.
Bulk update
PUT on the collection path (PUT /lead, PUT /contact, PUT /product-service) applies one set of changes to a selection.
{
"ids": ["665f...", "6660..."],
"updateType": "replace",
"formData": { "stage": "Qualified", "priority": "High" }
}
| Field | Type | Notes |
|---|---|---|
ids | string[] | Documents to update. |
nids | string[] | Documents to exclude — meaningful with selectAll. |
selectAll | boolean | Update everything matching the query-string filter instead of a fixed id list. |
formData | object | Fields to set. System fields and custom fields alike. |
updateType | replace | append | append adds to array-valued custom fields instead of overwriting. Default replace. |
Selecting everything that matches a filter
selectAll reads the query string for filters, advancedFilters and searchValue — the same parameters as the list endpoint. So "update every Hot lead" is:
curl -X PUT "https://api.leadx.in/api/key/protected/lead?filters=%7B%22status%22%3A%22Hot%22%7D" \
-H "apikey: $KEY" -H "Content-Type: application/json" \
-d '{ "selectAll": true, "formData": { "priority": "High" } }'
:::danger selectAll with no filter means every record
selectAll: true and an empty query string selects the whole company. Always send a filters or advancedFilters query parameter with it, and dry-run the same filter against GET first to see totalCount.
:::
Bulk delete
DELETE on the collection path, with the selection in the body:
curl -X DELETE https://api.leadx.in/api/key/protected/lead \
-H "apikey: $KEY" -H "Content-Type: application/json" \
-d '{ "ids": ["665f...", "6660..."] }'
Same three fields as bulk update: ids, nids, selectAll, with the same query-string filter behaviour and the same warning.
Sending neither ids nor selectAll is an error (NoLeadSelected_400 and its per-module equivalents) rather than a silent no-op.
Company scoping is always applied on top of your selection, so a bulk delete can never reach another tenant's records even if you pass their ids.
:::note Deletes on leads and contacts are soft
Leads and contacts set isDeleted rather than being removed, and deleted files land in a recycle bin (GET /file/recycle-bin). Most other collections delete outright.
:::