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 concept page or the related PUT semantics warning, 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.
- The Virtuous Contact ID (either stored from a previous sync, or looked up via
GET /api/Contact/Find— see Create a Contact). - 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 isGET-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 safe update pattern
Retrieve the current Contact record
Call
GET /api/Contact/{contactId} to retrieve the full current state. This is the baseline you will modify.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.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.Reference implementation
...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: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.
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.Field typing on update payloads
The CRM+ spec types every Contact field — including booleans, integers, and dates — asstring (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.
Common update scenarios
Change a primary email
The email lives on a ContactIndividual’scontactMethods array. To change it:
GET the parent Contact
GET /api/Contact/{contactId} to retrieve the Contact with all its individuals and their contact methods.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.Mark a donor deceased
SetisDeceased: true on the appropriate ContactIndividual:
JavaScript
Update a custom field value
Custom field updates work the same way as standard fields — modify thecustomFields array on the Contact, then PUT the full record. See Custom Fields.
Add a new external reference
To add an additionalcontactReferences entry (e.g., the donor signed up for a second integrated product), read the Contact, append to the array, and PUT:
JavaScript
Archive a Contact
Use the dedicated archive endpoint rather than settingisArchived via PUT:
cURL
PUT /api/Contact/Unarchive/{contactId} reverses the archive. See Statuses and Lifecycle States.
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.
Concurrent updates
The CRM+ API does not provide anIf-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.
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. |
422 Unprocessable Entity | Validation failed | Read error.details[] for field-specific messages. |
Where to go next
Create a Contact
The companion workflow — including the lookup pattern that returns the IDs you’ll need for updates.
Build a Two-Way Sync
How to combine create, update, and webhook-driven inbound changes into a continuous sync.
Statuses and Lifecycle States
The dedicated endpoints for archive/unarchive transitions that you should not implement via PUT.
Custom Fields
Reading and writing custom field values on Contacts — same PUT pattern, different schema discovery.