> ## Documentation Index
> Fetch the complete documentation index at: https://docs.virtuous.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Campaigns

> The Campaign resource in Raise — fields, goals, sync configuration, and the special operations like replace and toggle-sync.

A Campaign in Raise is a top-level fundraising effort — an annual appeal, a capital campaign, a specific initiative. It owns the Segments and Forms that produce donations and tracks aggregate metrics like total raised, donor count, and conversion rate against configured goals.

Unlike the donation submission and form-management surfaces, the Campaign resource has full CRUD plus several specialized operations. Partner integrations that need to read or manage Campaign-level data have a solid endpoint surface to work with.

## The Campaign record

| Field              | Type    | Description                                          |
| ------------------ | ------- | ---------------------------------------------------- |
| `id`               | integer | The Raise primary key. Immutable after creation.     |
| `name`             | string  | Display name of the campaign.                        |
| `description`      | string  | Free-text description.                               |
| `startDate`        | string  | When the campaign begins.                            |
| `endDate`          | string  | When the campaign ends.                              |
| `duration`         | string  | Computed display string for the campaign's duration. |
| `createdDateTime`  | string  | When the campaign was created in Raise.              |
| `modifiedDateTime` | string  | When any field on the campaign was last changed.     |

### Sync configuration

| Field                          | Type    | Description                                                                                 |
| ------------------------------ | ------- | ------------------------------------------------------------------------------------------- |
| `canSync`                      | boolean | Whether the campaign is configured to sync to downstream products (e.g., CRM+).             |
| `crmKey`                       | string  | The campaign's primary key in the external CRM.                                             |
| `crmKeyUrls`                   | object  | A map of integration types to URL info for linking to the campaign in each external system. |
| `applySegmentToRecurringGifts` | boolean | Whether segment attribution applies to recurring gifts generated by this campaign.          |

The `canSync` flag is particularly important for customers running CRM+ alongside Raise — it controls whether the campaign and its gifts surface in CRM+ at all. See [How Raise Data Flows to CRM+](/raise/concepts/data-flow-to-crm) for details.

### Goal tracking

| Field                      | Type    | Description                                                      |
| -------------------------- | ------- | ---------------------------------------------------------------- |
| `givingGoal`               | number  | The dollar amount the campaign aims to raise (in base currency). |
| `givingGoalFormatted`      | string  | The goal pre-formatted for display.                              |
| `hasGivingGoal`            | boolean | Whether a giving goal is configured.                             |
| `raisedAmount`             | string  | The amount raised against the goal.                              |
| `raisedPercentage`         | number  | The percentage of the goal achieved (0–100).                     |
| `totalGiftGoal`            | integer | The target count of gifts.                                       |
| `hasTotalGiftGoal`         | boolean | Whether a gift-count goal is configured.                         |
| `totalGifts`               | integer | The actual count of gifts received.                              |
| `totalGiftsPercentage`     | number  | The percentage of the gift-count goal achieved.                  |
| `totalDonors`              | integer | The count of distinct donors who have given.                     |
| `averageGiftGoal`          | number  | The target average gift amount.                                  |
| `averageGiftGoalFormatted` | string  | The target pre-formatted for display.                            |
| `hasAverageGiftGoal`       | boolean | Whether an average-gift goal is configured.                      |
| `averageGiftAmount`        | string  | The actual average gift amount, formatted.                       |
| `totalVisitors`            | integer | The count of unique visitors to the campaign's forms.            |
| `conversionRate`           | string  | The visitor-to-donor conversion rate as a percentage string.     |

The goal tracking is comprehensive — partner integrations doing campaign-progress reporting have direct access to the same metrics the customer sees in the Raise admin UI without needing to compute them client-side.

***

## Reading Campaigns

