The workflow for reading Donors from Raise — the three lookup surfaces (search, list, query), common filter scenarios, and the iteration patterns for bulk reads and segmentation.
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.
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.
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.
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.
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.
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).
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}.
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:
Endpoint
Returns
GET /api/Donor/{donorId}/gifts
All Gifts by this donor
GET /api/Donor/{donorId}/recurringgifts
All RecurringGifts by this donor
GET /api/Donor/{donorId}/projects
Projects this donor has supported
GET /api/Donor/{donorId}/donor-fields
Custom field values
GET /api/Donor/{donorId}/campaign-statistics
Per-campaign giving summary
GET /api/Donor/{donorId}/segment-statistics
Per-segment giving summary
GET /api/Donor/{donorId}/motivation-statistics
Per-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.
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.
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:
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.