Move a Contact
Move one contact out of a list and into another, keeping every column and its enrichment history.
POST /move_contact
Moves a single contact out of listname and into destination_listname. The contact leaves the first list and arrives in the second. Nothing is duplicated and nothing is re-enriched.
Headers
Content-Type
application/json
Body
api_key
string
Required. Your API key.
listname
string
Required. The list the contact is in now. One word, not list_name.
destination_listname
string
Required. The list to move the contact to. One word, same as above.
create_destination
boolean
Default true. Creates the destination list when it does not exist. Set false to get not_found instead.
dry_run
boolean
Default false. When true, reports what would move and moves nothing.
email
string
The contact's email address.
person_linkedin_url
string
The contact's LinkedIn profile URL.
person_name
string
The contact's full name.
organization_name
string
The company. Narrows a person_name match to one company, or on its own identifies a company row.
domain
string
The company's domain, e.g. openai.com. For a row with no contact on it.
company_linkedin_url
string
The company's LinkedIn page URL. For a row with no contact on it.
place_id
string
Google's id for a Maps listing. For a place row.
Pass at least one of email, person_linkedin_url, person_name, domain, company_linkedin_url or place_id. Passing none returns 422. organization_name on its own is not enough here — use Move Contacts in Bulk and its organization_names array, where moving everyone at a company is unmistakably what was asked for.
The strongest identifier you send is the one used, and it comes back in matched_on. Sending several does not widen the search, it sharpens it.
A dry run changes nothing
No contact moves and no list is created. A dry run against a destination that does not exist leaves no empty list behind; the list is only made on the real call.
If the destination already has that contact
The two rows are merged, not duplicated.
A column that is blank on the destination row is filled in from the row being moved.
A column with a value on both keeps the destination's value.
Both log histories are kept.
Those rows are counted in rows_merged, which is part of rows_moved rather than extra.
How matching works
You do not need to match the stored formatting.
email
Case and surrounding spaces. Matched against every column Parvenu stores an email in, so a contact is found whichever way it was added.
person_linkedin_url
https://, http://, www. and a trailing slash, plus case.
company_linkedin_url
The same.
domain
Scheme, www., path and case. openai.com matches a stored https://www.openai.com/.
person_name
Case and repeated spaces. Also matches a contact whose first and last name together make the name, even when no full name is stored.
Response
status
success, or not_found when nothing in the source list matched.
data.list_name
The list the contact came from.
data.destination_list_name
The list it went to.
data.destination_created
true when the destination did not exist and was made.
data.rows_matched
How many contacts matched.
data.rows_moved
How many moved. 0 on a dry run, otherwise equal to rows_matched.
data.rows_merged
How many were folded into a contact the destination already had. Part of rows_moved.
data.matched_on
Which identifier fields were used.
data.dry_run
Whether this was a dry run.
credits_used
Always 0.
not_found is not an error. It means nothing in that list matched and nothing moved. Check the list name and the spelling of the identifier.
Example
Example: checking first
A name can match more than one contact, so check before moving.
Two contacts share that name. Repeating the call without dry_run would move both. Add organization_name to pick one.
No match
A call that matches nothing answers status: not_found with rows_matched: 0 and the message No contacts in this list matched. Nothing moved and nothing was charged.