POST/campaigns/:campaignId/leads/batch-update
Update tag and/or excluded for up to 100 leads in one call. Prefer over many PATCH …/leads/:leadId calls.
0 credits. Rate class update_leads_batch (default 6/min). Max 100 items. Optional Idempotency-Key (max 128) — server persists a replay when summary.updated > 0.
Exclude is workspace-global when the lead has an email. Auto-exclude tags (Not interested, Wrong person) apply immediately (no polite-ack defer — differs from single PATCH / inbox).
Request
POST /campaigns/search-xyz/leads/batch-update
X-API-Key: sk_live_...
Content-Type: application/json
Idempotency-Key: client-unique-batch-update-1
{
"updates": [
{ "leadId": "lead_abc", "excluded": true },
{ "leadId": "lead_def", "tag": "Interested" }
]
}
| Field | Required | Notes |
|---|---|---|
updates | Yes | Non-empty array, length 1…100 |
updates[].leadId | Yes | Numeric ids normalize to lead_N |
updates[].tag | No | Writable allowlist (same as PATCH): Interested, Not interested, Wrong person, Meeting booked, Resolved; or null to clear |
updates[].excluded | No | boolean |
At least one of tag / excluded per item. Unknown top-level keys → 400.
Soft per-item outcomes (not envelope errors):
- Bad / unknown
leadId→not_found - Duplicate
leadIdin the same request → first wins; later copies →validation_error - Auto-exclude tag (
Not interested/Wrong person) withexcluded: false→validation_error
Idempotency: array order matters. Persist when summary.updated > 0 (including durable mid-batch partial success that still returns 200).
Response
{
"results": [
{
"index": 0,
"leadId": "lead_abc",
"status": "updated",
"tag": null,
"excluded": true
},
{
"index": 1,
"leadId": "lead_def",
"status": "updated",
"tag": "Interested",
"excluded": false
}
],
"summary": {
"total": 2,
"updated": 2,
"not_found": 0,
"validation_error": 0,
"failed": 0
},
"creditsCharged": 0
}
Per-item status: updated | not_found | validation_error | failed.
If the handler fails mid-batch after durable writes, response is still 200 with completed rows as updated (only fields that actually landed — never infer excluded from an auto-exclude tag alone) and remaining as failed.
vs single PATCH
| PATCH | batch-update | |
|---|---|---|
| Auto-exclude defer | Possible | No — immediate |
| Global exclude | Per-lead | One coalesced workspace exclude |
| Rate | ~60/min | 6/min |
| Idempotency-Key | No | Optional |
Errors
| Error code | When |
|---|---|
validation_error | Empty/updates over max / unknown body keys / bad Idempotency-Key |
not_found | Unknown campaign |
conflict | Idempotency mismatch / in progress |
upstream_unavailable | Idempotency store unavailable, or hard failure before any durable work |
Related
- Single lead: PATCH …/leads/:leadId
- List excluded: GET …/leads?excluded=true
- Credits & RPM: Credits and rate limits
- Permanent delete: POST …/leads/batch-delete
MCP
Tool: update_leads_batch (0 credits).