Skip to main content
Reading Donor records is the foundation of most analytics, reporting, and sync workflows. Partner integrations query donors to build mailing lists, segment for stewardship, sync to external CRMs, and produce donor-level reports. Raise exposes three distinct read surfaces for donors — GET /api/Donor/search for free-text lookups, GET /api/Donor/list for paginated lists with simple filters, and POST /api/Donor/query for structured filtering. This workflow covers when to use each. For the resource-level reference of the Donor record, see Donors.

When to use this workflow

ScenarioApproach
Type-ahead suggestions in a UI (“find donor named Bruce”)GET /api/Donor/search
Quick “is this email already a donor?” checkGET /api/Donor/search — see Create or Find a Donor
Recent donors for a dashboardGET /api/Donor/list with Take and a sort
Top donors by lifetime givingGET /api/Donor/list with SortBy=giftsum&Descending=true
Bulk export of donors matching specific criteriaPOST /api/Donor/query with structured conditions
Donors with no recent activity (for re-engagement)POST /api/Donor/query with date conditions on modifieddatetimeutc
Streaming donors for downstream syncPOST /api/Donor/query with ID-cursor iteration

Pattern 1: free-text search with GET /api/Donor/search

The simplest lookup. The endpoint accepts a single filter query parameter and matches across multiple donor fields:
Search for donors by a filter string. Can match on donor ID, first name, last name, organization name, phone number, email address, postal code, and state. Supports searching by first/last name combinations, name with state, name with postal code, phone number in (xxx) xxx-xxxx format, and email in angle brackets.
cURL
curl "https://prod-api.raisedonors.com/api/Donor/search?filter=bruce@wayne.example" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
Supported filter formats:
Input shapeMatches
Bare email (bruce@wayne.example)Email field
Email in angle brackets (<bruce@wayne.example>)Email field (exact match, less ambiguity)
Phone in (xxx) xxx-xxxx formatPhone field
First name only (Bruce)First-name match
First and last name (Bruce Wayne)Combined name match
Name with state (Bruce Wayne NJ)Name plus state
Name with postal code (Bruce Wayne 10001)Name plus postal code
Bare donor ID (12345)ID match
The endpoint is best for interactive lookups where the user can type a flexible search term. For deterministic exact-match queries, prefer the structured query in Pattern 3.

When search returns multiple matches

Common scenarios:
  • A name search where multiple donors share the name.
  • An email search where a donor has the email on multiple records (typically a data-quality issue worth flagging).
  • A postal-code search that hits many donors in the same area.
Handle the multi-match case explicitly in your integration:
JavaScript
async function findDonorOrSurface(searchTerm) {
  const response = await fetch(
    `https://prod-api.raisedonors.com/api/Donor/search?filter=${encodeURIComponent(searchTerm)}`,
    { headers: { Authorization: `Bearer ${process.env.RAISE_API_TOKEN}` } }
  );
  const results = await response.json();

  if (results.length === 0) return { match: null, candidates: [] };
  if (results.length === 1) return { match: results[0], candidates: [] };

  // Multiple matches — surface for human disambiguation
  return { match: null, candidates: results };
}
For programmatic flows that need to act on a single result (e.g., “find this donor by email and update them”), use Pattern 3’s exact-match query instead.

Pattern 2: paginated list with GET /api/Donor/list

For ranked or sorted lists of donors. Parameters:
ParameterTypeDescription
FilterstringFree-text filter
SkipintegerRecords to skip
TakeintegerRecords per page
SortBystringOne of the documented sort fields below (case-insensitive)
DescendingbooleanSort direction
IncludeDetailsbooleanInclude addresses, contact methods, etc.

Documented sort fields

The Raise spec documents these sort options for GET /api/Donor/list:
createdate
createddatetime
email
firstname
gifts
giftsum
id
lastname
modifieddatetimeutc
name
phone
Notable choices:
Sort fieldUse
giftsumTotal lifetime giving — for “top donors” lists
giftsCount of gifts given — for “most engaged donors” lists
createddatetimeNewest donors first
modifieddatetimeutcRecently-updated donors — useful for incremental sync
idStable iteration order — use for bulk reads
name, firstname, lastnameAlphabetical for display

Top donors by lifetime giving

cURL
curl -G "https://prod-api.raisedonors.com/api/Donor/list" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  --data-urlencode "Take=25" \
  --data-urlencode "SortBy=giftsum" \
  --data-urlencode "Descending=true"
Returns the 25 donors with the highest total lifetime giving. Useful for major-donor cultivation dashboards.

Recently-active donors

