Skip to main content
Two distinct concepts share this page because partner integrations encounter both when wiring an external system to CRM+:
  • IDs — how records are uniquely identified, both internally to Virtuous and via external references that bridge your platform’s identifiers with Virtuous’s.
  • Relationships — the explicit resource that models connections between Contacts (spouses, parents, employers, etc.).
The first section is foundational for every integration. The second is required only for integrations that need to model person-to-person relationships beyond the standard household structure.

Part 1: Identifier patterns

Primary keys

Every resource in CRM+ has a numeric id field as its primary key. IDs are assigned at creation time, are unique within an organization, and are immutable.
ResourcePrimary key field
Contactid
ContactIndividualid
ContactAddressid
Giftid
GiftDesignationid
Projectid
CampaigncampaignId
Segmentid
RecurringGiftid
Pledgeid
Relationshipid
Campaign is the one resource whose primary key is named after the resource (campaignId) rather than the standard id. Every other resource uses plain id on its own record and {resource}Id only when referenced from elsewhere. The campaignId naming on Campaign itself is a spec quirk; treat it as the Campaign’s primary key.

Foreign keys

When one resource references another, the field name follows the pattern {resource}Id. A Gift references its Contact through contactId; a GiftDesignation references its Project through projectId.
{
  "id": 78421,              // The Gift's own primary key
  "contactId": 4821,        // Foreign key → Contact
  "giftDesignations": [
    {
      "id": 91002,          // The Designation's own primary key
      "projectId": 311      // Foreign key → Project
    }
  ]
}
This pattern is consistent across the API. If you see a field ending in Id (other than id itself), it is a foreign key to another resource.

ID scoping — IDs are per-organization

A CRM+ ID is unique within a single Virtuous organization, but not unique across organizations. A Contact with id: 4821 in one nonprofit’s instance is unrelated to a Contact with id: 4821 in another nonprofit’s instance. The numerical space is independent per organization.
Partner integrations that serve multiple nonprofit customers must scope stored Virtuous IDs by customer. Storing just (virtuousContactId: 4821) is not sufficient — you need (customerId: "acme-nonprofit", virtuousContactId: 4821) to disambiguate. A single global index of Virtuous IDs across all customers will produce silent data mix-ups.
The simplest pattern: every record in your partner integration’s database that references a Virtuous record stores three columns — your customer identifier, the Virtuous resource type, and the Virtuous ID.

External reference identifiers

Two patterns let your platform’s identifiers travel alongside Virtuous IDs, eliminating the need to store the Virtuous ID separately for the most common workflows.

Contact references — contactReferences[]

Contacts carry an array of external reference identifiers:
{
  "id": 4821,
  "name": "Wayne, Bruce",
  "contactReferences": [
    { "source": "Stripe", "id": "cus_abc123" },
    { "source": "Eventbrite", "id": "event-attendee-9421" },
    { "source": "YourPlatform", "id": "donor-bw-001" }
  ]
}
Each entry pairs a source (your platform name) with an id (your platform’s identifier for the same donor). A Contact can have many references — one per integrated system. Setting references on Contact creation. Include referenceSource and referenceId in the Contact body or in the embedded contact block of a Gift Transaction. The Transaction endpoint uses these as the highest-priority match signal, so duplicate-prevention is most reliable when you set them. Looking up a Contact by reference. Two endpoints support this:
EndpointAuthUse
GET /api/Contact/{referenceSource}/{referenceId}Standard BearerThe recommended path — both parts in the URL path.
GET /api/Contact/ByReference/{referenceId}HMAC onlyOlder single-segment path. Requires HMAC authentication — contact Virtuous support for HMAC credentials.
cURL
# Look up a Virtuous Contact by your platform's identifier
curl https://api.virtuoussoftware.com/api/Contact/YourPlatform/donor-bw-001 \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Gift references — transactionSource + transactionId

Gifts use a single source/id pair (not an array — gifts have at most one external reference):
{
  "id": 78421,
  "transactionSource": "Stripe",
  "transactionId": "ch_3PXyz123"
}
The pair serves two purposes:
  • Idempotency. When you re-submit a Gift Transaction with the same (transactionSource, transactionId) pair, Virtuous recognizes it as a duplicate and does not create a second Gift.
  • Lookup. GET /api/Gift/{transactionSource}/{transactionId} retrieves the Gift (or pending Transaction) by your platform’s IDs.
cURL
# Look up a Virtuous Gift by your platform's transaction identifier
curl https://api.virtuoussoftware.com/api/Gift/Stripe/ch_3PXyz123 \
  -H "Authorization: Bearer YOUR_API_TOKEN"
See Transactions for the full idempotency discussion.

Choosing a reference convention

For partner integrations, pick a single, stable string for your source value and use it consistently across every Contact and Gift you submit. Common patterns:
  • Your platform name (e.g., "Stripe", "Eventbrite", "YourPlatformName") — the most readable choice.
  • Your platform name plus a sub-context when one partner integrates multiple data streams (e.g., "Acme/Donations" vs. "Acme/Events") — useful when one platform sends both gift and event-attendance data.
