Skip to main content
Raise paginates list and query responses with an items/total envelope and a Skip/Take cursor. It also exposes two separate filter surfaces: a simple Filter query parameter on /list endpoints, and an advanced query-builder pattern on /query endpoints. This page covers both, plus the iteration patterns for processing large result sets reliably. The conventions are Raise-specific — they differ from CRM+‘s pagination conventions in ways that partners coming from CRM+ need to know. Two of the most important differences are called out as you encounter them on this page.

The response envelope

Every paginated response from Raise uses the same shape:
{
  "items": [
    {
      "id": 12345,
      "firstName": "Bruce",
      "lastName": "Wayne",
      "email": "bruce@wayne.example"
    }
  ],
  "total": 8421
}
Two fields:
  • items — the array of records for the current page. Its length is at most the requested Take.
  • total — the total count of records matching the query across all pages, not just the current page.
This is items, not list. CRM+ paginated responses use list/total; Raise uses items/total. Code that expects one shape against the other will silently produce empty results.

The two filter surfaces

Raise exposes two distinct mechanisms for filtering results, and they live on different endpoints:
Endpoint patternFilter mechanismUse
/list (GET) — e.g., GET /api/Donor/listSimple Filter query parameter (free-text) plus pagination paramsQuick list views, free-text searches, common-case reads
/query (POST) — e.g., POST /api/Donor/queryStructured query builder in the JSON request body — supports field-level conditions, multi-value matching, AND/OR groupingComplex segmentation, exports, reporting
Most Raise list-and-search workflows use one or the other — not both on the same call. Choose based on what the workflow actually needs.

Pagination on /list endpoints

All /list endpoints accept the same set of query parameters. Below is the canonical example using GET /api/Donor/list:
ParameterTypeDescription
SkipintegerThe number of records to skip before returning results. Use for paging through results.
TakeintegerThe maximum number of records to return in this response.
SortBystringThe field to sort by. Endpoint-specific — see Sort fields per endpoint.
DescendingbooleanIf true, sort in descending order. If false or omitted, sort ascending.
FilterstringA free-text filter that narrows the result set. The exact matching behavior is endpoint-specific.
IncludeDetailsbooleanIf true, include related entities (addresses, contact methods, etc.) in each item. Performance cost — see below.
Query parameter names on /list endpoints use PascalCase: Skip, Take, SortBy, Descending, Filter, IncludeDetails. CRM+ uses camelCase (skip, take, etc.) on its equivalent endpoints. Sending the wrong casing produces unexpected behavior — the request may succeed but the parameter is ignored.Note also that the parameter names in the JSON body on /query endpoints use camelCase — see Pagination on /query endpoints below. This casing inconsistency between the two surfaces is a Raise-specific quirk; partners coming from CRM+ should not assume the same convention applies across endpoint types.

A basic list request

cURL
curl -G https://prod-api.raisedonors.com/api/Donor/list \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  --data-urlencode "Skip=0" \
  --data-urlencode "Take=100" \
  --data-urlencode "SortBy=createddatetime" \
  --data-urlencode "Descending=true"
The same request in JavaScript:
JavaScript
const params = new URLSearchParams({
  Skip: '0',
  Take: '100',
  SortBy: 'createddatetime',
  Descending: 'true',
});

const response = await fetch(
  `https://prod-api.raisedonors.com/api/Donor/list?${params}`,
  { headers: { Authorization: `Bearer ${process.env.RAISE_API_TOKEN}` } }
);

const page = await response.json();
console.log(`${page.items.length} of ${page.total} donors`);

Sort fields per endpoint

The valid SortBy values are endpoint-specific. The Raise spec documents them in the parameter description. For GET /api/Donor/list, the sortable fields are:
createdate
createddatetime
email
firstname
gifts
giftsum
id
lastname
modifieddatetimeutc
name
phone
The values are case-insensitive but spelled in lowercase in the spec. Other /list endpoints (/api/Gift/list, /api/Campaign/list, etc.) have their own sortable-field sets — check the parameter description on the specific endpoint.

The Filter parameter

The Filter query parameter is a free-text search filter on /list endpoints. The Raise OpenAPI spec does not document the exact matching behavior — whether it searches across all fields, specific fields, supports operators, or uses any wildcard syntax. Behavior may vary per endpoint.
The exact matching semantics of the Filter parameter are not documented in the spec. For predictable, structured filtering, prefer the /query endpoint pattern below. Use Filter for quick free-text searches in interactive contexts where the user can iterate on the search term.

The IncludeDetails toggle

