Skip to main content
The decisions a customer makes about how to structure their data in Raise — what’s a Campaign vs. a Segment, when to create a new Project, how to use Custom Fields, when to set crmKey — determine whether the data tells a coherent story years later. Partner integrations are often the first place where these structural decisions show up as friction: a reporting integration that can’t find the gift it expects, a sync that produces duplicate donors, an analytics dashboard that can’t slice by the dimension the customer wants. This page covers the modeling patterns that produce clean Raise data and the decisions to make explicitly during integration onboarding rather than discovering later. The audience is partner integration leads helping a customer think through their Raise setup, and partner engineering teams whose integrations depend on the data shape being predictable.

The five modeling decisions that matter most

In rough order of impact on integration design:
DecisionWhy it matters
How donors are matched and identifiedAffects every downstream sync, deduplication, and donor-level reporting
Campaign / Segment / Form structureDetermines whether per-channel attribution is meaningful or messy
Project (designation) taxonomyDrives the reporting axis for revenue tracking
Custom field placement and namingAdds dimensions partner integrations can use; gets messy if treated as a junk drawer
CRM key seedingWhether cross-product reconciliation is easy or hard
The next sections walk through each.

Decision 1: donor matching

The most consequential modeling decision: how donors are identified and matched across submissions.

Email is the de facto primary key

Raise’s donor-matching algorithm — embedded in POST /api/Raise/give and in donor lookups — primarily matches on email. The implications:
  • Donors with the same email collapse into one record. When a donor gives twice and uses the same email both times, both gifts attach to one Donor.
  • Donors who change emails effectively become new donors. A donor who gave as bruce@wayne.example last year and gives as bruce.wayne@updated.example this year will produce two distinct Donor records unless the integration explicitly reconciles them.
  • Donors who use a household email shared with a partner can produce ambiguous matches. “Bruce and Selina Wayne” giving from a shared email may produce one donor record that represents both — or different records if the form captures both names.

Practices that handle these cases well

For partner integrations interacting with donor records:
PracticeDescription
Use email as the matching key for new donor submissions, matching the platform’s behaviorAvoids creating fragmented donor records
Run periodic deduplication checks to catch cases where the same person has multiple recordsEspecially valuable after email migrations or major account changes
Use PUT /api/Donor/merge for confirmed duplicatesThe platform’s native merge handles the data fold-down
Surface ambiguous matches for human reviewWhen two records share characteristics but not email, the integration shouldn’t decide automatically
Handle the household case explicitlyEither treat shared-email gifts as one donor (default) or capture both names in the donor record and decide downstream
For partner integrations creating new donors via submissions, don’t try to outsmart the matching by uniquifying email addresses with suffixes — the resulting fragmentation is harder to fix later than the duplicate issue you were trying to avoid.

Organization vs. individual donors

The isOrganization flag distinguishes a corporate or foundation donor from an individual. Set it correctly at submission time:
JavaScript
const donor = {
  isOrganization: !!organization,
  firstName: isOrganization ? null : firstName,
  lastName: isOrganization ? null : lastName,
  organizationName: organization || null,
  email: contactEmail,
  // ...
};
Conflating individuals and organizations in the same record (an individual donor whose organizationName is set, or an organization whose firstName is “Wayne” and lastName is “Foundation”) produces messy reporting and odd-looking thank-you emails.

Decision 2: Campaign and Segment structure

Campaigns and Segments are how the customer reports on revenue by appeal, channel, and time period. The structure determines whether reports answer the questions the customer actually asks.

The hierarchy

Campaign
  └── Segment
        └── Donation Form
              └── Gift
  • Campaign = the umbrella effort (e.g., “2025 Annual Appeal”, “Capital Campaign Phase 2”)
  • Segment = the channel or wave within a campaign (e.g., “Email — Q1 2025”, “Direct Mail — March”)
  • Form = the specific donation form (e.g., “Annual Appeal Online Form”, “Donor Renewal Form”)
