> For the complete documentation index, see [llms.txt](https://developer.parvenu.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developer.parvenu.io/manage-lists/move-a-contact.md).

# Move a Contact

Move one contact out of a list and into another, keeping every column and its enrichment history.

<mark style="color:green;">`POST`</mark> `/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.

{% hint style="success" %}
**The contact keeps everything.** Every column and every existing log line moves with it, because the row itself is re-pointed at the new list rather than copied and deleted. One line is added to its history recording the move.
{% endhint %}

{% hint style="info" %}
**Free.** `credits_used` is always `0`.
{% endhint %}

{% hint style="warning" %}
**Every match moves.** A full name shared by two people in the list moves both, and a bare `organization_name` moves everyone at that company. Send `dry_run: true` first and check `rows_matched`.
{% endhint %}

{% hint style="danger" %}
**There is no undo.** The dry run is the safety net. The log line on each row records which list it came from, so a move back is a second move, done by hand.
{% endhint %}

## Headers

| Name         | Value              |
| ------------ | ------------------ |
| Content-Type | `application/json` |

## Body

| Name                   | Type    | Description                                                                                                  |
| ---------------------- | ------- | ------------------------------------------------------------------------------------------------------------ |
| `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](/manage-lists/move-contacts-in-bulk.md) 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.

| Field                  | What is ignored                                                                                                                         |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `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

| Field                        | Description                                                                            |
| ---------------------------- | -------------------------------------------------------------------------------------- |
| `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

{% tabs %}
{% tab title="Input" %}

```json
{
  "api_key": "your_api_key",
  "listname": "Roofing Owners in California",
  "destination_listname": "Q4 Outreach",
  "email": "jane@example.com"
}
```

{% endtab %}

{% tab title="Output" %}

```json
{
  "status": "success",
  "data": {
    "list_name": "Roofing Owners in California",
    "destination_list_name": "Q4 Outreach",
    "destination_created": false,
    "rows_matched": 1,
    "rows_moved": 1,
    "rows_merged": 0,
    "matched_on": ["email"],
    "dry_run": false
  },
  "credits_used": 0,
  "credits_remaining": 1734.5,
  "message": "Moved 1 contact to 'Q4 Outreach'."
}
```

{% endtab %}
{% endtabs %}

## Example: checking first

A name can match more than one contact, so check before moving.

{% tabs %}
{% tab title="Input" %}

```json
{
  "api_key": "your_api_key",
  "listname": "Roofing Owners in California",
  "destination_listname": "Q4 Outreach",
  "person_name": "Jane Doe",
  "dry_run": true
}
```

{% endtab %}

{% tab title="Output" %}

```json
{
  "status": "success",
  "data": {
    "list_name": "Roofing Owners in California",
    "destination_list_name": "Q4 Outreach",
    "destination_created": false,
    "rows_matched": 2,
    "rows_moved": 0,
    "rows_merged": 0,
    "matched_on": ["person_name"],
    "dry_run": true
  },
  "credits_used": 0,
  "credits_remaining": 1734.5,
  "message": "2 contacts would move. Nothing has moved."
}
```

{% endtab %}
{% endtabs %}

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.