Several /list endpoints support an IncludeDetails boolean parameter. When true, the response includes related entities embedded in each item — for GET /api/Donor/list, this means each donor’s addresses and contact methods are included inline. The Raise spec calls this out 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.
Two patterns:
When IncludeDetails is trueWhen IncludeDetails is false
Each item carries full related-entity data — addresses, contact methods, etc.Each item carries summary data only — id, name, email, etc.
Use for export workflows where the full record is neededUse for list views, segment iteration, change detection
Slower per request; less bulk throughputFaster per request; higher bulk throughput
Default to IncludeDetails=false and only request full details when the workflow needs them.

A complete pagination loop

The canonical pattern for iterating through every record matching a list query:
JavaScript
async function listAllDonors() {
  const allDonors = [];
  let skip = 0;
  const take = 1000;       // Use the maximum supported value for bulk reads
  let total = null;

  do {
    const params = new URLSearchParams({
      Skip: skip.toString(),
      Take: take.toString(),
      SortBy: 'id',          // Stable sort for resumable iteration
      Descending: 'false',
    });

    const response = await fetch(
      `https://prod-api.raisedonors.com/api/Donor/list?${params}`,
      { headers: { Authorization: `Bearer ${process.env.RAISE_API_TOKEN}` } }
    );

    if (!response.ok) {
      throw new Error(`Page ${skip} failed: ${response.status}`);
    }

    const page = await response.json();
    if (total === null) {
      total = page.total;
      console.log(`Iterating over ${total} donors`);
    }

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

  return allDonors;
}
Three patterns that matter:
  • Loop condition is skip < total, not page.items.length === take. The last page typically returns fewer items than Take.
  • total is captured from the first response. Capturing it once gives stable bounds even if records are inserted concurrently.
  • Sort by id for stable iteration. Other sort orders may produce inconsistent results if records change during pagination.

Pagination on /query endpoints

The advanced query-builder pattern lives on /query endpoints — POST /api/Donor/query, POST /api/Gift/query, POST /api/Campaign/query, POST /api/RecurringGift/query, POST /api/Segment/query. The pagination and sorting parameters are present, but in a different form: they’re camelCase fields in the JSON body, not query parameters.
{
  "skip": 0,
  "take": 100,
  "sortBy": "createddatetime",
  "descending": true,
  "includeDetails": false,
  "queryType": 0,
  "groups": [
    {
      "conditions": [
        {
          "parameter": "email",
          "operator": 0,
          "value": "@wayne.example"
        }
      ],
      "conjunct": 0
    }
  ]
}
Body fields on /query endpoints use camelCase (skip, take, sortBy, descending, includeDetails). Query parameters on /list endpoints use PascalCase (Skip, Take, SortBy, Descending, IncludeDetails). The casing difference is real and the API enforces it.This is a Raise-specific quirk that is not present in CRM+ (which uses camelCase consistently). The platform team is aware of the inconsistency; reconciliation is expected in a future API version.

The query body fields

FieldTypeDescription
skipintegerRecords to skip — same semantics as Skip on /list
takeintegerMaximum records to return
sortBystringSort field — same valid values as the corresponding /list endpoint
descendingbooleanSort direction
includeDetailsbooleanInclude related entities — same semantics as IncludeDetails on /list
queryTypeintegerThe type of query being executed. See Query types below.
groupsarrayArray of QueryGroupModel — the filter structure
selectedColumnsarray of stringsList of result columns to return for each item — lets you request a subset
filterstringFree-text filter — same role as Filter on /list

The filter structure

The groups[] array carries the structured filter. Each group contains a set of conditions[] and a conjunct operator that determines how the conditions combine:
{
  "groups": [
    {
      "conditions": [
        {
          "parameter": "email",
          "operator": 0,
          "value": "@wayne.example"
        },
        {
          "parameter": "giftsum",
          "operator": 10,
          "value": "1000"
        }
      ],
      "conjunct": 0
    }
  ]
}
Three concepts:
  • parameter is the field being filtered. The valid parameter names per query type are discovered via GET /api/Query/options/{queryType} — see Discovering query options.
  • operator is an integer enum (0–29, 30 possible values). The numeric values map to operators like Equals, Not Equals, Greater Than, Between, etc. — but the spec does not expose the mapping.
  • conjunct is an integer enum (0 or 1) that combines conditions or groups. The two values correspond to AND and OR, but the spec does not document which is which.
A condition can also use values (array) for multi-value matches or secondaryValue for two-bound operators (Between).
The operator and conjunct values are integer enums in the spec without label documentation. Partner integrations must discover the integer-to-meaning mapping through the GET /api/Query/options/{queryType} endpoint (or empirically by testing). Building queries with hardcoded integer literals is fragile until the mapping is published.⚠️ Spec gap: The mapping from QueryOperator integers (0–29) to semantic operators (Equals, Greater Than, Contains, etc.) is not documented in the OpenAPI spec. Future spec updates are expected to label these enum values. Until then, use the discovery endpoint at integration startup and store the mapping in your integration.

Query types

The queryType field on a query request identifies what’s being queried — donor, gift, recurring gift, campaign, etc. Like operator, it’s an integer enum (0–9) without documented labels in the spec. When calling a specific query endpoint (POST /api/Donor/query), the queryType is implied — the endpoint operates on donors. The field is still required in the body, with the integer value matching the donor query type. The GET /api/Query/list/{queryType} and GET /api/Query/options/{queryType} endpoints take the queryType integer as a path parameter, so partners do need to know the integer values to use them.

Discovering query options

The GET /api/Query/options/{queryType} endpoint returns the available parameters, operators, and result fields for a given query type:
cURL
curl https://prod-api.raisedonors.com/api/Query/options/0 \
  -H "Authorization: Bearer YOUR_API_TOKEN"
The response shape is not formally documented in the spec (the response schema is empty). In practice, partners should use this endpoint at integration startup to discover:
  • Valid parameter names for filter conditions
  • The integer-to-label mapping for operators
  • The list of selectable result columns (for selectedColumns)
Cache the discovery result and refresh it on a slow cadence (daily is fine).

Selecting specific columns

The selectedColumns array on a query request lets you request a subset of result fields rather than the full record. For large result sets where the full record is unnecessary, this reduces response payload size:
{
  "queryType": 0,
  "skip": 0,
  "take": 1000,
  "selectedColumns": ["id", "email", "giftsum"],
  "groups": [
    {
      "conditions": [{ "parameter": "giftsum", "operator": 10, "value": "1000" }]
    }
  ]
}
The set of valid column names is discovered via GET /api/Query/options/{queryType} (same endpoint as for parameters and operators).

Iteration patterns

Basic pagination

The same pattern as for /list endpoints, applied to the body fields:
JavaScript
async function queryAllDonors(filterGroups, queryType) {
  const allItems = [];
  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({
          queryType,
          skip,
          take,
          sortBy: 'id',
          descending: false,
          groups: filterGroups,
        }),
      }
    );

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

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

  return allItems;
}

