> ## 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.

# Contacts

> The Contact, ContactIndividual, ContactAddress, and ContactMethod resources — how the donor data model is structured and what partner integrations need to know to avoid duplicates.

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.

| Resource              | Cardinality                | What it is                                                                             |
| --------------------- | -------------------------- | -------------------------------------------------------------------------------------- |
| **Contact**           | The top-level record       | A household (e.g., "The Wayne Family") or an organization (e.g., "Wayne Enterprises"). |
| **ContactIndividual** | Many per Contact           | A person inside the Contact — first name, last name, birth date, gender, etc.          |
| **ContactAddress**    | Many per Contact           | A postal address belonging to the Contact (one is the primary).                        |
| **ContactMethod**     | Many per ContactIndividual | An email or phone number belonging to a specific ContactIndividual.                    |

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:

```json theme={null}
{
  "id": 4821,
  "contactType": "Household",
  "name": "Wayne, Bruce",
  "contactIndividuals": [
    {
      "id": 9012,
      "firstName": "Bruce",
      "lastName": "Wayne",
      "isPrimary": true,
      "contactMethods": [
        { "id": 31001, "type": "Home Email", "value": "bruce@wayne.example", "isPrimary": true },
        { "id": 31002, "type": "Mobile Phone", "value": "555-0100", "isPrimary": true }
      ]
    }
  ],
  "address": {
    "id": 51001,
    "address1": "1007 Mountain Drive",
    "city": "Gotham",
    "state": "NJ",
    "postal": "07001",
    "country": "US",
    "isPrimary": true
  }
}
```

***

## 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.

<Note>
  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.
</Note>

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.

| Flag                | Meaning                                                                                                         |
| ------------------- | --------------------------------------------------------------------------------------------------------------- |
| `isPrimary: true`   | The main contact for the household. Receipts, communications, and gift attributions default to this individual. |
| `isSecondary: true` | The partner/spouse role. Some communications and joint-giving displays include this individual.                 |
| Neither flag set    | Other household members (e.g., children). Stored but not the default recipient for communications.              |

`canBePrimary` and `canBeSecondary` indicate whether an individual is eligible for those roles based on other organization settings.

<Tip>
  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.
</Tip>

***

## 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:

| Field                                          | Type      | Description                                                                      |
| ---------------------------------------------- | --------- | -------------------------------------------------------------------------------- |
| `id`                                           | `integer` | The unique address ID. Required for updates.                                     |
| `label`                                        | `string`  | The address's display label (e.g., "Home", "Work", "Seasonal").                  |
| `address1`, `address2`                         | `string`  | Street address lines.                                                            |
| `city`, `state`, `postal`, `country`           | `string`  | Standard postal components.                                                      |
| `isPrimary`                                    | `boolean` | Whether this is the Contact's primary address.                                   |
| `canBePrimary`                                 | `boolean` | Whether this address is eligible to be primary (e.g., contains required fields). |
| `startMonth`, `startDay`, `endMonth`, `endDay` | `integer` | Seasonal address date ranges.                                                    |

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.

<Warning>
  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.
</Warning>

***

## 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](/crm/concepts/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](/crm/concepts/relationships-and-ids).

```json theme={null}
{
  "tags": ["Major Donor", "Board Member"],
  "customFields": [
    { "name": "wealthScore", "value": "850", "displayName": "Wealth Score" }
  ],
  "contactReferences": [
    { "source": "Stripe", "id": "cus_abc123" },
    { "source": "Eventbrite", "id": "event-attendee-9421" }
  ]
}
```

`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.

<Note>
  `GET /api/Contact/ByReference/{referenceId}` requires HMAC authentication rather than standard Bearer token auth. See [Authentication](/crm/authentication#hmac-authenticated-endpoints) for details — contact Virtuous support to obtain HMAC credentials if you need to use this lookup pattern.
</Note>

***

## Statuses and lifecycle flags

Contacts have several boolean fields that affect how the record behaves in the system:

| Field                               | What it means                                                                                                               |
| ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `isPrivate`                         | The Contact is hidden from most users — visible only to administrators. Typical for high-profile donors or anonymous gifts. |
| `isArchived`                        | The Contact has been archived. Excluded from most queries by default.                                                       |
| `isDeceased` (on ContactIndividual) | The individual has died. The Contact may still be active if other individuals remain.                                       |

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](/crm/concepts/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:

### Strategy 1: Use the Contact Transaction endpoint (recommended)

`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](/crm/concepts/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.

```javascript theme={null}
async function upsertContact(donor) {
  // 1. Try direct lookup by email (GET with query parameter)
  const find = await fetch(
    `https://api.virtuoussoftware.com/api/Contact/Find?email=${encodeURIComponent(donor.email)}`,
    {
      headers: { Authorization: `Bearer ${token}` },
    }
  );

  if (find.ok) {
    return await find.json(); // existing Contact found
  }

  // 2. Broader query as a fallback (omitted for brevity — see Pagination and Filtering)

  // 3. No match — create a new Contact
  const create = await fetch(
    'https://api.virtuoussoftware.com/api/Contact',
    {
      method: 'POST',
      headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
      body: JSON.stringify({
        contactType: donor.isOrganization ? 'Organization' : 'Household',
        name: donor.displayName,
        contactIndividuals: [{
          firstName: donor.firstName,
          lastName: donor.lastName,
          isPrimary: true,
          contactMethods: [
            { type: 'Home Email', value: donor.email, isPrimary: true, isOptedIn: donor.emailOptIn },
          ],
        }],
        address: donor.address,
      }),
    }
  );

  return await create.json();
}
```

<Warning>
  `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.
</Warning>

***

## 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.

<Warning>
  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.
</Warning>

For the field-by-field update pattern and the safe `GET`-then-`PUT` workflow, see the [Update a Contact](/crm/workflows/update-a-contact) workflow.

***

## Created and modified timestamps

Every Contact carries metadata about when it was created and last modified:

| Field                 | Type       | Description                                              |
| --------------------- | ---------- | -------------------------------------------------------- |
| `createDateTimeUtc`   | `dateTime` | ISO 8601 timestamp of when the Contact was created. UTC. |
| `createdByUser`       | `string`   | Name of the user who created the record.                 |
| `modifiedDateTimeUtc` | `dateTime` | ISO 8601 timestamp of the most recent modification. UTC. |
| `modifiedByUser`      | `string`   | Name of the user who most recently modified the record.  |

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](/crm/workflows/query-contacts-by-filters) for the pattern.

***

## Where to go next

<CardGroup cols={2}>
  <Card title="Donations / Gifts" icon="hand-holding-dollar" href="/crm/concepts/gifts">
    Gift records — what they contain and how they reference a Contact.
  </Card>

  <Card title="Transactions" icon="arrow-down-to-line" href="/crm/concepts/transactions">
    The recommended Contact and Gift import pattern with built-in matching and deduplication.
  </Card>

  <Card title="Custom Fields" icon="square-plus" href="/crm/concepts/custom-fields">
    How nonprofits extend the Contact schema and how to read/write custom field values.
  </Card>

  <Card title="Relationships and IDs" icon="link" href="/crm/concepts/relationships-and-ids">
    Using `contactReferences` to bridge your platform's IDs with Virtuous Contact IDs.
  </Card>

  <Card title="Create a Contact" icon="user-plus" href="/crm/workflows/create-a-contact">
    A working example of the Contact create workflow with all required fields.
  </Card>

  <Card title="Query Contacts by Filters" icon="filter" href="/crm/workflows/query-contacts-by-filters">
    Use `POST /api/Contact/Query` for incremental sync and bulk retrieval.
  </Card>
</CardGroup>