| Pattern         | Endpoint                   | Use                                                                                                  |
| --------------- | -------------------------- | ---------------------------------------------------------------------------------------------------- |
| List all        | `GET /api/Campaign/list`   | Paginated list of all campaigns                                                                      |
| Query by filter | `POST /api/Campaign/query` | Advanced filtering — see [Pagination and Filtering](/raise/pagination#pagination-on-query-endpoints) |
| Get by ID       | `GET /api/Campaign/{id}`   | Single campaign with full detail                                                                     |

The list endpoint uses the standard `Filter`, `Skip`, `Take`, `SortBy`, `Descending`, `IncludeDetails` parameters described in [Pagination and Filtering](/raise/pagination#pagination-on-list-endpoints).

### Reading campaigns for analytics

For partner integrations building campaign-progress dashboards, the goal-tracking fields on `CampaignModel` give you everything you need in a single call per campaign:

```javascript JavaScript theme={null}
async function getCampaignProgress(campaignId) {
  const response = await fetch(
    `https://prod-api.raisedonors.com/api/Campaign/${campaignId}`,
    { headers: { Authorization: `Bearer ${process.env.RAISE_API_TOKEN}` } }
  );
  const campaign = await response.json();

  return {
    name: campaign.name,
    raisedAmount: campaign.raisedAmount,
    raisedPercentage: campaign.raisedPercentage,
    totalGifts: campaign.totalGifts,
    totalGiftsPercentage: campaign.totalGiftsPercentage,
    totalDonors: campaign.totalDonors,
    conversionRate: campaign.conversionRate,
  };
}
```

No need to query Gifts and aggregate them yourself — the campaign record carries the rollup.

***

## Creating and updating Campaigns

### Create

```bash cURL theme={null}
curl -X POST https://prod-api.raisedonors.com/api/Campaign \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "2025 Annual Appeal",
    "description": "Year-end fundraising campaign",
    "startDate": "2025-01-01",
    "endDate": "2025-12-31",
    "givingGoal": 100000,
    "totalGiftGoal": 500,
    "canSync": true
  }'
```

A successful create returns the new Campaign record with its assigned `id`.

### Update

`PUT /api/Campaign/{id}` updates an existing campaign. Send the full record:

```bash cURL theme={null}
curl -X PUT https://prod-api.raisedonors.com/api/Campaign/5678 \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "id": 5678,
    "name": "2025 Annual Appeal - Updated",
    "description": "Year-end fundraising campaign with extended deadline",
    "startDate": "2025-01-01",
    "endDate": "2026-01-31",
    "givingGoal": 150000,
    "canSync": true
  }'
```

The PUT semantics are documented as full-replace — send every field you want to retain. Unlike the Donor endpoint, the Campaign endpoint doesn't expose a PATCH variant in the spec, so partial-update workflows need to GET-then-PUT.

### Delete

```bash cURL theme={null}
curl -X DELETE https://prod-api.raisedonors.com/api/Campaign/5678 \
  -H "Authorization: Bearer YOUR_API_TOKEN"
```

Deleting a Campaign that has dependent Segments, Forms, or Gifts may be blocked by referential integrity. Use the `dependency-count` endpoint to check before attempting deletion — see [Checking dependencies](#checking-dependencies) below.

***

## Special operations

### Toggle sync

`PUT /api/Campaign/{campaignId}/toggle-sync` enables or disables the campaign's CRM sync without requiring a full update:

```bash cURL theme={null}
curl -X PUT https://prod-api.raisedonors.com/api/Campaign/5678/toggle-sync \
  -H "Authorization: Bearer YOUR_API_TOKEN"
```

This is the safer way to flip `canSync` because it's a single-purpose operation — no risk of inadvertently changing other fields. Use it whenever the customer needs to pause or resume sync for a specific campaign.

When sync is disabled (`canSync: false`):

* New gifts created against this campaign continue to flow into Raise normally.
* Those gifts do **not** propagate to CRM+ via the platform sync.
* Existing gifts already synced to CRM+ remain there — toggle-sync doesn't retroactively remove records.

When sync is re-enabled, the platform sync resumes for new gifts. Gifts that occurred during the disabled window are not automatically backfilled — coordinate with the customer's admin team if backfill is needed.

### Replace

`PUT /api/Campaign/replace` migrates dependencies (Segments, Forms, Gifts) from one campaign to another:

```bash cURL theme={null}
curl -X PUT https://prod-api.raisedonors.com/api/Campaign/replace \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "sourceCampaignId": 5678,
    "targetCampaignId": 9012
  }'
```

Useful for campaign restructuring — when the customer realizes mid-year that several Segments belong under a different Campaign than originally configured, `replace` moves them in a single atomic operation.

After the replace completes, the source Campaign typically has no dependencies left and can be safely deleted.

<Note>
  Confirm the exact request body shape for `PUT /api/Campaign/replace` against the live API. The spec's endpoint description says "Replace a campaign with another campaign for its dependencies" but the body structure for source/target identification isn't detailed in the schema.
</Note>

### Checking dependencies

```bash cURL theme={null}
curl https://prod-api.raisedonors.com/api/Campaign/5678/dependency-count \
  -H "Authorization: Bearer YOUR_API_TOKEN"
