> 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/update-a-contact.md).

# Update a Contact

Change or clear fields on a contact that is already in a list.

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

Finds the contact in `listname` that matches `match`, writes the values in `fields` and empties the fields named in `clear_fields`. Use it when your own data is newer than what the list holds — a new job title, a corrected email, a phone number that should go.

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

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

{% hint style="danger" %}
**There is no undo.** The old values are replaced. The dry run is the safety net.
{% 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. One word, not `list_name`.                     |
| `match`               | object           | **Required.** How to find the contact. See [Matching](#matching) below.                  |
| `fields`              | object           | Values to write. Same field names as [Add a Contact](/manage-lists/add-a-contact.md).    |
| `clear_fields`        | array of strings | Field names to empty.                                                                    |
| `custom_fields`       | object           | Values for the list's [custom columns](/manage-lists/custom-columns.md), by column name. |
| `clear_custom_fields` | array of strings | Custom column names to empty.                                                            |
| `dry_run`             | boolean          | Default `false`. When `true`, reports what would change and writes nothing.              |

Send at least one of `fields`, `clear_fields`, `custom_fields` or `clear_custom_fields`.

## Matching

`match` takes one or more of `email`, `person_linkedin_url`, `person_name` (with `organization_name` to narrow it to one company), `company_linkedin_url`, `domain`, `organization_name` or `place_id`.

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.

You do not need to match the stored formatting.

| Field                  | What is ignored                                                                             |
| ---------------------- | ------------------------------------------------------------------------------------------- |
| `email`                | Case and surrounding spaces.                                                                |
| `person_linkedin_url`  | `https://`, `www.` and a trailing slash, plus case.                                         |
| `company_linkedin_url` | The same.                                                                                   |
| `domain`               | Scheme, `www.` and case. `acmeroofing.com` matches a stored `https://www.acmeroofing.com/`. |
| `person_name`          | Case and extra spaces.                                                                      |
| `organization_name`    | Case and extra spaces.                                                                      |

## How fields are written

* A value in `fields` **replaces** what is stored.
* A `null` or empty `""` value in `fields` is **skipped**. It never clears anything, so a blank cell in your source data cannot wipe a stored value by accident.
* To empty a field, name it in `clear_fields`.
* A field that is in both `fields` and `clear_fields` is refused.
* Changing `email` without also sending `email_status` clears the stored email status, since it described the old address. It is listed in `fields_cleared`.

The log, `list_name` and the results of AI and ICP enrichments cannot be set or cleared.

## Custom columns

A list's [custom columns](/manage-lists/custom-columns.md) are filled with `custom_fields`, keyed by the column's name (any case). Each value is checked against the column's type: a Select takes one of its options, a Number a number, a Checkbox `true` or `false`, a Date `2026-10-20` or `{"start", "end"}`. A value that does not fit refuses the call with the reason and writes nothing. A custom column name sent in `fields` works too. The same blank and clearing rules apply: a blank value is skipped, and `clear_custom_fields` empties.

```json
{
  "api_key": "your_api_key",
  "listname": "Q4 Roofers",
  "match": {"email": "jane@acmeroofing.com"},
  "custom_fields": {"Account Tier": "Tier 1", "Contacted": true, "Tags": ["Hot"]}
}
```

## History

Every updated contact gets one line in its history naming what changed, for example `Update Fields - Parvenu - Updated current_title, email; cleared email_status, phone_number ✅ ... - 0 Credits - API`. Custom columns are named the same way (`Updated Account Tier`).

## Response

| Field                 | Description                                                                      |
| --------------------- | -------------------------------------------------------------------------------- |
| `status`              | `success`, or `not_found` when nothing in the list matched.                      |
| `data.list_name`      | The list.                                                                        |
| `data.rows_matched`   | How many contacts matched.                                                       |
| `data.rows_updated`   | How many were changed.                                                           |
| `data.fields_updated` | Which fields were written, custom columns included.                              |
| `data.fields_cleared` | Which fields were emptied, including an email status cleared by an email change. |
| `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 changed. A list name that does not exist is different: that returns a `not_found` error.

## Example

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

```json
{
  "api_key": "your_api_key",
  "listname": "Q4 Roofers",
  "match": {
    "email": "jane@acmeroofing.com"
  },
  "fields": {
    "current_title": "VP Sales",
    "email": "jane.new@acmeroofing.com",
    "city": ""
  },
  "clear_fields": ["phone_number"]
}
```

{% endtab %}

{% tab title="Output" %}

```json
{
  "status": "success",
  "credits_used": 0.0,
  "credits_remaining": 6042.2,
  "message": "Updated 1 contact in 'Q4 Roofers'.",
  "data": {
    "list_name": "Q4 Roofers",
    "rows_matched": 1,
    "rows_updated": 1,
    "fields_updated": ["current_title", "email"],
    "fields_cleared": ["email_status", "phone_number"],
    "matched_on": ["email"],
    "dry_run": false
  }
}
```

{% endtab %}
{% endtabs %}

The title and email were written. `city` was sent empty, so it was skipped and the stored city is unchanged. `phone_number` was cleared as asked, and `email_status` was cleared because the email changed without a new status.

## No match

```json
{
  "status": "not_found",
  "credits_used": 0.0,
  "message": "No contacts in this list matched.",
  "data": {
    "list_name": "Q4 Roofers",
    "rows_matched": 0,
    "rows_updated": 0,
    "fields_updated": [],
    "fields_cleared": [],
    "matched_on": [],
    "dry_run": false
  }
}
```

Nothing changed and nothing was charged. Check the list name and the spelling of the identifier.
