Contact References can now be retrieved and updated by their numeric Virtuous record id, and queried through the CRM+ API.
POST /api/ContactReference continues to return 200 with the created reference, including its numeric id and a Location header. This id is distinct from the external referenceId.
GET /api/ContactReference/{id} retrieves a reference by its numeric id. PUT /api/ContactReference/{id} updates its external referenceId and can clear or change its associated contactIndividualId; the contact and referenceSource cannot be changed.
POST /api/ContactReference/Query returns matching references with list and total. Use GET /api/ContactReference/QueryOptions to build filters and check supported operators. Query an existing contact, source, and external reference id before creating another reference if you need to avoid duplicates.
DELETE /api/ContactReference/{referenceSource} accepts either the numeric record id without a referenceId query parameter or a referenceSource and external referenceId pair. Use the numeric id to target a specific record.
Improved: Read back more CRM+ API fields
CRM+ API responses now return 16 additional fields across seven objects. You can read these values when matching existing records or preparing updates, rather than relying on write requests alone:
- Planned Gifts:
contactIndividualId and thankYouDate on Query, GET by id, and ByContact.
- Volunteer Organizers:
contactIndividualId on the organizer list, individual GET, and create response.
- Event Attendees:
schedule, table, and meal on Query, attendee list, individual GET, and write responses. These fields remain update-only in request bodies.
- Segments:
contactQueryId and receiptSegmentId on Search, GET by id, Code, ByContact, and write responses.
- Projects:
enableSync on Query, GET by id or code, Search, balance-update and write responses. This is a boolean; false is returned explicitly.
- Events:
address1, address2, city, state, postal, and country on Query, GET by id, Search, and write responses. The existing composed address fields remain available.
- Contact Notes:
timeSpent on Query, GET by id, ByContact, Important ByContact, and write responses.
Include fields you want to retain when updating a record. Planned Gift thankYouDate is returned as a date formatted in the current user’s time zone, not an ISO timestamp. This change does not add PATCH or change full-record replacement semantics; other writable fields and response formats may still differ. Event eventbriteId read support is coming soon and is not included in this release.
Added: Tribute read and query endpoints
You can now retrieve a tribute by id and query tributes by exact-match conditions through the CRM+ API.
GET /api/Tribute/{tributeId} returns a tribute by its identifier, or 404 if it is not found in your organization.
POST /api/Tribute/Query returns a paginated list and total. Use a condition such as {"parameter":"Tribute Id","operator":"Is","value":"750001"} to find an exact id match. skip defaults to 0; take defaults to 10 and cannot exceed 1,000.
GET /api/Tribute/QueryOptions returns the available query parameters and operators. Check it before building more complex queries.
- Tribute responses now include
contactIndividualId and contactIndividualUrl for a linked honoree. Both are null when no individual is linked; the existing acknowledgee id and URL remain available.
GET /api/Tribute/Search is unchanged: it matches name prefixes and returns an array, not a paginated query response. Use Query with Is when you need an exact match.Improved: Project expense pagination and filtering
Pagination on GET /api/ProjectExpense now retrieves expenses beyond the first 10 rows. skip defaults to 0, and take defaults to 10 with a maximum of 1,000. Use filter to match expenses by description or accounting code.The legacy count parameter is deprecated but remains supported for existing integrations. When both count and take are sent, count overrides take.Added: Create and Update Campaign endpoints
You can now create and update campaigns directly through the CRM+ API.
POST /api/Campaign creates a campaign. name must be unique within the organization, and ownerId must resolve to an organization user — use GET /api/Organization/Current to look up the id of the API user. You can optionally pass projectTypeNames to auto-create a communication for each recognized channel type on the new campaign.
PUT /api/Campaign/{campaignId} updates an existing campaign. As with other update endpoints, this is a full replace — omitting a property clears it, so send the complete model even when you’re only changing one field.
- The Campaign response — returned by
GET /api/Campaign/{campaignId}, the two endpoints above, and POST /api/Campaign/Query — now also includes description, hashtag, googleAnalyticsViewId, googleAnalyticsCampaign, and ownerId, which were previously missing from the response even though they could be set in Virtuous. This is an additive change — no existing fields changed or were removed.
Individual records returned by the CRM+ API now include an isGloballyUnsubscribed boolean that tells you whether the person has globally unsubscribed from all email.Use it to keep your own sends in sync with a donor’s global email preference without a separate lookup — for example, to suppress an individual from an email campaign or to reflect their opt-out state in a connected system.
isGloballyUnsubscribed is returned on the Contact Individual endpoints — GET /api/ContactIndividual/{contactIndividualId}, GET /api/ContactIndividual/ByContact/{contactId}, GET /api/ContactIndividual/Find, POST /api/ContactIndividual (create), PUT /api/ContactIndividual/{contactIndividualId} (update), and POST /api/ContactIndividual/Query — as well as the individual’s avatar endpoints.
- The field is read-only. It reflects the individual’s email subscription state and cannot be set through the API — it is deliberately absent from the create and update request bodies.
- This is an additive change — no existing fields changed or were removed.
Added: organizationId on List My Organizations
GET /api/Organization now returns an organizationId for each organization the authenticated user belongs to, alongside the existing organizationName.Use it as the stable key for an organization. Organization names can change and often overlap between live, test, and sandbox databases, and chapters can share wording with the primary organization they roll up to. organizationId stays the same for the life of the organization, so integrations that connect to more than one database can tell them apart reliably.A few notes:
organizationId identifies the organization. organizationUserId identifies the user’s membership in it and is still what you send to PUT /api/Organization/Switch.
- This is an additive change — no existing fields changed or were removed.
Improved: Realistic response examples across the API reference
We refreshed the success response examples on several high-value CRM+ endpoints so they show realistic, connected data instead of generic <string> placeholders. Examples now use a consistent fictional world (the Riverside Hope Foundation and its donors), so the same person and gift appear coherently across endpoints.Updated endpoints include:
- Contacts —
GET /api/Contact/{contactId}, POST /api/Contact, POST /api/Contact/Search, POST /api/Contact/Query
- Gifts —
GET /api/Gift/{giftId}, POST /api/Gift, POST /api/Gift/Query
- Recurring Gifts —
GET /api/RecurringGift/{recurringGiftId}, POST /api/RecurringGift
- Pledges —
GET /api/v2/Pledge/{pledgeId}, POST /api/v2/Pledge
- Projects —
GET /api/Project/{projectId}
- Grants —
GET /api/Grant/{grantId}, POST /api/Grant, POST /api/Grant/Query
- Events —
GET /api/Event, POST /api/Event, GET /api/Event/{eventId}, POST /api/Event/Query, GET /api/Event/{eventId}/Attendees
- Tributes —
GET /api/Tribute/Search, POST /api/Tribute, PUT /api/Tribute/{tributeId}
- Notes —
GET /api/ContactNote/{noteId}, POST /api/ContactNote, GET /api/ContactNote/ByContact/{contactId}, POST /api/ContactNote/Query
- Planned Gifts —
GET /api/PlannedGift/{plannedGiftId}, POST /api/PlannedGift, POST /api/PlannedGift/Query
- Premiums —
GET /api/Premium/{premiumId}, POST /api/Premium, POST /api/Premium/Search
- Communications —
GET /api/Communication/{communicationId}, POST /api/Communication
- Segments —
GET /api/Segment/{segmentId}, POST /api/Segment
- Project Notes & Roles —
GET /api/ProjectNote/{noteId}, POST /api/ProjectNote, GET /api/ProjectRole/{projectRoleId}, POST /api/ProjectRole
- Reminders —
GET /api/Reminder/Active, POST /api/Reminder, GET /api/Reminder/ByContact/{contactId}
We then extended the same realistic examples across the rest of the high-traffic surface: the update (PUT) responses for the resources above, their ByContact/list/query variants, the individual, address, contact-method, tag, organization-group, planned-gift, premium, segment, and reminder endpoints, webhooks, and the composite Query/FullContact, Query/FullGift, and {id}/Gifts responses. The majority of CRM+ read, create, update, and list endpoints now return realistic sample data.These are documentation-only changes — no schemas, fields, or behavior changed.Improved: Endpoint-specific error documentation
Error responses across the CRM+ API reference now explain what each status code means for the endpoint you’re looking at, instead of showing a bare BadRequest or NotFound label.
- A new Errors section in the API overview covers the shared error envelope (
message plus modelState) and what 400, 401, 403, 404, 429, and 5xx mean across the API.
- Every authenticated endpoint now documents
401, 403, and 429, including the rate limit headers to use for backoff.
400 descriptions call out the likely cause for that endpoint — model validation with modelState on write endpoints, condition/operator/paging problems on query endpoints, and malformed path values elsewhere.
404 descriptions name the identifier that didn’t resolve, and were added to single-record GET, PUT, and DELETE endpoints that were missing them. List endpoints were left alone, since they generally return an empty result set instead.
- The transaction (import) endpoints now note that a success response only means the payload was queued — matching failures surface in the import review screen, not in the API response.
These are documentation-only changes — no schemas, fields, or behavior changed.Improved: Consistent endpoint names and cleaned-up wording
Endpoint names across the CRM+ API reference now follow a consistent verb + concise object pattern, matching the Volunteer API reference.
- Sidebar and page titles use
List, Get, Create, Update, Delete, Query, and Search plus the resource — for example, “List Contact Method Types” instead of “Gets the types of contact methods”, and “Search Contacts” instead of “Find all Contacts that match, fully or partially, the given search parameters”.
POST /api/Contact/Transaction and POST /api/Contact/Batch are now “Create a Contact Transaction” and “Create a Contact Import”, with descriptions covering the change and duplicate checking the old names described.
- Typos and awkward wording were fixed, including “it’s value” → “its value”, “Contact Indvidual” → “Contact Individual”, and “Communictation Query Options” → “Communication Query Options”.
- Leftover internal code references such as
{M:Virtuous.WebApi.Controllers.ProjectController.QueryOptions} now name the endpoint they point to.
URLs, paths, fields, and operations are unchanged — this only affects the names and descriptions shown in the docs.Improved: Parameter and field descriptions
Parameters and the fields partners work with most now carry descriptions in the CRM+ API reference, so the playground and generated code samples explain what to send instead of just showing a type.
- Paging and sorting —
skip, take, sortBy, and descending are described consistently everywhere they appear, including endpoints where they previously read int, Optional boolean, or nothing at all. Endpoints that cap take at 1000 still say so.
- Path parameters — every templated identifier is described, including the ones that previously showed only
(Required). GET /api/ContactIndividual/{contactIndividualId}/EmailList now declares its contactIndividualId path parameter, which it was missing.
- Query filters — the
groups/conditions filter structure used by the Query endpoints now explains how groups combine, what parameter and operator accept, and when to use value, secondaryValue, or values. The matching QueryOptions responses describe the options they return.
- List responses —
list and total are described on every paged response, including how to use total with skip and take.
- Request and response fields — the common ones are documented across resources: identifiers,
customFields and customCollections, transactionSource/transactionId and referenceSource/referenceId and their role in preventing duplicate imports, isPrivate, audit fields such as createDateTimeUtc and modifiedByUser, and the *Formatted display variants.
These are documentation-only changes — no schemas, fields, or behavior changed.Improved: Endpoint descriptions
Every endpoint in the CRM+ API reference now has a description explaining what it does, not just a name — 228 endpoints that previously showed nothing at all, plus a handful whose copy was a placeholder or a leftover from another endpoint.
- What each endpoint does — descriptions say what a call returns or changes, name the fields you most often need to send, and point to the related endpoint when there is a better one for the job (
Create a Contact to Create a Contact Transaction, Delete a Gift to Create a Reversing Transaction, Search Contacts to Query Contacts).
- Queries — every
Query endpoint points to its QueryOptions endpoint and repeats the 1,000 record take cap, and every QueryOptions endpoint explains that its response is what you use to build a query.
- Placeholder copy removed — the five volunteer endpoints whose description read only “Available starting 3/29/2022” now describe what they do,
List Non-Cash Gift Types and List Contact Note Custom Fields no longer describe the wrong object, and the endpoints whose description was just “HMAC Auth only.” now explain what they return as well.
These are documentation-only changes — no schemas, fields, or behavior changed.
New: CRM+ API Documentation Changelog
We’ve launched the CRM+ API Documentation Changelog to make it easier to track updates to the CRM+ developer experience.This changelog will include updates such as:
- New and updated CRM+ API endpoints
- Request and response schema changes
- New fields, objects, filters, and parameters
- Authentication, permissions, and access updates
- Deprecation notices and breaking changes
- Improvements to examples, guides, recipes, and API reference documentation
Last modified on October 1, 2026