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

# Update a Contact

> How to safely modify an existing Contact in CRM+ without overwriting fields you did not intend to change.

Updating a Contact in CRM+ looks like a simple `PUT /api/Contact/{contactId}` call — but the safe pattern involves more steps than the endpoint signature suggests. CRM+'s PUT semantics are ambiguous (the spec declares full-replacement PUT, but the live API typically behaves as PATCH), and the field-typing in the spec doesn't match the live API. This workflow walks through the pattern that works correctly regardless of which behavior the platform settles on.

If you have not read the [Contacts](/crm/concepts/contacts) concept page or the related [PUT semantics warning](/crm/concepts/contacts#updating-contacts), start there.

## Scenario

Your platform has detected a change on a donor's record — a new address from a USPS validation service, an updated email after a bounce-back, a phone number correction submitted through your portal. You need to push the change to the corresponding Contact in CRM+ without disturbing any of the other fields on the record.

## Prerequisites

* A valid CRM+ API token — see [Authentication](/crm/authentication).
* The Virtuous Contact ID (either stored from a previous sync, or looked up via `GET /api/Contact/Find` — see [Create a Contact](/crm/workflows/create-a-contact#step-2-pre-create-lookup)).
* The specific fields you intend to change.

***

## The PUT-as-PATCH ambiguity

The single most important thing to understand about updating in CRM+: **the safe pattern is `GET`-then-`PUT` with the full record**, regardless of how the API actually behaves.

| Possible API behavior        | What happens if you PUT a partial body                                                          |
| ---------------------------- | ----------------------------------------------------------------------------------------------- |
| PUT is true full-replacement | Fields omitted from your request are **cleared** to null. Likely catastrophic.                  |
| PUT behaves as PATCH         | Fields omitted from your request are **preserved**. The intended behavior most partners assume. |

The CRM+ spec declares the first behavior; the live API reportedly behaves as the second on most endpoints (with the [Relationship PUT](/crm/concepts/relationships-and-ids#working-with-relationships) being a confirmed exception). Until the spec and the live behavior are reconciled, the only pattern that is correct under both interpretations is to send the complete record on every PUT — with your intended changes applied to the full data you read first.

<Warning>
  Do not send "partial" PUT bodies that omit fields you don't want to change. Even if the API today behaves as PATCH and treats omissions as "keep existing value," that behavior is not guaranteed in the spec and could change. The `GET`-then-`PUT` pattern is correct under both interpretations and adds at most one extra request per update.
</Warning>

***

## The safe update pattern

<Steps>
  <Step title="Retrieve the current Contact record">
    Call `GET /api/Contact/{contactId}` to retrieve the full current state. This is the baseline you will modify.
  </Step>

  <Step title="Apply your changes to the retrieved record">
    Mutate only the fields you intend to change. Leave every other field as-is. Be especially careful with sub-resource arrays (`contactIndividuals`, `address`) — replacing an array wholesale will remove individuals or addresses you didn't intend to touch.
  </Step>

  <Step title="Send the complete record back as PUT">
    Call `PUT /api/Contact/{contactId}` with the full modified record. This is safe under both PUT and PATCH semantics.
  </Step>

  <Step title="Verify the update via the response or a follow-up GET">
    The PUT response should reflect your changes. For high-stakes updates (financial fields, deletion flags), follow up with a GET to confirm the persisted state.
  </Step>
</Steps>

### Reference implementation

<CodeGroup>
  ```bash cURL theme={null}
  # Step 1: Read the current Contact
  curl https://api.virtuoussoftware.com/api/Contact/4821 \
    -H "Authorization: Bearer YOUR_API_TOKEN" \
    -o current-contact.json

  # Step 2: Modify the file locally — change the address1 field
  # (use jq, a text editor, or your scripting tool of choice)

  # Step 3: Send the full modified record back
  curl -X PUT https://api.virtuoussoftware.com/api/Contact/4821 \
    -H "Authorization: Bearer YOUR_API_TOKEN" \
    -H "Content-Type: application/json" \
    --data-binary @current-contact.json
  ```

  ```javascript JavaScript theme={null}
  async function updateContactSafely(contactId, changes) {
    const baseUrl = 'https://api.virtuoussoftware.com';
    const token = process.env.VIRTUOUS_API_TOKEN;
    const headers = {
      Authorization: `Bearer ${token}`,
      'Content-Type': 'application/json',
    };

    // Step 1: Read the current Contact
    const getResponse = await fetch(`${baseUrl}/api/Contact/${contactId}`, { headers });
    if (!getResponse.ok) {
      throw new Error(`Read failed: ${getResponse.status}`);
    }
    const current = await getResponse.json();

    // Step 2: Apply changes to the full record
    const updated = { ...current, ...changes };

    // Step 3: PUT the complete modified record
    const putResponse = await fetch(`${baseUrl}/api/Contact/${contactId}`, {
      method: 'PUT',
      headers,
      body: JSON.stringify(updated),
    });
    if (!putResponse.ok) {
      throw new Error(`Update failed: ${putResponse.status}`);
    }
    return await putResponse.json();
  }

  // Usage: change only the description field
  const updated = await updateContactSafely(4821, {
    description: 'Updated by sync at 2025-01-15',
  });
  ```
</CodeGroup>

The JavaScript example's spread operator (`...current, ...changes`) merges the change set on top of the current record. This is the safest pattern for top-level field changes. For nested fields (changing one ContactIndividual inside the `contactIndividuals` array), use the sub-resource update endpoints described below.

***

## Updating sub-resources

A Contact contains three classes of sub-resources, each with its own dedicated update endpoint. Use the sub-resource endpoint for changes that affect only one item — it avoids the risk of accidentally clearing other items in the array.

| Sub-resource      | Update endpoint                                                   |
| ----------------- | ----------------------------------------------------------------- |
| ContactAddress    | `PUT /api/ContactAddress/{contactAddressId}`                      |
| ContactIndividual | `PUT /api/ContactIndividual/{contactIndividualId}`                |
| ContactMethod     | Updated via the parent ContactIndividual (no standalone endpoint) |

### Updating an address

Use the address's own ID to update it directly:

<CodeGroup>
  ```bash cURL theme={null}
  # Read the address first (the ID is in the Contact response's address.id field)
  curl https://api.virtuoussoftware.com/api/ContactAddress/51001 \
    -H "Authorization: Bearer YOUR_API_TOKEN"

  # Update the full address record
  curl -X PUT https://api.virtuoussoftware.com/api/ContactAddress/51001 \
    -H "Authorization: Bearer YOUR_API_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "id": 51001,
      "label": "Home",
      "address1": "1007 Mountain Drive",
      "address2": null,
      "city": "Gotham",
      "state": "NJ",
      "postal": "07001",
      "country": "US",
      "isPrimary": true
    }'
  ```

  ```javascript JavaScript theme={null}
  async function updateAddress(addressId, changes) {
    const baseUrl = 'https://api.virtuoussoftware.com';
    const headers = {
      Authorization: `Bearer ${process.env.VIRTUOUS_API_TOKEN}`,
      'Content-Type': 'application/json',
    };

    // Read current
    const current = await fetch(`${baseUrl}/api/ContactAddress/${addressId}`, {
      headers,
    }).then((r) => r.json());

    // Modify
    const updated = { ...current, ...changes };

    // Write
    const response = await fetch(`${baseUrl}/api/ContactAddress/${addressId}`, {
      method: 'PUT',
      headers,
      body: JSON.stringify(updated),
    });
    return await response.json();
  }
  ```
</CodeGroup>

### Updating an individual

`PUT /api/ContactIndividual/{contactIndividualId}` updates the person record inside a Contact. Use this when changes apply to a specific person (first name, last name, birth date) rather than the household as a whole.

<Note>
  The CRM+ spec description on `PUT /api/ContactIndividual/{contactIndividualId}` does **not** explicitly state full-replacement semantics — but the audit (finding #18) notes that this is an implicit constraint of the PUT model. As with Contact, follow the `GET`-then-`PUT` pattern to be safe.
</Note>

***

## Field typing on update payloads

The CRM+ spec types every Contact field — including booleans, integers, and dates — as `string` (audit findings #5–#8). The live API accepts the natural types: send booleans as `true`/`false`, send integers as numbers, send dates as ISO 8601 strings.

```json theme={null}
{
  "isPrivate": false,                          // boolean, not "false"
  "anniversaryYear": 2010,                     // integer, not "2010"
  "contactIndividuals": [
    {
      "id": 9012,
      "birthDate": "1972-02-19",               // ISO 8601 date string
      "isPrimary": true,                       // boolean
      "isDeceased": false                      // boolean
    }
  ]
}
```

<Warning>
  Strict SDK generators that read the spec literally will type these fields as strings and may serialize values as `"true"` or `"123"`. Verify that your HTTP client sends the natural types. If you are using an auto-generated SDK and seeing unexpected results, this type mismatch is the most likely cause.
</Warning>

***

## Common update scenarios

### Change a primary email

The email lives on a ContactIndividual's `contactMethods` array. To change it:

<Steps>
  <Step title="GET the parent Contact">
    `GET /api/Contact/{contactId}` to retrieve the Contact with all its individuals and their contact methods.
  </Step>

  <Step title="Modify the ContactMethod in the contactIndividuals array">
    Find the ContactIndividual with `isPrimary: true`, find their existing primary email ContactMethod, and update its `value`. Or add a new ContactMethod with `isPrimary: true` and set the existing primary's `isPrimary` to false.
  </Step>

  <Step title="PUT the full Contact back">
    `PUT /api/Contact/{contactId}` with the modified record.
  </Step>
</Steps>

### Mark a donor deceased

Set `isDeceased: true` on the appropriate ContactIndividual:

```javascript JavaScript theme={null}
const contact = await fetch(`https://api.virtuoussoftware.com/api/Contact/${contactId}`, {
  headers: { Authorization: `Bearer ${token}` },
}).then((r) => r.json());

const updated = {
  ...contact,
  contactIndividuals: contact.contactIndividuals.map((ind) =>
    ind.id === deceasedIndividualId ? { ...ind, isDeceased: true } : ind
  ),
};

await fetch(`https://api.virtuoussoftware.com/api/Contact/${contactId}`, {
  method: 'PUT',
  headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
  body: JSON.stringify(updated),
});
```

After marking a ContactIndividual deceased, suppress that individual's contact methods from outbound communications. See [Statuses and Lifecycle States](/crm/concepts/statuses-and-lifecycle-states#handling-deceased-individuals).

### Update a custom field value

Custom field updates work the same way as standard fields — modify the `customFields` array on the Contact, then PUT the full record. See [Custom Fields](/crm/concepts/custom-fields#writing-custom-field-values).

### Add a new external reference

To add an additional `contactReferences` entry (e.g., the donor signed up for a second integrated product), read the Contact, append to the array, and PUT:

```javascript JavaScript theme={null}
const updated = {
  ...contact,
  contactReferences: [
    ...(contact.contactReferences || []),
    { source: 'NewIntegration', id: 'new-id-here' },
  ],
};
```

### Archive a Contact

Use the dedicated archive endpoint rather than setting `isArchived` via PUT:

```bash cURL theme={null}
curl -X PUT https://api.virtuoussoftware.com/api/Contact/Archive/4821 \
  -H "Authorization: Bearer YOUR_API_TOKEN"
```

`PUT /api/Contact/Unarchive/{contactId}` reverses the archive. See [Statuses and Lifecycle States](/crm/concepts/statuses-and-lifecycle-states#archive-and-unarchive).

***

## Verifying updates

After a successful PUT, two patterns confirm the persisted state:

* **The PUT response.** The response body should reflect the updated record. For simple field changes, this is sufficient — the response confirms what was stored.
* **A follow-up GET.** For high-stakes updates (financial fields, ownership transfers, lifecycle transitions), follow up with `GET /api/Contact/{contactId}` to confirm the persisted state independent of the PUT response.

In rare cases, the PUT response may reflect server-side normalization (e.g., trimmed whitespace, canonicalized state codes) that differs from the values you sent. This is expected — store the response's representation, not your input, as the canonical state in your integration's database.

***

## Concurrent updates

The CRM+ API does not provide an `If-Match` / `ETag` mechanism for optimistic concurrency control. If two callers PUT the same Contact at the same time, the later write wins — with no warning.

For partner integrations, this is rarely a problem because:

* Each customer has one partner integration writing on their behalf.
* The Virtuous UI is the other typical writer, and concurrent UI + API writes on the same record at the exact same instant are rare in practice.

If concurrent writes are a concern (e.g., a customer using two integrations that both write to the same Contact field), use the `modifiedDateTimeUtc` field as a soft concurrency check: read the current `modifiedDateTimeUtc` before the GET-then-PUT cycle, and after the PUT compare the response's new `modifiedDateTimeUtc` to confirm no other write landed between your GET and PUT.

***

## Error handling

| Status                     | Cause                                                                | Action                                                                                                            |
| -------------------------- | -------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `400 Bad Request`          | Malformed JSON or type mismatch                                      | Inspect the response body; check spec-vs-live field types.                                                        |
| `404 Not Found`            | Contact ID does not exist                                            | Confirm the ID; the Contact may have been merged or deleted.                                                      |
| `409 Conflict`             | Unique-constraint violation (e.g., email already on another Contact) | Reconcile the duplicate before retrying. See [Handle Duplicate Records](/crm/workflows/handle-duplicate-records). |
| `422 Unprocessable Entity` | Validation failed                                                    | Read `error.details[]` for field-specific messages.                                                               |

***

## Where to go next

<CardGroup cols={2}>
  <Card title="Create a Contact" icon="user-plus" href="/crm/workflows/create-a-contact">
    The companion workflow — including the lookup pattern that returns the IDs you'll need for updates.
  </Card>

  <Card title="Build a Two-Way Sync" icon="arrow-right-arrow-left" href="/crm/workflows/build-a-two-way-sync">
    How to combine create, update, and webhook-driven inbound changes into a continuous sync.
  </Card>

  <Card title="Statuses and Lifecycle States" icon="circle-half-stroke" href="/crm/concepts/statuses-and-lifecycle-states">
    The dedicated endpoints for archive/unarchive transitions that you should not implement via PUT.
  </Card>

  <Card title="Custom Fields" icon="square-plus" href="/crm/concepts/custom-fields">
    Reading and writing custom field values on Contacts — same PUT pattern, different schema discovery.
  </Card>
</CardGroup>