cURL
curl -G "https://prod-api.raisedonors.com/api/Donor/list" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  --data-urlencode "Take=100" \
  --data-urlencode "SortBy=modifieddatetimeutc" \
  --data-urlencode "Descending=true"
Returns donors with the most recent activity. Combined with a checkpoint, this is a viable lightweight sync pattern — see incremental sync below.

Pattern 3: structured query with POST /api/Donor/query

For anything beyond free-text and basic sort. The body has the same shape as other /query endpoints (see Pagination and Filtering: Pagination on /query endpoints):
{
  "skip": 0,
  "take": 1000,
  "sortBy": "id",
  "descending": false,
  "includeDetails": false,
  "queryType": 0,
  "groups": [
    {
      "conditions": [
        { "parameter": "email", "operator": 0, "value": "bruce@wayne.example" }
      ]
    }
  ]
}
As with all Raise query endpoints, the operator integer values (0–29) and queryType integer values (0–9) are not labeled in the OpenAPI spec. Discover the valid integers via GET /api/Query/options/{queryType} at integration startup. See Pagination and Filtering: Discovering query options.

Common donor filter scenarios

Donors with no recent activity

cURL
curl -X POST https://prod-api.raisedonors.com/api/Donor/query \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "skip": 0,
    "take": 1000,
    "sortBy": "modifieddatetimeutc",
    "descending": false,
    "groups": [
      {
        "conditions": [
          { "parameter": "modifieddatetimeutc", "operator": 11, "value": "2024-07-01" }
        ]
      }
    ]
  }'
Returns donors whose modifieddatetimeutc is before mid-2024 — candidates for re-engagement campaigns. The operator: 11 placeholder represents “less than” (discover the actual integer via QueryOptions).

High-value donors

cURL
curl -X POST https://prod-api.raisedonors.com/api/Donor/query \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "skip": 0,
    "take": 100,
    "sortBy": "giftsum",
    "descending": true,
    "groups": [
      {
        "conditions": [
          { "parameter": "giftsum", "operator": 10, "value": "10000" }
        ]
      }
    ]
  }'
Returns donors with lifetime giving above $10,000, sorted by total descending. Useful for major-donor segmentation.

Donors by state (organization-wide segmentation)

cURL
curl -X POST https://prod-api.raisedonors.com/api/Donor/query \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "skip": 0,
    "take": 1000,
    "groups": [
      {
        "conditions": [
          { "parameter": "state", "operator": 0, "value": "NJ" }
        ]
      }
    ]
  }'
Returns donors with addresses in New Jersey. Useful for region-specific outreach.

Excluding archived and test-mode donors

For production reports, always filter out archived and test-mode donors:
{
  "skip": 0,
  "take": 1000,
  "groups": [
    {
      "conditions": [
        { "parameter": "isArchived", "operator": 0, "value": "false" },
        { "parameter": "isTestMode", "operator": 0, "value": "false" }
      ],
      "conjunct": 0
    }
  ]
}
A common pattern: build a helper function that wraps every query with these filters automatically:
JavaScript
function productionDonorFilters(additionalConditions = []) {
  return [
    {
      conditions: [
        { parameter: 'isArchived', operator: EQUALS, value: 'false' },
        { parameter: 'isTestMode', operator: EQUALS, value: 'false' },
        ...additionalConditions,
      ],
      conjunct: AND_CONJUNCT,
    },
  ];
}
This makes it harder to accidentally include archived or test data in production-facing reports.

Reducing payload size

For bulk reads where you don’t need every field, selectedColumns reduces per-item payload:
cURL
curl -X POST https://prod-api.raisedonors.com/api/Donor/query \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "skip": 0,
    "take": 1000,
    "selectedColumns": ["id", "email", "name", "giftsum"],
    "groups": [
      {
        "conditions": [
          { "parameter": "giftsum", "operator": 10, "value": "100" }
        ]
      }
    ]
  }'
For a mailing-list export that only needs id, email, name, and lifetime giving, the smaller response is significantly faster than the full record. The set of valid column names is discovered via GET /api/Query/options/{queryType}.

IncludeDetails for full donor records

