Skip to main content
POST /api/Gift/Query is the Gift counterpart to Contact Query — paginated, filtered retrieval of Gift records. This workflow focuses on the most common partner use of the endpoint: pulling gifts within a date window for reporting, reconciliation with accounting systems, or incremental sync to a data warehouse. The structure parallels Query Contacts by Filters, but Gifts have date-specific filter patterns and the response contains designation and reference information that’s useful for the partner-canonical reconciliation use case.

Scenario

Your integration needs to retrieve Gifts within a specific date window:
  • Monthly reporting — every gift in a calendar month for accounting reconciliation.
  • Accounting sync — gifts processed since the last sync to push into the customer’s accounting system.
  • Custom date range — gifts for a specific campaign window or fiscal period.
The pattern is the same across all three: build a date-range filter, paginate through the result set, and process each Gift.

Prerequisites

  • A valid CRM+ API token — see Authentication.
  • Understanding of the filter structure — see Pagination and Filtering.
  • The list of valid filter parameters and operators for the organization, retrieved from GET /api/Gift/QueryOptions.

Step 1: discover valid filter parameters

Gift Query has its own QueryOptions endpoint — independent of the Contact one:
cURL
curl https://api.virtuoussoftware.com/api/Gift/QueryOptions \
  -H "Authorization: Bearer YOUR_API_TOKEN"
The response shape is identical to Contact’s QueryOptions — see Pagination and Filtering. The Gift-specific parameters typically include Gift Date, Gift Type, Amount, Receipt Date, Batch, Project, Campaign, and Segment, plus any custom fields configured on the Gift resource. The spec notes that Gift Query supports sorting by Id, Amount, GiftDate, ReceiptDate, and Batch.

Pattern 1: gifts within a date window

The canonical date-range query — find all gifts with Gift Date between two values:
curl -X POST https://api.virtuoussoftware.com/api/Gift/Query \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "groups": [
      {
        "conditions": [
          {
            "parameter": "Gift Date",
            "operator": "Between",
            "value": "2024-12-01",
            "secondaryValue": "2024-12-31"
          }
        ]
      }
    ],
    "sortBy": "GiftDate",
    "descending": false,
    "skip": 0,
    "take": 1000
  }'
The Between operator with value and secondaryValue provides the inclusive bounds. Other operators commonly available for date fields include Is After, Is Before, Is On, and Is After Or On — confirm the exact set via GET /api/Gift/QueryOptions.

Gift Date vs. Receipt Date

CRM+ distinguishes between two date fields on a Gift:
FieldMeaning
giftDateThe date the donation actually occurred — when the donor gave the gift.
receiptDateThe date the organization processed and receipted the gift — often the same as giftDate but can differ for backdated entries or mailed checks.
For most partner use cases — donor analytics, year-end giving reports, campaign performance — giftDate is the right filter field. For accounting and tax reconciliation, receiptDate is often more appropriate because it aligns with when the organization recognized the gift in its books.
{
  "groups": [
    {
      "conditions": [
        {
          "parameter": "Receipt Date",
          "operator": "Between",
          "value": "2024-12-01",
          "secondaryValue": "2024-12-31"
        }
      ]
    }
  ],
  "sortBy": "ReceiptDate",
  "skip": 0,
  "take": 1000
}
When pulling data for an accounting integration, ask the customer which date field aligns with their accounting period. Reporting on giftDate when the customer’s books recognize on receiptDate produces a reconciliation discrepancy at the end of every period.

Pattern 2: incremental gift sync

For continuous sync of new and modified gifts, filter by Last Modified Date rather than Gift Date. This catches both new gifts and edits to existing ones.
JavaScript
async function pullModifiedGifts(lastSyncTimestamp) {
  const allGifts = [];
  let skip = 0;
  const take = 1000;
  let total = null;
  let highestModifiedDate = lastSyncTimestamp;

  do {
    const response = await fetch(
      'https://api.virtuoussoftware.com/api/Gift/Query',
      {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${process.env.VIRTUOUS_API_TOKEN}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          groups: [
            {
              conditions: [
                {
                  parameter: 'Last Modified Date',
                  operator: 'Is After',
                  value: lastSyncTimestamp,
                },
              ],
            },
          ],
          sortBy: 'GiftDate',                  // GiftDate is indexed; LastModifiedDate may not be
          descending: false,
          skip,
          take,
        }),
      }
    );

    const page = await response.json();
    if (total === null) total = page.total;

    for (const gift of page.list) {
      allGifts.push(gift);
      // Track the highest modification timestamp seen
      if (gift.modifiedDateTimeUtc && gift.modifiedDateTimeUtc > highestModifiedDate) {
        highestModifiedDate = gift.modifiedDateTimeUtc;
      }
    }

    skip += take;
  } while (skip < total);

  return { gifts: allGifts, nextSyncTimestamp: highestModifiedDate };
}
As with Contact incremental sync, webhooks (giftCreate and giftUpdate) are the preferred primary signal. Use this poll-based pattern as a reconciliation backstop, not as the primary detection mechanism. See Webhooks Overview.

Pattern 3: gifts for a specific Project or Campaign

