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

# Query Donations by Date Range

> Retrieve Gift records by date range — reporting exports, reconciliation snapshots, and incremental sync of gift data into downstream systems.

`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](/crm/workflows/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](/crm/authentication).
* Understanding of the filter structure — see [Pagination and Filtering](/crm/pagination#part-2-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:

```bash cURL theme={null}
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](/crm/pagination#discover-available-filter-fields). 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:

<CodeGroup>
  ```bash cURL theme={null}
  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
    }'
  ```

  ```javascript JavaScript theme={null}
  async function queryGiftsByDateRange(startDate, endDate) {
    const allGifts = [];
    let skip = 0;
    const take = 1000;
    let total = null;

    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: 'Gift Date',
                    operator: 'Between',
                    value: startDate,             // 'YYYY-MM-DD'
                    secondaryValue: endDate,
                  },
                ],
              },
            ],
            sortBy: 'GiftDate',
            descending: false,
            skip,
            take,
          }),
        }
      );

      if (!response.ok) {
        throw new Error(`Query failed: ${response.status}`);
      }
      const page = await response.json();

      if (total === null) {
        total = page.total;
        console.log(`Found ${total} gifts between ${startDate} and ${endDate}`);
      }

      allGifts.push(...page.list);
      skip += take;
    } while (skip < total);

    return allGifts;
  }
  ```
</CodeGroup>

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:

| Field         | Meaning                                                                                                                                          |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `giftDate`    | The date the donation actually occurred — when the donor gave the gift.                                                                          |
| `receiptDate` | The 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.

```json theme={null}
{
  "groups": [
    {
      "conditions": [
        {
          "parameter": "Receipt Date",
          "operator": "Between",
          "value": "2024-12-01",
          "secondaryValue": "2024-12-31"
        }
      ]
    }
  ],
  "sortBy": "ReceiptDate",
  "skip": 0,
  "take": 1000
}
```

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

***

## 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 JavaScript theme={null}
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 };
}
```

<Tip>
  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](/crm/webhooks/overview).
</Tip>

***

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

```json theme={null}
{
  "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:

```json theme={null}
{
  "groups": [
    {
      "conditions": [
        { "parameter": "Campaign", "operator": "Is", "value": "Year-End Giving 2024" }
      ]
    }
  ],
  "sortBy": "GiftDate",
  "skip": 0,
  "take": 1000
}
```

<Note>
  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](/crm/concepts/funds-campaigns-designations) for the data-model context.
</Note>

***

## Pattern 4: gifts by amount range

For high-value gift reporting or major-donor identification:

```json theme={null}
{
  "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:

| Field                              | Description                                |
| ---------------------------------- | ------------------------------------------ |
| `id`                               | The Gift's primary key.                    |
| `contactId`, `contactIndividualId` | The donor's IDs.                           |
| `giftType`, `giftDate`, `amount`   | The basic gift fields.                     |
| `segment`, `batch`                 | Categorization metadata.                   |
| `giftUrl`                          | A 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:

<Steps>
  <Step title="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.
  </Step>

  <Step title="Query Virtuous for the gifts in that window">
    Use the pattern from Pattern 1 with `take=1000` to minimize requests.
  </Step>

  <Step title="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).
  </Step>

  <Step title="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](/crm/workflows/reconcile-failed-syncs).
  </Step>

  <Step title="Persist the reconciliation result">
    Store a summary of the reconciliation run for audit purposes. Most customers want a monthly artifact showing both sides matched.
  </Step>
</Steps>

***

## 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](/crm/workflows/query-contacts-by-filters#error-handling) 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

<CardGroup cols={2}>
  <Card title="Query Contacts by Filters" icon="filter" href="/crm/workflows/query-contacts-by-filters">
    The Contact equivalent — same pattern, different resource.
  </Card>

  <Card title="Reconcile Failed Syncs" icon="arrows-rotate" href="/crm/workflows/reconcile-failed-syncs">
    Use date-range queries as the foundation for periodic reconciliation between partner and Virtuous records.
  </Card>

  <Card title="Sync External Donations into Virtuous" icon="arrow-right-arrow-left" href="/crm/workflows/sync-external-donations">
    The write-side architecture that this read pattern reconciles against.
  </Card>

  <Card title="Donations / Gifts" icon="hand-holding-dollar" href="/crm/concepts/gifts">
    The Gift data model — fields, designations, types, and lifecycle.
  </Card>
</CardGroup>
