prospect/post_bulk
Tool: create
bulk-upsert prospects by email into a list and/or campaign.
Upserts each prospect by email (inserts new, updates existing) and optionally adds all to a list and/or campaign. Returns counters and prospect IDs for every affected row.
This is the campaign-enrolment path. Passing
campaignId(see Params) adds contacts to a campaign — the intended, safe use for enrolling new contacts. The merge caveat below is about values you explicitly supply overwriting existing ones — it has nothing to do with enrolment, and omitted fields are never at risk. End-to-end flow: sending-a-campaign.
Upsert is keyed by
nullhere: it is silently dropped, yet the response still reports the prospect as updated, so it looks like a successful write while the old value survives (call-contract). To update a single existing prospect,prospect/patch_by_idis more direct. WithoutlistId, prospects are subscribed to the account's 'Unassigned' list (subscriptionsAdded:1reflects that implicit assignment). The Unassigned list is an ordinary list — it can be renamed or deleted like any other.
Max 100 rows per call (split larger imports). Enrolling into a Running campaign starts sending to those prospects immediately — get the user's confirmation first.
Naming trap: this op takes query param
listId; single create (prospect/post) takes body fieldbaseListId. They are NOT interchangeable.
Campaign enrollment: passing
campaignIdenrolls prospects into the campaign's FIRST sequence; there is no sequence-targeting option.
campaignAdded: -1is a sentinel, not a count — it can come back in place of0/1, sometimes withtotalProcessed: 0. Its meaning is unspecified, so it is evidence neither of enrolment nor of a skip: settle it by reading prospect/get withincludeCampaignIds.campaignIds: [<campaignId>].
Conditional — shown while this account's documentation includes the Outreach Playbook
Load quality is decided before this call. Email finding and verification gives the bounce numbers to hold a list to — total under 2%, hard bounces under 1% — why to verify before every send rather than once at build time, and how to handle catch-all and role addresses. List hygiene and data quality covers what else makes an import safe: B2B contact data decays 22-28% a year, deduplicate by person and by company, and opt-outs and hard bounces must stay suppressed across list rebuilds.
Arguments
| name | type | req | notes |
|---|---|---|---|
| listId | integer | – | List to assign all prospects to; omitted → Unassigned list |
| campaignId | integer | – | Enrols into campaign's first sequence |
| addOnlyIfNew | boolean | – | Skip prospects already in CRM when true |
| notInOtherCampaign | boolean | – | Skip prospects enrolled in any other campaign when true |
Body
| field | type | req | constraints | description |
|---|---|---|---|---|
| prospects | array | ✓ | ≥1 items | Array of prospect objects; each is upserted by email |
| prospects[].email | string | ✓ | ≤256 chars | Email key for the upsert |
| prospects[].firstName | string | – | ≤512 chars | Omitted → preserved on existing prospects; send "" to clear — never null |
| prospects[].lastName | string | – | ≤512 chars | Omitted → preserved on existing prospects; send "" to clear — never null |
| prospects[].company | string | – | ≤512 chars | Omitted → preserved on existing prospects; send "" to clear — never null |
| prospects[].sendingStatus | string | – | see schemas/prospect | Campaign participation state |
| prospects[].sendingActive | boolean | – | Whether eligible for campaign sending | |
| prospects[].industry | string | – | ≤512 chars | |
| prospects[].city | string | – | ≤512 chars | |
| prospects[].website | string | – | ≤512 chars | |
| prospects[].phone | string | – | ≤512 chars | |
| prospects[].country | string | – | ≤512 chars | |
| prospects[].domain | string | – | ≤180 chars | |
| prospects[].companySocial | string | – | ≤512 chars | |
| prospects[].companySize | string | – | ≤32 chars | |
| prospects[].jobPosition | string | – | ≤512 chars | |
| prospects[].location | string | – | ≤512 chars | |
| prospects[].personalSocial | string | – | ≤512 chars | |
| prospects[].customImageUrl | string | – | ≤256 chars | |
| prospects[].screenshotUrl | string | – | ≤256 chars | |
| prospects[].logoUrl | string | – | ≤256 chars | |
| prospects[].state | string | – | ≤128 chars | |
| prospects[].icebreaker | string | – | ≤4000 chars | |
| prospects[].custom1–custom20 | string | – | ≤2000 chars each | Free-text custom fields |
| prospects[].notes | string | – | Internal notes |
Response
201 — upsert summary with IDs; 200 — empty OK (no-op path).
| field | type | description |
|---|---|---|
| totalProcessed | integer | Total prospects processed (inserts + updates) |
| prospectsInserted | integer | New prospects created |
| prospectsUpdated | integer | Existing prospects updated |
| duplicatesInBatch | integer | Duplicate emails removed from the batch before processing |
| subscriptionsAdded | integer | List subscriptions created (includes implicit Unassigned assignment) |
| campaignAdded | integer | Prospects enrolled into the campaign (only when campaignId supplied); -1 is a sentinel, not a count — see the note above |
| prospectIds | array | Prospect IDs affected by this operation |
| prospectIdByEmail | object | Map of normalized email → prospectId for affected rows |
Errors: 400, 401, 404, 406, 409, 415, 422, 429, 500 — shared schema: schemas/errors.
Invoke
Call create with name: "prospect/post_bulk".