A gift attributed to a form inherits the form’s segment, which inherits the campaign. So a customer with this hierarchy can report on:
  • All gifts to the 2025 Annual Appeal (campaign level)
  • All gifts driven by email in Q1 2025 (segment level)
  • All gifts from a specific form (form level)

Practices for designing the hierarchy

PracticeDescription
Time-bound campaignsMost campaigns have a start and end date. A “2025 Annual Appeal” closes when 2025 ends; “2026 Annual Appeal” begins anew.
Channel-based segmentsSegments cleanly map to channels — email, direct mail, social, paid. This makes “channel performance” reports straightforward.
Sub-channel segments for major channelsIf email is a primary channel, sub-divide by audience or appeal — “Email — Lapsed Donors Q1”, “Email — Major Donors Q1”.
Form per segment is acceptable when forms are channel-specificA form embedded in an email blast can be a one-off segment; a general form serving multiple channels typically has one segment per channel using URL parameters.
Resist creating campaigns for everythingCampaigns are heavy; not every appeal needs its own. Use Segments for sub-divisions within an ongoing campaign.

Anti-pattern: segments as Projects

A common mistake: using Segments as a substitute for Projects (designations). Segments are about how the donor came in; Projects are about what the donor funded. Don’t conflate them — a gift to the “General Fund” Project that arrived through the “Email Q1” Segment can answer both “how much did email drive?” and “how much went to general operations?” Separately, you get clean intersections.

Working with the structure in partner integrations

When reading gifts for analytics, the attribution fields on GiftModel provide direct access:
JavaScript
function attributeGift(gift) {
  return {
    campaign: gift.campaignName,
    segment: gift.segment,
    segmentName: gift.segmentName,
    form: gift.form?.title,
    formId: gift.form?.id,
    project: gift.projects?.[0]?.projectName,
  };
}
Group, filter, and aggregate by these fields. The customer’s reporting questions (revenue by campaign, by segment, by form) become straightforward aggregations.

Decision 3: Project taxonomy

Projects represent the funding destinations gifts designate to. The taxonomy — what Projects exist, how granular they are, how they evolve over time — determines what reports the customer can produce.

Two opposing pressures

PressureSymptom of going too far
Granularity (more Projects = finer reporting)Hundreds of Projects, none with meaningful giving, hard to maintain
Aggregation (fewer Projects = simpler reporting)One “General Fund” Project hides all the program-level revenue distinctions the customer needs
The right answer is in between, and customer-specific.

Practices for Project taxonomy

PracticeDescription
Start with the customer’s existing program structureTheir accounting team or development office typically has a working program list. Map it 1:1 first.
Aggregate at the right levelIf the customer reports on five programs internally, have five Projects (not 50 sub-programs).
Use one Project per restricted-giving destinationRestricted gifts need their own Projects for accounting compliance.
Keep “General Fund” or equivalent as a ProjectThe default destination for unrestricted gifts.
Don’t reuse Project IDs across reorganizationsWhen a program closes, archive the Project (don’t repurpose its ID for a new program).

Project codes vs. names

Each Project has both a name (display) and a code (stable identifier used in projectOverrideCode URL parameters and similar). The code should:
  • Be short and code-like (GOTHAM-OUTREACH, not Gotham Outreach Program 2025).
  • Be stable across the Project’s lifetime — don’t rename it as the program evolves.
  • Be unique across the organization.
If the code changes, every URL parameter using projectOverrideCode=OLD-CODE breaks. Treat codes as part of the integration’s public contract.

Working with Projects in partner integrations

For analytics integrations, the project allocation is in gift.projects[]:
JavaScript
function revenueByProject(gifts) {
  const totals = new Map();

  for (const gift of gifts) {
    for (const allocation of gift.projects ?? []) {
      const current = totals.get(allocation.projectName) ?? 0;
      totals.set(allocation.projectName, current + allocation.amount);
    }
  }

  return Object.fromEntries(totals);
}
A gift split across multiple Projects contributes to each — the projects[] array on a 100giftsplit60/40hastwoentriesthatsumto100 gift split 60/40 has two entries that sum to 100.

