> ## 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 Gifts by Filters

> The workflow for reading Gifts from Raise with filters — the two query surfaces, the common filter scenarios, and the iteration patterns for bulk reads and reconciliation.

Reading Gift records is one of the most common partner integration workflows in Raise. Reporting tools query gifts to build dashboards. Reconciliation jobs query gifts to compare against external systems. Sync workflows query gifts to feed downstream platforms. This page covers the patterns that make these workflows efficient and reliable.

Raise exposes two distinct query surfaces for Gifts — a simpler `GET /api/Gift/list` with query-parameter filters, and a more powerful `POST /api/Gift/query` with a structured request body. This workflow page covers both and shows when to use each.

For the resource-level reference of the Gift record itself, see [Gifts](/raise/concepts/gifts).

## When to use this workflow

| Scenario                                             | Approach                                                                        |
| ---------------------------------------------------- | ------------------------------------------------------------------------------- |
| Pulling a quick list of recent gifts for display     | `GET /api/Gift/list` with `Take` and a sort                                     |
| Free-text search across gift fields                  | `GET /api/Gift/list` with the `Filter` parameter                                |
| Exporting all gifts within a date range              | `POST /api/Gift/query` with a date condition                                    |
| Filtering by donor, campaign, designation, or status | `POST /api/Gift/query` with structured conditions                               |
| Streaming gifts for a downstream sync                | `POST /api/Gift/query` with ID-cursor iteration                                 |
| Getting all gifts for a specific donor               | `GET /api/Donor/{donorId}/gifts` — cheaper than a Query with a `donorId` filter |

For the underlying mechanics of both endpoint patterns, see [Pagination and Filtering](/raise/pagination).

***

## Pattern 1: simple list with `GET /api/Gift/list`

The simplest read. Documented parameters:

| Parameter        | Type    | Description                                                              |
| ---------------- | ------- | ------------------------------------------------------------------------ |
| `Filter`         | string  | Free-text filter                                                         |
| `Skip`           | integer | Records to skip                                                          |
| `Take`           | integer | Records to return per page                                               |
| `SortBy`         | string  | One of: `amount`, `date`, `donorname`, `id`, `status` (case-insensitive) |
| `Descending`     | boolean | Sort direction                                                           |
| `IncludeDetails` | boolean | Include related entities (Donor, Gateway, Segment, etc.)                 |

### Recent gifts for a dashboard

```bash cURL theme={null}
curl -G "https://prod-api.raisedonors.com/api/Gift/list" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  --data-urlencode "Take=25" \
  --data-urlencode "SortBy=date" \
  --data-urlencode "Descending=true"
```

The result is the 25 most recent gifts. For a "latest activity" dashboard, this is typically all you need.

### When `IncludeDetails` is worth the cost

`IncludeDetails=true` adds the Donor record, gateway details, segment context, and other related entities to each item in the response. Useful when:

* You're displaying gifts with donor info in a single UI view (avoids a follow-up lookup per gift).
* You're exporting gifts to an external system that needs the full context per record.

Not worth the cost when:

* You only need summary fields (amount, date, donor ID) for aggregation.
* You're streaming large result sets — the extra payload per record adds up.

For most bulk-read workflows, default to `IncludeDetails=false` and fetch related records only when needed.

### Sort fields available

The Gift `/list` endpoint documents five sortable fields:

| Field       | Sorts by                                       |
| ----------- | ---------------------------------------------- |
| `amount`    | Donation amount                                |
| `date`      | Gift date                                      |
| `donorname` | Donor's display name                           |
| `id`        | Gift's internal ID — best for stable iteration |
| `status`    | Gift's status integer                          |

For pagination through large result sets, sort by `id` — it's the only field guaranteed to be stable across pages even if records are inserted or updated concurrently.

***

## Pattern 2: structured filter with `POST /api/Gift/query`

For anything beyond free-text search, use the structured query body. The body fields:

| Field             | Type                     | Description                                                                                        |
| ----------------- | ------------------------ | -------------------------------------------------------------------------------------------------- |
| `skip`            | integer                  | Records to skip                                                                                    |
| `take`            | integer                  | Records per page                                                                                   |
| `sortBy`          | string                   | Same options as `/list` (`amount`, `date`, `donorname`, `id`, `status`)                            |
| `descending`      | boolean                  | Sort direction                                                                                     |
| `includeDetails`  | boolean                  | Include related entities                                                                           |
| `queryType`       | integer                  | Required — the integer for the Gift query type. Discover via `GET /api/Query/options/{queryType}`. |
| `groups`          | array of QueryGroupModel | The filter structure                                                                               |
| `selectedColumns` | array of strings         | Subset of fields to return per item                                                                |
| `filter`          | string                   | Free-text filter (same role as `Filter` on `/list`)                                                |

