Skip to main content
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.

When to use this workflow

ScenarioApproach
Pulling a quick list of recent gifts for displayGET /api/Gift/list with Take and a sort
Free-text search across gift fieldsGET /api/Gift/list with the Filter parameter
Exporting all gifts within a date rangePOST /api/Gift/query with a date condition
Filtering by donor, campaign, designation, or statusPOST /api/Gift/query with structured conditions
Streaming gifts for a downstream syncPOST /api/Gift/query with ID-cursor iteration
Getting all gifts for a specific donorGET /api/Donor/{donorId}/gifts — cheaper than a Query with a donorId filter
For the underlying mechanics of both endpoint patterns, see Pagination and Filtering.

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

The simplest read. Documented parameters:
ParameterTypeDescription
FilterstringFree-text filter
SkipintegerRecords to skip
TakeintegerRecords to return per page
SortBystringOne of: amount, date, donorname, id, status (case-insensitive)
DescendingbooleanSort direction
IncludeDetailsbooleanInclude related entities (Donor, Gateway, Segment, etc.)

Recent gifts for a dashboard

cURL
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:
FieldSorts by
amountDonation amount
dateGift date
donornameDonor’s display name
idGift’s internal ID — best for stable iteration
statusGift’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:
FieldTypeDescription
skipintegerRecords to skip
takeintegerRecords per page
sortBystringSame options as /list (amount, date, donorname, id, status)
descendingbooleanSort direction
includeDetailsbooleanInclude related entities
queryTypeintegerRequired — the integer for the Gift query type. Discover via GET /api/Query/options/{queryType}.
groupsarray of QueryGroupModelThe filter structure
selectedColumnsarray of stringsSubset of fields to return per item
filterstringFree-text filter (same role as Filter on /list)
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.

Common filter scenarios

Gifts within a date range

cURL
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
      }
    ]
  }'
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.

Gifts above a certain amount

cURL
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

cURL
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

cURL
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 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:
{
  "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:
{
  "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:
cURL
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
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
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.

A common pattern: incremental sync

For partner integrations that periodically pull new gifts since the last run:
JavaScript
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.

Performance considerations

PatternWhy it matters
Use Take=1000 for bulk readsEach request consumes one rate-limit slot; larger pages mean fewer requests.
Sort by id for stable iterationOther sort fields can shift pages if records change during iteration.
Use IncludeDetails=false for bulk readsThe default summary representation is smaller per record.
Use selectedColumns when you don’t need every fieldReduces payload size significantly for queries that only need a few columns.
Filter aggressively in the requestA query that returns 500 records after filtering is much cheaper than one that returns 50,000 you discard client-side.
Cache the QueryOptions discovery resultThe integer-to-label mapping doesn’t change often. Cache it at integration startup.
See Rate Limits for the broader rate-limit-aware design pattern.

Where to go next

Query Donors by Filters

The Donor-specific version of these patterns.

Pagination and Filtering

The reference-level documentation of both endpoint surfaces and the integer-enum discovery patterns.

Gifts

The Gift resource reference — the fields you’re querying and selecting.

Reconcile with CRM+

Apply these query patterns to the cross-product reconciliation workflow.
Last modified on May 20, 2026