Skip to main content
The Contact is the foundational resource in CRM+. Every Gift, every Pledge, every RecurringGift, and every Note references a Contact. Most partner integrations begin by figuring out the Contact model, because everything else depends on it. This page covers the Contact resource and its sub-resources — the relationships, the most-used fields, and the patterns for creating, updating, and looking up Contacts without producing duplicates.

The Contact hierarchy

A Contact in Virtuous represents a household or organization — not a person. The people, addresses, and contact methods inside a Contact are separate sub-resources. The two sub-resources that trip up new partner integrations most often are ContactIndividual and ContactMethod:
  • ContactIndividual is the person record. First name, last name, birth date, and gender live here — not on Contact.
  • ContactMethod is the email or phone record. Email addresses live here — not on Contact or ContactIndividual as a top-level string.
A typical individual donor looks like this in the data model:

Contact types

The contactType field on a Contact identifies what kind of record it is. Common values include Household, Organization, and Foundation, but the exact enum is configured per organization.
The CRM+ spec does not enumerate the valid contactType values in the OpenAPI definition — the field is typed as string. To discover the values valid for a specific organization, call GET /api/Contact/Types. Cache the result; do not hardcode contact type strings in your integration.
The contactType affects which fields are meaningful on the record:
  • Household Contacts represent a family unit. They have one or more ContactIndividuals (typically the adult householders), and gifts are attributed to the household even when one specific individual made the donation.
  • Organization and Foundation Contacts represent institutions. They have ContactIndividuals for the relevant contact people (development director, program officer, etc.), and gifts are attributed to the institution.

Primary and secondary individuals

Within a household Contact, ContactIndividuals carry isPrimary and isSecondary flags. Exactly one individual is the primary; at most one is the secondary. Other individuals (children, extended family) are neither. canBePrimary and canBeSecondary indicate whether an individual is eligible for those roles based on other organization settings.
When creating a new household Contact from your platform, mark the donor as isPrimary: true. If a spouse is also captured, mark them as isSecondary: true. This matches the convention Virtuous uses internally and ensures downstream features (joint giving, household receipts) work as expected.

Addresses

Each Contact has one or more ContactAddress records, exposed both as a top-level address field on the Contact (the primary address) and as a separate resource with its own ID for management. Key fields: A Contact can have multiple addresses (e.g., a seasonal/snowbird donor with a winter and summer address). The isPrimary flag controls which address is used for mailings.
The CRM+ spec types every ContactAddress field — including isPrimary, startMonth, and other typed values — as string. The live API accepts and returns correct types: boolean for flags, integer for IDs and month/day numbers. Send the correct type from your integration; libraries that strictly enforce the spec’s declared types may need a workaround.

Tags, custom fields, and references

Three mechanisms let you attach additional information to a Contact:
  • Tags — simple labels (strings) used for ad-hoc grouping. A Contact can have any number of tags. Tags are organization-defined; see GET /api/Contact/Tags for the list valid in the current organization.
  • Custom fields — typed, named fields configured by the organization. Used for structured data that doesn’t fit the standard schema (e.g., a wealth screening score, a communications preference). See the Custom Fields concept page.
  • Contact references — external system identifiers stored on the Contact. Used to bridge your platform’s IDs with Virtuous IDs (e.g., source: "Stripe", id: "cus_abc123"). See Relationships and IDs.
contactReferences are particularly important for partner integrations — they enable looking up the Virtuous Contact by your platform’s ID via GET /api/Contact/ByReference/{referenceId}, removing the need to store the Virtuous Contact ID in your own database.
GET /api/Contact/ByReference/{referenceId} requires HMAC authentication rather than standard Bearer token auth. See Authentication for details — contact Virtuous support to obtain HMAC credentials if you need to use this lookup pattern.

Statuses and lifecycle flags

Contacts have several boolean fields that affect how the record behaves in the system: By default, POST /api/Contact/Query excludes archived Contacts. Set includeArchived: true in the filter body to include them when reconciling deleted records or auditing historical data. See Statuses and Lifecycle States for the full lifecycle treatment.

Creating Contacts without producing duplicates

The single most important pattern in any Contact-write integration is never create blindly. The CRM+ API does not prevent duplicate Contacts on direct creation — if you POST a Contact for a donor who already exists in the system, you produce a second record with the same name and address as the first. Two strategies handle this correctly: POST /api/Contact/Transaction submits a Contact for asynchronous import. The nightly batch run applies Virtuous’s matching algorithm — checking email, phone, address, name, and contactReferences against existing records — and creates a new Contact only if no match is found. If a match is found, the incoming data is merged into the existing record. This is the path the Virtuous team explicitly recommends for partner integrations. It is described in detail on the Transactions concept page.

Strategy 2: Find-then-create

If you need synchronous Contact creation, the safe pattern is:
  1. GET /api/Contact/Find — attempt to locate an existing Contact by email or external reference (source + ID). Returns a single matched Contact if one exists. Accepts email, referenceSource, and referenceId as query parameters.
  2. POST /api/Contact/Query — if Find returns nothing, run a broader query (by name + postal code, for example) to catch fuzzy matches that Find did not.
  3. POST /api/Contact — create only if both lookups confirm no existing record matches.
POST /api/Contact does not return 201 Created — it returns 200 OK. The created record’s ID is in the response body. Treat any successful response from this endpoint as a creation, regardless of the status code.

Updating Contacts

The CRM+ API uses PUT /api/Contact/{contactId} for updates. The endpoint name suggests full-replacement PUT semantics, but in practice the live API behaves as PATCH — fields omitted from the request body are not cleared.
This is undocumented behavior. The spec declares PUT for these endpoints, and the canonical REST semantics for PUT are full-replacement (omitted fields would be cleared to null). The live API does not behave that way today, but relying on this undocumented behavior is a risk. The safe pattern is: GET the current record, modify the fields you intend to change, PUT the complete record. This insulates your integration from any future change to PUT semantics.
For the field-by-field update pattern and the safe GET-then-PUT workflow, see the Update a Contact workflow.

Created and modified timestamps

Every Contact carries metadata about when it was created and last modified: The modifiedDateTimeUtc field is the canonical anchor for incremental sync — query for Contacts with modifiedDateTimeUtc > {last_sync_run} to retrieve only records that have changed since your last run. See Query Contacts by Filters for the pattern.

Where to go next

Donations / Gifts

Gift records — what they contain and how they reference a Contact.

Transactions

The recommended Contact and Gift import pattern with built-in matching and deduplication.

Custom Fields

How nonprofits extend the Contact schema and how to read/write custom field values.

Relationships and IDs

Using contactReferences to bridge your platform’s IDs with Virtuous Contact IDs.

Create a Contact

A working example of the Contact create workflow with all required fields.

Query Contacts by Filters

Use POST /api/Contact/Query for incremental sync and bulk retrieval.
Last modified on May 27, 2026