<Warning>
  Query parameters on `/list` are **PascalCase** (`Skip`, `Take`, `SortBy`, `Descending`, `IncludeDetails`, `Filter`). Body fields on `/query` are **camelCase** (`skip`, `take`, `sortBy`, `descending`, `includeDetails`, `filter`). The casing difference is intentional in the current spec — sending the wrong shape on either endpoint will produce unexpected results.
</Warning>

### Common filter scenarios

#### Gifts within a date range

```bash cURL theme={null}
curl -X POST https://prod-api.raisedonors.com/api/Gift/query \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "skip": 0,
    "take": 1000,
    "sortBy": "id",
    "descending": false,
    "groups": [
      {
        "conditions": [
          { "parameter": "date", "operator": 10, "value": "2025-01-01" },
          { "parameter": "date", "operator": 11, "value": "2025-01-31" }
        ],
        "conjunct": 0
      }
    ]
  }'
```

<Note>
  The `operator` integers (`10`, `11` used here as placeholders for "greater than or equal" and "less than or equal") and the `conjunct` integer (`0` for AND) need to be discovered via `GET /api/Query/options/{queryType}`. The spec doesn't label the `QueryOperator` enum's 30 integer values or the `ConjunctOperator` enum's 2 values. See [Pagination and Filtering: Discovering query options](/raise/pagination#discovering-query-options).
</Note>

#### Gifts above a certain amount

```bash cURL theme={null}
curl -X POST https://prod-api.raisedonors.com/api/Gift/query \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "skip": 0,
    "take": 100,
    "sortBy": "amount",
    "descending": true,
    "groups": [
      {
        "conditions": [
          { "parameter": "amount", "operator": 10, "value": "1000" }
        ]
      }
    ]
  }'
```

Returns large gifts (over \$1,000) sorted from highest to lowest. Useful for major-donor stewardship workflows.

#### Gifts for a specific campaign

```bash cURL theme={null}
curl -X POST https://prod-api.raisedonors.com/api/Gift/query \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "skip": 0,
    "take": 1000,
    "groups": [
      {
        "conditions": [
          { "parameter": "campaignId", "operator": 0, "value": "5678" }
        ]
      }
    ]
  }'
```

Returns all gifts attributed to the specified Campaign. The exact parameter name (`campaignId` here) may vary — confirm via `GET /api/Query/options/{queryType}` for the Gift query type.

#### Gifts in a non-final status

```bash cURL theme={null}
curl -X POST https://prod-api.raisedonors.com/api/Gift/query \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "skip": 0,
    "take": 100,
    "groups": [
      {
        "conditions": [
          { "parameter": "status", "operator": 1, "values": [0, 1, 2] }
        ]
      }
    ]
  }'
```

Returns gifts in any of the listed status integers — useful for "pending" or "in-process" gifts. The exact integer-to-status mapping is in the [Statuses and Lifecycle States](/raise/concepts/statuses-and-lifecycle-states#gift-lifecycle-states) page (with the caveat that the spec doesn't document the mapping itself).

#### Combining filters with AND / OR

A common combined query: recent gifts above a threshold from a specific Campaign:

```json theme={null}
{
  "groups": [
    {
      "conditions": [
        { "parameter": "date", "operator": 10, "value": "2025-01-01" },
        { "parameter": "amount", "operator": 10, "value": "100" },
        { "parameter": "campaignId", "operator": 0, "value": "5678" }
      ],
      "conjunct": 0
    }
  ]
}
```

For OR logic — gifts from either of two campaigns — use multiple condition groups:

```json theme={null}
{
  "groups": [
    {
      "conditions": [
        { "parameter": "campaignId", "operator": 0, "value": "5678" }
      ]
    },
    {
      "conditions": [
        { "parameter": "campaignId", "operator": 0, "value": "9012" }
      ]
    }
  ]
}
```

The exact AND-vs-OR semantics of multiple groups vs. multiple conditions within a group are determined by the `conjunct` integer (0 or 1, mapping to AND or OR — confirm via discovery). For complex multi-criteria filters, prototype against the live API to confirm the resulting logic matches your intent.

***

## Reducing payload size

For bulk reads where you don't need every field, `selectedColumns` reduces the per-item payload:

```bash cURL theme={null}
curl -X POST https://prod-api.raisedonors.com/api/Gift/query \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "skip": 0,
    "take": 1000,
    "selectedColumns": ["id", "amount", "date", "donorId", "status"],
    "groups": [
      {
        "conditions": [
          { "parameter": "date", "operator": 10, "value": "2025-01-01" }
        ]
      }
    ]
  }'
```

The response items will contain only `id`, `amount`, `date`, `donorId`, and `status` — much smaller per record than the default full payload. For workflows that aggregate millions of gifts, this can be the difference between a 10-minute query and a one-hour query.

The set of selectable columns is discovered via `GET /api/Query/options/{queryType}`.

***

## Iteration patterns for bulk reads

### Standard skip/take loop

For result sets in the hundreds or low thousands:

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

  do {
    const response = await fetch(
      'https://prod-api.raisedonors.com/api/Gift/query',
      {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${process.env.RAISE_API_TOKEN}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          skip,
          take,
          sortBy: 'id',
          descending: false,
          groups: [
            {
              conditions: [
                { parameter: 'date', operator: 10, value: startDate },
                { parameter: 'date', operator: 11, value: endDate },
              ],
              conjunct: 0,
            },
          ],
        }),
      }
    );

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

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

  return allGifts;
}
```

### ID-cursor iteration for very large sets

For result sets in the tens of thousands or larger, the `skip`-based loop becomes inefficient at high offsets and is vulnerable to inserts shifting the page boundaries. ID-cursor iteration handles both:

```javascript JavaScript theme={null}
async function streamGiftsCursor(filterGroups) {
  const allGifts = [];
  let cursorId = 0;
  const take = 1000;

  while (true) {
    // Add an "id > cursorId" condition to each request
    const groupsWithCursor = [
      ...filterGroups,
      {
        conditions: [
          { parameter: 'id', operator: GT_OPERATOR, value: cursorId.toString() },
        ],
      },
    ];

    const response = await fetch(
      'https://prod-api.raisedonors.com/api/Gift/query',
      {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${process.env.RAISE_API_TOKEN}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          skip: 0,
          take,
          sortBy: 'id',
          descending: false,
          groups: groupsWithCursor,
        }),
      }
    );

    const page = await response.json();
    if (page.items.length === 0) break;

    allGifts.push(...page.items);
    cursorId = page.items[page.items.length - 1].id;
  }

  return allGifts;
}
```

The `GT_OPERATOR` constant comes from discovery via `GET /api/Query/options/{queryType}` at integration startup. See [Pagination and Filtering: ID-cursor iteration](/raise/pagination#id-cursor-iteration-for-very-large-result-sets).

***

## A common pattern: incremental sync

For partner integrations that periodically pull new gifts since the last run:

```javascript JavaScript theme={null}
async function pullNewGiftsSince(lastSyncIso) {
  return streamGiftsCursor([
    {
      conditions: [
        // createdDateTime > lastSyncIso
        { parameter: 'createddatetime', operator: GT_OPERATOR, value: lastSyncIso },
      ],
    },
  ]);
}