ID-cursor iteration for very large result sets

For result sets in the tens of thousands of records or larger, the skip-based loop has two costs: server-side performance degrades at high skip values, and concurrent inserts can shift offsets. An ID-cursor pattern avoids both:
JavaScript
async function queryAllDonorsCursor(baseFilterGroups, queryType, idOperator) {
  // idOperator: the integer enum value for "Greater Than" — discover via /api/Query/options
  const allItems = [];
  let cursorId = 0;
  const take = 1000;

  while (true) {
    // Add an ID > cursorId condition to each request
    const groupsWithCursor = [
      ...baseFilterGroups,
      {
        conditions: [
          { parameter: 'id', operator: idOperator, 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({
          queryType,
          skip: 0,
          take,
          sortBy: 'id',
          descending: false,
          groups: groupsWithCursor,
        }),
      }
    );

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

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

  return allItems;
}
The cursor approach requires knowing the integer operator value for “Greater Than” — which you discover via GET /api/Query/options/{queryType} at integration startup.

Performance considerations

Use the largest Take the endpoint supports

Each request consumes one slot in your rate-limit budget regardless of how many records it returns. A Take=1000 request returns 1,000 records for the cost of one request; a Take=25 request returns 25. For bulk operations, use the maximum the endpoint accepts. For interactive list views in a UI, smaller pages (Take=25 or Take=50) load faster and don’t fetch records the user never sees.

Filter aggressively in the request, not client-side

A query that returns 500 records after filtering is much cheaper than one that returns 50,000 you discard client-side. Push filters into the request — either as Filter on /list or as groups[] on /query — wherever possible.

Use IncludeDetails=false when you can

Embedded related entities (includeDetails: true) significantly increases response payload size. Use it only when the workflow needs the full record. For change detection, segment iteration, and list views, the default summary representation is enough.

Use selectedColumns on /query endpoints

For workflows that only need a handful of fields per record, selectedColumns reduces payload size without sacrificing pagination throughput.

Where to go next

Rate Limits

The throttling patterns that pair with pagination strategy.

The Raise Data Model

Understand what parameter values mean per query type.

Query Gifts by Filters

The Gift-specific version of the query patterns on this page.

Query Donors by Filters

The Donor-specific version of the query patterns on this page.
Last modified on May 21, 2026