The opposite case: when you do need full records with addresses and contact methods, set includeDetails: true:
{
  "skip": 0,
  "take": 100,
  "includeDetails": true,
  "groups": [
    {
      "conditions": [
        { "parameter": "id", "operator": 0, "value": "12345" }
      ]
    }
  ]
}
This is much heavier per record — the spec calls out the performance impact explicitly on POST /api/Donor/query:
When includeDetails=true, the response includes all related entities (DonorAddresses, DonorContactMethods) similar to the GET by ID endpoint. This may impact performance for large result sets.
Use includeDetails: true sparingly for bulk reads. For exporting one record at a time, GET /api/Donor/{donorId} is the cleaner path.
Several donor sub-resources have dedicated paginated endpoints — use these instead of filtered queries when the workflow is donor-scoped:
EndpointReturns
GET /api/Donor/{donorId}/giftsAll Gifts by this donor
GET /api/Donor/{donorId}/recurringgiftsAll RecurringGifts by this donor
GET /api/Donor/{donorId}/projectsProjects this donor has supported
GET /api/Donor/{donorId}/donor-fieldsCustom field values
GET /api/Donor/{donorId}/campaign-statisticsPer-campaign giving summary
GET /api/Donor/{donorId}/segment-statisticsPer-segment giving summary
GET /api/Donor/{donorId}/motivation-statisticsPer-motivation giving summary
These are typically cheaper than equivalent filtered queries on /api/Gift/query because the filter is applied at the source rather than evaluated across the full gift table.

Iteration patterns

Standard skip/take loop

JavaScript
async function exportDonorsMatching(filterGroups) {
  const allDonors = [];
  let skip = 0;
  const take = 1000;
  let total = null;

  do {
    const response = await fetch(
      'https://prod-api.raisedonors.com/api/Donor/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: filterGroups,
        }),
      }
    );

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

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

  return allDonors;
}

Incremental sync

For partner integrations that periodically pull updated donors since the last run:
JavaScript
async function pullUpdatedDonorsSince(lastSyncIso) {
  return exportDonorsMatching([
    {
      conditions: [
        { parameter: 'modifieddatetimeutc', operator: GT_OPERATOR, value: lastSyncIso },
      ],
    },
  ]);
}

async function dailyDonorSync() {
  const lastSync = await checkpointStore.get('donor_sync_last_run');
  const updated = await pullUpdatedDonorsSince(lastSync);

  for (const donor of updated) {
    await downstreamSystem.upsertContact(donor);
  }

  // Advance the checkpoint
  const latestModified = updated[updated.length - 1]?.modifiedDateTime;
  if (latestModified) {
    await checkpointStore.set('donor_sync_last_run', latestModified);
  }
}
The same patterns apply as in Query Gifts: incremental sync — advance the checkpoint to the last successfully-processed record’s timestamp, not to the current time.

ID-cursor iteration for very large sets

For donor counts in the hundreds of thousands, the skip-based loop becomes inefficient. Use ID-cursor iteration:
JavaScript
async function streamDonorsCursor(baseFilterGroups) {
  const allDonors = [];
  let cursorId = 0;
  const take = 1000;

  while (true) {
    const groupsWithCursor = [
      ...baseFilterGroups,
      {
        conditions: [
          { parameter: 'id', operator: GT_OPERATOR, value: cursorId.toString() },
        ],
      },
    ];

    const response = await fetch(
      'https://prod-api.raisedonors.com/api/Donor/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;

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

  return allDonors;
}

Combining lookups for richer reports

For reports that need both donor and gift detail (a donor-level summary with their recent giving), combine the donor query with per-donor follow-up calls:
JavaScript
async function buildMajorDonorReport() {
  // 1. Get high-value donors with summary fields only
  const majorDonors = await exportDonorsMatching([
    {
      conditions: [
        { parameter: 'giftsum', operator: GT_OPERATOR, value: '10000' },
        { parameter: 'isArchived', operator: EQUALS, value: 'false' },
      ],
      conjunct: AND_CONJUNCT,
    },
  ]);

  // 2. For each, pull the recent gifts and the campaign statistics
  const enriched = await Promise.all(
    majorDonors.map(async (donor) => {
      const [gifts, campaignStats] = await Promise.all([
        fetchDonorGifts(donor.id, { take: 5 }),
        fetchDonorCampaignStatistics(donor.id),
      ]);
      return { donor, recentGifts: gifts, campaignStats };
    })
  );

  return enriched;
}
For large donor sets, throttle the per-donor follow-up calls — see Rate Limits. For very large sets, batch the per-donor reads through a controlled-concurrency queue rather than Promise.all.

Where to go next

Query Gifts by Filters

The Gift-side version of these patterns — useful for reports that combine both.

Donors

The Donor resource reference — fields, sub-resources, and special operations.

Create or Find a Donor

The workflow that combines search, query, and the implicit creation path.

Pagination and Filtering

The reference-level documentation of all three query patterns.
Last modified on May 20, 2026