> 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-contacts-in-bulk.md).

# Move Contacts in Bulk

Move many contacts out of one list and into another in a single call.

<mark style="color:green;">`POST`</mark> `/move_contacts_bulk`

Moves many contacts out of `listname` and into `destination_listname` in one call. Same matching and the same merge behaviour as [Move a Contact](/manage-lists/move-a-contact.md).

{% hint style="success" %}
**The contacts keep everything.** Every column and every existing log line moves with them. One line is added to each row's history recording the move.
{% endhint %}

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

{% hint style="info" %}
**Synchronous, not a background job.** Unlike the bulk enrichment endpoints, this returns the result immediately. There is no `job_id`, and you do not need to call Check List Status or Download List Data afterwards.
{% endhint %}

{% hint style="warning" %}
**Send `dry_run: true` first** on anything but a handful of contacts. It reports how many would move and moves nothing. That count, not the number of rows you think you selected, is what to show a user.
{% 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 contacts are in now.                                      |
| `destination_listname`  | string           | **Required.** The list to move them to.                                              |
| `create_destination`    | boolean          | Default `true`. Creates the destination list when it does not exist.                 |
| `dry_run`               | boolean          | Default `false`. When `true`, reports what would move and moves nothing.             |
| `contacts`              | array of objects | The precise shape. Each entry carries one contact's own identifiers.                 |
| `emails`                | array of strings | Email addresses to move.                                                             |
| `person_linkedin_urls`  | array of strings | LinkedIn profile URLs to move.                                                       |
| `person_names`          | array of strings | Full names to move. A name shared by two people moves both.                          |
| `organization_names`    | array of strings | Company names. **Moves every row at that company**, which is the point of the field. |
| `domains`               | array of strings | Company domains to move.                                                             |
| `company_linkedin_urls` | array of strings | Company LinkedIn page URLs to move.                                                  |

Pass at least one contact, in `contacts` or in one of the arrays. Passing none returns `422`. The two shapes can be mixed in one call.

### Which shape to use

Use the flat arrays when you hold a list of one kind of value — a column of emails, a column of LinkedIn URLs. Each value becomes a contact of its own.

Use `contacts` when a name needs its company beside it, so the two travel together rather than being paired up by position across two arrays. Each entry accepts `email`, `person_linkedin_url`, `person_name`, `organization_name`, `domain`, `company_linkedin_url` and `place_id`.

## Limits

Up to **5,000 contacts** and **10,000 matching rows** in one call. Past either, the call is refused rather than quietly truncated — move them in batches.

## Response

The same fields as [Move a Contact](/manage-lists/move-a-contact.md), totalled across the whole call.

| Field                        | Description                                                                           |
| ---------------------------- | ------------------------------------------------------------------------------------- |
| `status`                     | `success`, or `not_found` when nothing in the source list matched.                    |
| `data.list_name`             | The list the contacts came from.                                                      |
| `data.destination_list_name` | The list they 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.                                                     |
| `data.rows_merged`           | How many were folded into contacts the destination already had. Part of `rows_moved`. |
| `data.matched_on`            | Which identifier fields were used across the call.                                    |
| `data.dry_run`               | Whether this was a dry run.                                                           |
| `credits_used`               | Always `0`.                                                                           |

## Example

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

```json
{
  "api_key": "your_api_key",
  "listname": "Roofing Owners in California",
  "destination_listname": "Q4 Outreach",
  "emails": [
    "jane@example.com"
  ],
  "contacts": [
    {
      "person_name": "Sam Rivera",
      "organization_name": "Acme Roofing"
    }
  ]
}
```

{% 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": 2,
    "rows_merged": 0,
    "matched_on": ["email", "person_name"],
    "dry_run": false
  },
  "credits_used": 0,
  "credits_remaining": 1734.5,
  "message": "Moved 2 contacts to 'Q4 Outreach'."
}
```

{% endtab %}
{% endtabs %}

The two shapes are mixed here: one contact identified by email, one by name plus company. `matched_on` lists both fields that were used.

## Moving contacts back

A move back is just another move, with the two list names swapped. Send `create_destination: false` so a typo in the list name fails loudly instead of creating a new list.

## 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.
