Move A Contact Between Campaigns
li_move_contactMove people's membership from one campaign to another, keeping their invite and message history — nothing is re-created, so the sequence continues where it was. This is how you correct a batch that landed in the wrong campaign; before it existed the only option was to stop every person and re-import them, which loses their history. Both campaigns must send from the SAME account: a campaign is bound to one LinkedIn login, so moving across accounts would change who is approaching this person mid-sequence. Refused if the target already holds them (one membership per campaign) or is completed. Their scheduled steps belong to the target campaign afterwards. ⚠️ Moving people into a PAUSED campaign holds further INVITES but does NOT hold a message step that is already scheduled — nothing checks the campaign at send time for those. Three forms: one person, a list of profileUrls, or allInCampaign for everyone still in the source, which is the shape a mis-filed import actually has. A list refuses outright if some cannot move rather than splitting the group across two campaigns; allInCampaign previews first and needs the count echoed back before it writes.
MOVE AND RE-COPY in one call: resnapshot=true (or stepTexts) first re-copies each mover's step kinds and delays from the TARGET campaign's strategy, keeping their own text, then moves them. Only for people nothing has happened to yet (invite not sent, no step sent or scheduled); anyone else, or anyone with no text for a target step kind, is named and nothing is moved. Calling again after a failure is safe.
idempotencyKey: pass the same key when retrying after a timeout, 502 or 503; a call that already finished returns its stored answer instead of running twice (24h).
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| dryRun | No | Preview the move (and the re-copy) without writing. | |
| partial | No | Move the ones that can move even though some cannot. Default false. | |
| contactId | No | One contact by id, instead of profileUrl | |
| stepTexts | No | Per-person text for target step kinds a mover has no text for (or to replace theirs). Implies resnapshot. | |
| profileUrl | No | One person | |
| resnapshot | No | Re-copy each mover's steps from the target campaign's strategy before moving (kinds and delays; their own text kept). Untouched people only. Not with contactId. | |
| expectCount | No | How many you expect to move, from the preview. The call is refused if the campaign holds a different number — which is what stops a mistyped fromCampaignId moving a live campaign wholesale. | |
| profileUrls | No | Several people in ONE call, max 500. Every one is checked BEFORE anything is written: if some cannot move the whole call is refused, because moving the rest would split the group across two campaigns — which is the problem you are fixing, made harder to see. Pass partial=true to move the ones that can. | |
| toCampaignId | Yes | Where they should be instead | |
| allInCampaign | No | Move EVERYONE still in the source campaign. The shape a mis-filed import actually has, since nobody holds a list of them. Returns a PREVIEW first and writes nothing until you call again with expectCount set to the number it showed. | |
| fromCampaignId | Yes | The campaign they are in now | |
| idempotencyKey | No | Any unique string for THIS request (e.g. a UUID). Reuse it only to retry the same call. |