> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cometly.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Update Contact

> Update an existing contact's emails, phones, names, locations, or custom fields

## Overview

This endpoint updates an existing contact using **sparse PUT** semantics. Any field you include in the request body is treated as the full, authoritative list for that collection — existing values are deleted and replaced with the values you provide. Any field you omit is left untouched.

This design lets you change one aspect of a contact (e.g. add a phone number) without having to send the contact's entire state. However, you must include the **complete list** for any field you choose to update — sending `emails: ["a@x.com"]` will remove every other email already on the contact.

## Path Parameters

<ParamField path="id" type="integer" required>
  The unique identifier of the contact to update. Merged profile aliases are automatically resolved to the current canonical profile.
</ParamField>

## Request Body

At least one of `emails`, `phones`, `names`, `locations`, or `custom_fields` must be provided.

<ParamField body="emails" type="string[]" placeholder="['john@example.com']">
  Full replacement list of email addresses. Provide the complete authoritative array — anything not included is removed. Provide `[]` to wipe all emails. The first item becomes the primary email on the contact's profile.
</ParamField>

<ParamField body="phones" type="string[]" placeholder="['+1234567890']">
  Full replacement list of phone numbers. Provide `[]` to wipe all phones. The first item becomes the primary phone.
</ParamField>

<ParamField body="names" type="string[]" placeholder="['John Doe']">
  Full replacement list of names. Provide `[]` to wipe all names. The first item becomes the primary name.
</ParamField>

<ParamField body="locations" type="string[]" placeholder="['New York, NY']">
  Full replacement list of locations. Provide `[]` to wipe all locations. The first item becomes the primary location.
</ParamField>

<ParamField body="custom_fields" type="object">
  Full replacement object of custom profile fields keyed by field slug (`profile_field_1` through `profile_field_30`). When provided, every key NOT included is set to `null`. Provide `{}` to wipe all 30 custom fields. Fields 1-15 accept text (max 10,240 chars), 16-25 accept numbers, 26-30 accept dates.
</ParamField>

## Response

### Success Response

Returns the updated contact with only the fields that were modified. Omitted request fields are not echoed back.

<ResponseField name="id" type="integer">
  The unique identifier of the contact (after resolving merged-profile aliases).
</ResponseField>

<ResponseField name="emails" type="array">
  The new list of emails, if `emails` was provided in the request.
</ResponseField>

<ResponseField name="phones" type="array">
  The new list of phone numbers, if `phones` was provided in the request.
</ResponseField>

<ResponseField name="names" type="array">
  The new list of names, if `names` was provided in the request.
</ResponseField>

<ResponseField name="locations" type="array">
  The new list of locations, if `locations` was provided in the request.
</ResponseField>

<ResponseField name="custom_fields" type="object">
  The new custom fields object, if `custom_fields` was provided in the request.
</ResponseField>

### Error Response

<ResponseField name="message" type="string">
  Error description explaining what went wrong.
</ResponseField>

## Example Requests

<CodeGroup>
  ```bash cURL theme={null}
  curl -X PUT "https://app.cometly.com/public-api/v1/contacts/12345" \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Accept: application/json" \
    -H "Content-Type: application/json" \
    -d '{
      "emails": ["john@example.com", "john.doe@example.com"],
      "phones": ["+1234567890"],
      "custom_fields": {
        "profile_field_1": "Enterprise",
        "profile_field_16": "500"
      }
    }'
  ```

  ```javascript JavaScript theme={null}
  const contactId = 12345;
  const response = await fetch(`https://app.cometly.com/public-api/v1/contacts/${contactId}`, {
    method: 'PUT',
    headers: {
      'Authorization': 'Bearer YOUR_API_KEY',
      'Accept': 'application/json',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      emails: ['john@example.com', 'john.doe@example.com'],
      phones: ['+1234567890'],
      custom_fields: {
        profile_field_1: 'Enterprise',
        profile_field_16: '500'
      }
    })
  });

  const data = await response.json();
  console.log('Updated contact:', data);
  ```

  ```php PHP theme={null}
  <?php
  $contactId = 12345;
  $url = 'https://app.cometly.com/public-api/v1/contacts/' . $contactId;
  $headers = [
      'Authorization: Bearer YOUR_API_KEY',
      'Accept: application/json',
      'Content-Type: application/json'
  ];

  $data = [
      'emails' => ['john@example.com', 'john.doe@example.com'],
      'phones' => ['+1234567890'],
      'custom_fields' => [
          'profile_field_1' => 'Enterprise',
          'profile_field_16' => '500',
      ],
  ];

  $ch = curl_init();
  curl_setopt($ch, CURLOPT_URL, $url);
  curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
  curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
  curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

  $response = curl_exec($ch);
  $result = json_decode($response, true);
  curl_close($ch);
  ?>
  ```

  ```python Python theme={null}
  import requests

  contact_id = 12345
  url = f'https://app.cometly.com/public-api/v1/contacts/{contact_id}'
  headers = {
      'Authorization': 'Bearer YOUR_API_KEY',
      'Accept': 'application/json',
      'Content-Type': 'application/json'
  }

  data = {
      'emails': ['john@example.com', 'john.doe@example.com'],
      'phones': ['+1234567890'],
      'custom_fields': {
          'profile_field_1': 'Enterprise',
          'profile_field_16': '500',
      },
  }

  response = requests.put(url, headers=headers, json=data)
  result = response.json()
  ```
</CodeGroup>

## Status Codes

| Status Code | Description                                                                               |
| ----------- | ----------------------------------------------------------------------------------------- |
| 200         | Contact successfully updated                                                              |
| 401         | Missing or invalid API key                                                                |
| 403         | API key doesn't have permission or subscription is inactive                               |
| 404         | Contact not found                                                                         |
| 422         | Invalid parameters provided (check error message for details)                             |
| 429         | Too many requests - rate limit exceeded. See [Rate Limiting](/introduction/rate-limiting) |

## Notes

* **Rate Limit**: This endpoint has a limit of **60 requests per minute** per Space. See [Rate Limiting](/introduction/rate-limiting) for details.
* **Sparse PUT semantics**: Omitted fields are NOT modified. Provided fields are treated as the complete authoritative state for that collection.
* **To add a value without losing existing data**, first call [Get Contact](/api-reference/endpoint/get-contact) to retrieve the current array, then PUT the merged list.
* **Custom fields are all-or-nothing**: providing a `custom_fields` object sets ALL 30 columns — keys not in the object are cleared to `null`. To preserve unchanged fields, include them in the object.
* **Empty arrays wipe a collection**: `phones: []` removes every phone row for the contact and sets the primary phone to `null`.
* **Merged profiles**: If you pass an ID that was merged into another profile, the update is applied to the canonical (current) profile.
* **Transaction Safety**: Updates are wrapped in a database transaction to ensure data consistency.