To pull all gifts associated with a particular Project or Campaign — useful for campaign reporting integrations:
{
  "groups": [
    {
      "conditions": [
        { "parameter": "Project", "operator": "Is", "value": "Clean Water Initiative" },
        {
          "parameter": "Gift Date",
          "operator": "Between",
          "value": "2024-01-01",
          "secondaryValue": "2024-12-31"
        }
      ]
    }
  ],
  "sortBy": "GiftDate",
  "skip": 0,
  "take": 1000
}
Or by Campaign:
{
  "groups": [
    {
      "conditions": [
        { "parameter": "Campaign", "operator": "Is", "value": "Year-End Giving 2024" }
      ]
    }
  ],
  "sortBy": "GiftDate",
  "skip": 0,
  "take": 1000
}
A Gift is associated with a Campaign indirectly — through the Project(s) in its GiftDesignations, which belong to a Campaign. The Campaign filter parameter resolves this relationship server-side. See Funds, Campaigns, and Designations for the data-model context.

Pattern 4: gifts by amount range

For high-value gift reporting or major-donor identification:
{
  "groups": [
    {
      "conditions": [
        {
          "parameter": "Amount",
          "operator": "Greater Than Or Equal To",
          "value": "10000"
        },
        {
          "parameter": "Gift Date",
          "operator": "Between",
          "value": "2024-01-01",
          "secondaryValue": "2024-12-31"
        }
      ]
    }
  ],
  "sortBy": "Amount",
  "descending": true,
  "skip": 0,
  "take": 100
}
Sorting by Amount descending puts the largest gifts first — useful for major-gift dashboards. The amount filter values are passed as strings in the spec; the live API accepts both string and numeric forms.

Working with the response

The POST /api/Gift/Query abbreviated response includes:
FieldDescription
idThe Gift’s primary key.
contactId, contactIndividualIdThe donor’s IDs.
giftType, giftDate, amountThe basic gift fields.
segment, batchCategorization metadata.
giftUrlA URL to view the Gift in the Virtuous UI.
For most reporting use cases this is sufficient. For a richer payload — including designations, premiums, and full Contact data — use POST /api/Gift/Query/FullGift instead.

Including transactionSource and transactionId

Gifts that came from your platform’s Transactions carry transactionSource and transactionId. These appear in the full-gift response. To reconcile a partner-side donation log with the corresponding Virtuous Gifts, use POST /api/Gift/Query/FullGift (or fetch each Gift individually with GET /api/Gift/{giftId} for the small subset you need full detail on).

Reconciliation workflow

A common partner-integration use of date-range queries is monthly reconciliation between the partner’s records and Virtuous. The shape:
1

Define the reconciliation window

Typically a calendar month or the period since the last reconciliation. Use Gift Date if reconciling against donor-facing reports; use Receipt Date for accounting reconciliation.
2

Query Virtuous for the gifts in that window

Use the pattern from Pattern 1 with take=1000 to minimize requests.
3

Join against your own records

For each Virtuous Gift, look up the matching record in your platform by transactionSource + transactionId. Categorize as: matched (present on both sides), Virtuous-only (gift in Virtuous but not on your side), partner-only (gift on your side but not in Virtuous).
4

Investigate discrepancies

Virtuous-only gifts typically indicate manual entries in Virtuous or other integrations. Partner-only gifts indicate sync failures — submitted but never appeared. See Reconcile Failed Syncs.
5

Persist the reconciliation result

Store a summary of the reconciliation run for audit purposes. Most customers want a monthly artifact showing both sides matched.

Performance considerations

  • take=1000 for reconciliation. Each request returns up to 1,000 Gifts and consumes one rate-limit slot. A year’s worth of gifts for a typical mid-sized nonprofit (10,000–50,000 gifts) is 10–50 requests — well within budget.
  • Sort on indexed fields. GiftDate, ReceiptDate, Amount, Id, and Batch are indexed per the spec. Custom sort fields may be slow on large result sets.
  • Filter aggressively. Narrow date ranges produce smaller result sets and faster queries. A query for “all gifts in 2024” is fine; a query for “all gifts ever” should be broken into year-by-year chunks for large databases.
  • Use the abbreviated response when possible. POST /api/Gift/Query returns the most useful fields; POST /api/Gift/Query/FullGift is meaningfully slower per request.

Error handling

The error model is the same as Contact Query — see Query Contacts by Filters for the status codes and remediation. Two Gift-specific cases worth calling out:
  • 400 on unknown Project or Campaign filter values. The filter accepts a Project or Campaign name — if no Project/Campaign by that name exists, the API returns 400 rather than an empty result set. Validate the names against GET /api/Project/Query and POST /api/Campaign/Query before constructing the filter.
  • Date format mismatches. Date filter values are typically YYYY-MM-DD for Gift Date and Receipt Date. Last Modified Date is typically YYYY-MM-DDTHH:MM:SSZ. Confirm via the response from GET /api/Gift/QueryOptions.

Where to go next

Query Contacts by Filters

The Contact equivalent — same pattern, different resource.

Reconcile Failed Syncs

Use date-range queries as the foundation for periodic reconciliation between partner and Virtuous records.

Sync External Donations into Virtuous

The write-side architecture that this read pattern reconciles against.

Donations / Gifts

The Gift data model — fields, designations, types, and lifecycle.
Last modified on May 21, 2026