// In your sync job
async function dailySync() {
  const lastSync = await checkpointStore.get('gift_sync_last_run');
  const newGifts = await pullNewGiftsSince(lastSync);

  for (const gift of newGifts) {
    await downstreamSystem.ingestGift(gift);
  }

  // Advance the checkpoint
  const latestCreated = newGifts[newGifts.length - 1]?.createdDateTime;
  if (latestCreated) {
    await checkpointStore.set('gift_sync_last_run', latestCreated);
  }
}
```

Three patterns this gets right:

* **Advance the checkpoint to the last successful record's timestamp**, not to the current time. If the sync run crashes mid-stream, the next run resumes from the last actually-processed record.
* **Sort by `id` ascending** within the cursor loop so each page's last record is the highest ID seen so far.
* **Skip an explicit total check** — the cursor loop naturally terminates when a page is empty.

For more on incremental-sync patterns, see [Sync Architecture Patterns](/raise/best-practices/sync-architecture-patterns).

***

## Performance considerations

| Pattern                                               | Why it matters                                                                                                         |
| ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| Use `Take=1000` for bulk reads                        | Each request consumes one rate-limit slot; larger pages mean fewer requests.                                           |
| Sort by `id` for stable iteration                     | Other sort fields can shift pages if records change during iteration.                                                  |
| Use `IncludeDetails=false` for bulk reads             | The default summary representation is smaller per record.                                                              |
| Use `selectedColumns` when you don't need every field | Reduces payload size significantly for queries that only need a few columns.                                           |
| Filter aggressively in the request                    | A query that returns 500 records after filtering is much cheaper than one that returns 50,000 you discard client-side. |
| Cache the QueryOptions discovery result               | The integer-to-label mapping doesn't change often. Cache it at integration startup.                                    |

See [Rate Limits](/raise/rate-limits) for the broader rate-limit-aware design pattern.

***

## Where to go next

<CardGroup cols={2}>
  <Card title="Query Donors by Filters" icon="filter" href="/raise/workflows/query-donors-by-filters">
    The Donor-specific version of these patterns.
  </Card>

  <Card title="Pagination and Filtering" icon="list" href="/raise/pagination">
    The reference-level documentation of both endpoint surfaces and the integer-enum discovery patterns.
  </Card>

  <Card title="Gifts" icon="hand-holding-dollar" href="/raise/concepts/gifts">
    The Gift resource reference — the fields you're querying and selecting.
  </Card>

  <Card title="Reconcile with CRM+" icon="arrows-rotate" href="/raise/workflows/reconcile-with-crm-plus">
    Apply these query patterns to the cross-product reconciliation workflow.
  </Card>
</CardGroup>