Avoid generic source names like "API" or "Import" — they make reconciliation harder when multiple integrations write to the same Virtuous organization.

Part 2: The Relationship resource

The Relationship resource models explicit connections between two Contacts — spouses, parents and children, employers and employees, board members and the organizations they serve. Relationships are distinct from the ContactIndividual structure inside a single Contact: a household Contact already groups its members internally, but a Relationship connects two separate Contacts. A typical Relationship record:
{
  "id": 5501,
  "contactId": 4821,
  "contact": "Wayne, Bruce",
  "contactIndividual": "Bruce Wayne",
  "relatedContactId": 7102,
  "relatedContact": "Wayne Enterprises",
  "relatedContactIndividual": null,
  "relationshipType": "Employer",
  "notes": "Founder and majority shareholder"
}
FieldTypeDescription
idintegerPrimary key of the Relationship.
contactIdintegerThe Contact this relationship is “owned by” — usually the Individual perspective.
contactIndividualIdintegerOptionally the specific ContactIndividual on the owning Contact involved in the relationship.
relatedContactIdintegerThe Contact on the other side of the relationship.
relatedContactIndividualIdintegerOptionally the specific ContactIndividual on the related Contact.
relationshipTypestringThe kind of relationship — see Discovering relationship types.
notesstringFree-text notes about the relationship.
A Relationship is directional in storage (one Contact is the “owner,” one is the “related”), but for most relationship types the meaning is symmetric — if Bruce is married to Selina, both Contact records will show the relationship.

Discovering relationship types

The set of valid relationshipType values is configured per organization. Discover them with:
cURL
curl https://api.virtuoussoftware.com/api/Relationship/Types \
  -H "Authorization: Bearer YOUR_API_TOKEN"
Common relationship types include Spouse, Parent, Child, Sibling, Employer, Employee, Board Member, and Family, but the authoritative list comes from the endpoint.
As with custom fields and contact types, don’t hardcode relationship type strings in your integration. Fetch the valid types at integration setup, cache them, and validate incoming relationship data against the cached list before submitting.

Working with Relationships

The endpoints follow standard CRUD patterns:
EndpointUse
GET /api/Relationship/ByContact/{contactId}List all relationships for a Contact. Paginated.
GET /api/Relationship/{relationshipId}Retrieve a single Relationship.
POST /api/RelationshipCreate a new Relationship between two Contacts.
PUT /api/Relationship/{relationshipId}Update an existing Relationship.
DELETE /api/Relationship/{relationshipId}Delete a Relationship. The Contacts themselves are not affected.
GET /api/Relationship/TypesDiscover the valid relationshipType values for this organization.
The PUT /api/Relationship/{relationshipId} endpoint description in the spec explicitly states: “excluding a property will remove its value from the object. If you’re only updating a single property, the entire model is still required.” This is the strict PUT-as-full-replacement behavior that other CRM+ PUT endpoints do not enforce in practice.The safe pattern across all PUT endpoints is the same: GET the current record, modify the fields you intend to change, and PUT the complete record back. This pattern is correct whether the live endpoint behaves as strict-PUT or as PATCH.

When to use Relationships

Most partner integrations do not need to interact with Relationships directly. The resource is most relevant for:
  • Wealth screening and major-gift integrations — tracking the connections between a major donor and the affiliated organizations they influence.
  • Corporate giving integrations — linking corporate Contacts to the individual contacts at those corporations.
  • Family giving programs — modeling parent-child or extended-family relationships when those are tracked separately from household structures.
For straightforward donor sync (one donor in your system = one Contact in Virtuous), Relationships are typically out of scope.

Part 3: Other relationship patterns in the data model

Beyond the explicit Relationship resource, CRM+ uses three implicit relationship patterns:

Hierarchical — parent and child

Projects can be organized in a parent-child hierarchy through the parentId field. A sub-project rolls up to its parent for reporting purposes. See Funds, Campaigns, and Designations.

Aggregating — one-to-many

Several resources have one-to-many aggregate relationships:
ContainerContents
ContactContactIndividuals, ContactAddresses, Gifts, RecurringGifts, Pledges, Notes, Tags
GiftGiftDesignations, GiftPremiums, PledgePayments
ContactIndividualContactMethods
ProjectSub-Projects, GiftDesignations (via the Gifts that fund the Project)
These are typically retrieved together — a GET /api/Contact/{contactId} returns the Contact plus its ContactIndividuals and addresses; a GET /api/Gift/{giftId} returns the Gift plus its designations and premiums.

Reference — single foreign keys

The most common pattern: one record references another via a single foreign key field. Gift.contactIdContact.id; GiftDesignation.projectIdProject.id; RecurringGift.contactIdContact.id. These references are how your integration navigates from one record to another via GET /api/{Resource}/{id} calls.

Where to go next

Transactions

How external reference identifiers drive Transaction matching and idempotency.

Contacts

The Contact resource — where contactReferences[] lives.

Statuses and Lifecycle States

The next concept page — lifecycle flags across Contacts, Projects, RecurringGifts, and Pledges.

Build a Two-Way Sync

A workflow that depends heavily on stable external references in both directions.
Last modified on May 27, 2026