Skip to content

Apply one action to up to 1000 contacts

POST
/v1/contacts/bulk
curl --request POST \
--url https://api.vocapable.com/v1/contacts/bulk \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "contact_ids": [ "example" ], "action": "add_to_list", "contact_list_id": "example", "lead_status": "new" }'

The morning-after actions, in one request instead of one at a time.

Every branch is the existing per-contact write, run over a batch - the suppression goes through the revocation path with all its derived writes, the delete is the same soft delete DELETE /contacts/{id} performs, and list membership is the same join POST /contact-lists/{id}/members writes. None of them invents a shortcut, which is why a bulk suppress cannot end up weaker than a single one.

suppress additionally requires dnc:manage: it writes the internal DNC list, and a key trusted to edit contacts is not thereby trusted to change what may be dialed.

Media typeapplication/json
ContactBulkRequest

One action over up to 1000 contacts.

The same cap as POST /contact-lists/{id}/members, for the same reason: the whole action runs in one request and one transaction.

object
contact_ids
required
Contact Ids
Array<string>
>= 1 items <= 1000 items
action
required
Action
string
Allowed values: add_to_list remove_from_list set_lead_status suppress delete
contact_list_id
Any of:
string
lead_status
Any of:
LeadStatus

Where the tenant’s own pipeline holds a contact (migration 0035).

Durable per-contact intent, deliberately separate from Disposition, which is pinned (CANON fact 28) and answers “what happened to the dial” rather than “is this person interested”. A contact is dialed many times and disposed once per attempt; the lead status is the one value that survives across those attempts.

It carries NO compliance meaning whatsoever: LOST is not a suppression, and marking a contact WON neither grants nor implies consent. Suppression is dnc_entries and consent is consent_records - both have their own writers, and neither reads this column.

string
Allowed values: new working qualified unqualified won lost

Successful Response

Media typeapplication/json
ContactBulkResponse

What the action did, per outcome rather than per id.

unknown_contact_ids is enumerated because a caller pasting stale ids has to find out; matched and changed differ whenever an action was already applied - bulk writes are idempotent, so re-running one reports changed = 0 rather than failing.

object
action
required
Action
string
Allowed values: add_to_list remove_from_list set_lead_status suppress delete
requested
required
Requested
integer
matched
required
Matched
integer
changed
required
Changed
integer
unknown_contact_ids
Unknown Contact Ids
Array<string>
Example
{
"action": "add_to_list"
}

Validation Error

Media typeapplication/json
HTTPValidationError
object
detail
Detail
Array<object>
ValidationError
object
loc
required
Location
Array
msg
required
Message
string
type
required
Error Type
string
input
Input
ctx
Context
object
Examplegenerated
{
"detail": [
{
"loc": [
"example"
],
"msg": "example",
"type": "example",
"input": "example",
"ctx": {}
}
]
}