Decision 4: Custom Fields

Custom Fields capture data that doesn’t fit the standard schema — donor preferences, gift-time questions, organizational metadata. Used well, they add dimensions to reporting and integration logic. Used poorly, they become a junk drawer of inconsistent values.

Practices for Custom Field design

PracticeDescription
Treat Custom Fields like database schemasEach field has a clear name, type, expected values, and purpose. Document these before creating fields.
Use Custom Fields on the right resourceA field about the donor (preferred communication method) belongs on the Donor; a field about the gift (event ticket type) belongs on the Gift.
Avoid free-text where an enum would workA “Source” custom field with values like “Google Ads”, “google ads”, “Google ads”, “Google” produces messy reports. Use a constrained list.
Don’t create Custom Fields for fields the standard schema already supportsDon’t add a “Donor Phone” Custom Field — use the standard phone field and donorPhoneNumbers[] sub-resource.
Plan for evolutionAdd a “deprecated” prefix or naming pattern for fields you’re phasing out so integrations know to ignore them.

Working with Custom Fields in partner integrations

For reading Custom Field values on a Donor:
cURL
curl https://prod-api.raisedonors.com/api/Donor/12345/donor-fields \
  -H "Authorization: Bearer YOUR_API_TOKEN"
For Gifts:
cURL
curl https://prod-api.raisedonors.com/api/Gift/9876/gift-fields \
  -H "Authorization: Bearer YOUR_API_TOKEN"
These return the custom field responses captured on the resource. Partner integrations that need to act on Custom Field values (e.g., segmenting donors by a Custom Field response, routing gifts based on event ticket type) pull these alongside the main resource.

Don’t use Custom Fields for everything

A common anti-pattern: treating Custom Fields as the universal extensibility mechanism for every integration’s needs. A partner integration that creates 50 Custom Fields on every customer’s account pollutes the customer’s data model — the customer sees these in their admin UI and has no idea what they’re for. For partner-internal state (sync metadata, processing flags), keep the data in your own database. Use Custom Fields only when:
  • The data is meaningful to the customer’s reporting or staff workflow.
  • The customer would benefit from seeing the field in the Raise admin UI.
  • The data belongs to the donor or gift, not to the integration.

Decision 5: CRM key seeding

For customers running Raise alongside CRM+ (or another external CRM), the crmKey, crmSecondKey, and crmKeyUrls fields link Raise records to their counterparts in the external system. See How Raise Data Flows to CRM+. The decision: when (if ever) should the partner integration seed these values explicitly versus letting the platform sync populate them?

When to seed crmKey explicitly

ScenarioApproach
Partner is creating a Raise record from data that originated in CRM+Seed crmKey with the CRM+ Contact ID at creation time
Partner is creating a Raise record matched to an external CRM identifier the customer has in their handSeed crmKey with that identifier
Partner is creating a Raise record with no external counterpart yetDon’t seed; let the platform sync populate it after the fact

When not to seed

ScenarioWhy
The partner doesn’t know the corresponding CRM+ Contact IDDon’t guess; let the sync find or create the right Contact
The customer’s CRM+ account is new or emptySync will populate crmKey once the Contact is created
The partner integration is read-only against RaiseNo need to write linkage data

Reading vs. writing crmKey

Reading crmKey is always useful — it tells the partner integration whether the record has been synced and which external Contact it links to. Writing crmKey only matters when the partner has authoritative knowledge of the linkage.
JavaScript
// Reading — always useful
function buildCrmLink(donor) {
  const entries = Object.values(donor.crmKeyUrls ?? {});
  return entries[0]?.url ?? null;
}