```

Returns counts of how many Segments, Forms, Gifts, and other resources depend on this Campaign. Use this before attempting deletion to avoid blocked-delete errors, and to surface a clear "this campaign has N gifts attached — are you sure you want to delete it?" warning in your integration's UI.

***

## Campaign lifecycle in practice

A typical Campaign goes through a small number of states over its lifetime:

| State        | What it means                           | Indicators                                                                        |
| ------------ | --------------------------------------- | --------------------------------------------------------------------------------- |
| **Active**   | The campaign is open for donations      | Current date between `startDate` and `endDate`; forms accept submissions          |
| **Closed**   | The campaign's giving window has ended  | Current date after `endDate`; forms may still be visible but typically redirected |
| **Archived** | The campaign is no longer in active use | (No explicit field — customers typically delete or stop syncing)                  |

The Raise spec doesn't expose a separate `isArchived` flag on Campaigns the way it does on Donors. Campaigns past their `endDate` continue to exist with their historical data; the customer chooses whether to delete them (via the API or admin UI) or leave them in place for historical reporting.

***

## Common patterns

### Campaign-by-name lookup

A frequent partner pattern: looking up a Campaign by name rather than ID. The Raise API doesn't expose a dedicated lookup-by-name endpoint, but the Query endpoint handles it:

```javascript JavaScript theme={null}
async function findCampaignByName(name) {
  const response = await fetch(
    'https://prod-api.raisedonors.com/api/Campaign/query',
    {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${process.env.RAISE_API_TOKEN}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        skip: 0,
        take: 1,
        // Use the structured filter — see Pagination and Filtering for the integer operator discovery
        groups: [
          {
            conditions: [
              { parameter: 'name', operator: 0 /* equals — discover via QueryOptions */, value: name },
            ],
          },
        ],
      }),
    }
  );
  const page = await response.json();
  return page.items[0] || null;
}
```

The integer operator value for "equals" needs to be discovered through `GET /api/Query/options/{queryType}` — see [Pagination and Filtering: Discovering query options](/raise/pagination#discovering-query-options).

### Active-campaigns cache

Most partner integrations need a current list of active campaigns. Cache the result at startup and refresh on a slow cadence:

```javascript JavaScript theme={null}
class CampaignCache {
  constructor(token) {
    this.token = token;
    this.campaigns = new Map();
    this.lastRefresh = 0;
    this.ttl = 60 * 60 * 1000; // 1 hour
  }

  async getAll() {
    if (Date.now() - this.lastRefresh < this.ttl && this.campaigns.size > 0) {
      return Array.from(this.campaigns.values());
    }
    await this.refresh();
    return Array.from(this.campaigns.values());
  }

  async refresh() {
    const params = new URLSearchParams({ Take: '1000', SortBy: 'createddatetime' });
    const response = await fetch(
      `https://prod-api.raisedonors.com/api/Campaign/list?${params}`,
      { headers: { Authorization: `Bearer ${this.token}` } }
    );
    const page = await response.json();

    this.campaigns.clear();
    for (const campaign of page.items) {
      this.campaigns.set(campaign.id, campaign);
    }
    this.lastRefresh = Date.now();
  }
}
```

A 1-hour TTL is reasonable for most integrations — Campaigns don't change minute to minute.

***

## Where to go next

<CardGroup cols={2}>
  <Card title="Recurring Gifts" icon="arrows-rotate" href="/raise/concepts/recurring-gifts">
    The other core resource — recurring donation schedules.
  </Card>

  <Card title="How Raise Data Flows to CRM+" icon="arrow-right-arrow-left" href="/raise/concepts/data-flow-to-crm">
    The platform-level sync controlled by the `canSync` flag.
  </Card>

  <Card title="Donation Forms" icon="file-lines" href="/raise/concepts/donation-forms">
    The forms that sit under Campaigns and Segments.
  </Card>

  <Card title="Statuses and Lifecycle States" icon="circle-half-stroke" href="/raise/concepts/statuses-and-lifecycle-states">
    The lifecycle states that apply to Campaigns, Gifts, and RecurringGifts.
  </Card>
</CardGroup>