// Writing — only when authoritative
async function createRaiseFromCrmContact(crmContact, donationDetails) {
  return fetch('https://prod-api.raisedonors.com/api/Raise/give', {
    method: 'POST',
    headers: { /* ... */ },
    body: JSON.stringify({
      ...donationDetails,
      donor: {
        firstName: crmContact.firstName,
        lastName: crmContact.lastName,
        email: crmContact.email,
        crmKey: crmContact.id.toString(),
      },
    }),
  }).then((r) => r.json());
}

Test mode and production separation

Throughout the data model, isTestMode flags appear on submissions, donors, gifts, and recurring gifts. Treat test mode as a first-class data axis:
PracticeDescription
Filter production reports to isTestMode: falseTest data should never appear in donor counts, revenue totals, or campaign performance
Tag test-mode usage clearlyUse distinct donor email domains (test@example.com, dev+timestamp@example.com) so test data is easy to identify
Run dedicated test-mode donations during developmentDon’t develop against production data; use the test-payment-method generator
Clean up test-mode data periodicallyThe customer’s admin team can archive or delete accumulated test records
Partner integrations should also filter isTestMode: true records out of downstream syncs unless explicitly running in a development environment. See Sync Raise Gifts to an External System: Test-mode in production.

Modeling pitfalls to avoid

A few specific anti-patterns that produce messy data:

Pitfall 1: one Project for everything

Every gift designating to “General Fund” produces a single revenue line in reports. The customer can’t slice by program, can’t show donors what their gift funded specifically, can’t distinguish unrestricted from restricted giving. Add at minimum 5–10 Projects to cover the major programs.

Pitfall 2: a Campaign per appeal

The opposite problem: every email blast is a new Campaign. Hundreds of Campaigns accumulate, most with a few gifts each, and the “all campaigns” view becomes unreadable. Use Segments for sub-divisions; reserve Campaigns for the major efforts.

Pitfall 3: Custom Fields as a junk drawer

A customer accumulates 50+ Custom Fields over the years, most with inconsistent values, half of them used by integrations that no longer exist. Periodically audit Custom Fields and archive the ones that aren’t actively used.

Pitfall 4: not setting isOrganization

A foundation giving $50,000 with isOrganization: false and firstName: "Wayne" lastName: "Foundation" looks weird in reports and breaks any logic that filters individual vs. organizational donors. Always set the flag correctly.

Pitfall 5: using free-text where a Project would do

“What did the donor designate to?” captured in a free-text Custom Field rather than as a Project allocation produces unstructured data. Use Projects for designation; Custom Fields for the things that genuinely don’t fit standard fields.

Pitfall 6: keeping test-mode donors in production reports

A common reporting issue: the customer’s “all donors” count includes hundreds of test-mode records from development work. Filter isTestMode: false everywhere production-facing.

Onboarding a new customer

For partner integrations onboarding a new customer, walk through these decisions together:
1

Audit the customer's existing data shape

How many Campaigns? Segments? Projects? Are the conventions consistent? Are there orphaned Custom Fields?
2

Document the canonical taxonomy

Write down what Campaigns and Projects exist, what they mean, and what new ones should be added. This becomes the integration’s contract.
3

Set the integration's expectations

“Our integration filters out test-mode gifts.” “Our integration assumes Projects have stable codes.” Make implicit expectations explicit.
4

Plan for Custom Field needs

If the integration needs Custom Fields, propose them with clear names and types — and confirm the customer wants them in their data model.
5

Coordinate on `crmKey` policy

If the customer also runs CRM+, agree on who’s responsible for what — the partner integration may or may not need to seed linkage data.
6

Document and revisit annually

The data model evolves. Revisit the taxonomy decisions at least once a year to catch drift.
The investment upfront makes every subsequent integration interaction smoother. A customer with a clean data model produces clean reports, clean syncs, and clean handoffs between systems.

Where to go next

API Performance Tips

The performance practices that make integrations against this data model efficient.

Sync Architecture Patterns

How to design integrations that move data between Raise and external systems.

How Raise Data Flows to CRM+

The cross-product implications of the modeling decisions on this page.

The Raise Data Model

The resource-level reference for the data the modeling decisions apply to.
Last modified on May 21, 2026