Skip to main content
This workflow covers the full lifecycle of a recurring donation schedule: creating one through POST /api/Raise/give, modifying it after creation, handling payment failures, and cancelling when the donor is done. The Raise API doesn’t expose a dedicated POST /api/RecurringGift create endpoint. Schedules are created through the same POST /api/Raise/give path that processes one-time donations — with the isRecurring, frequency, and schedule-related fields set. For the resource-level reference, see Recurring Gifts.

When to use this workflow

ScenarioApproach
Donor opts into recurring giving on a donation formThe form’s submit handler calls POST /api/Raise/give with isRecurring: true.
Partner integration captures a recurring commitment through its own UIIntegration calls POST /api/Raise/give with the recurring fields set.
Updating an existing recurring schedule (amount, frequency, payment method)PUT /api/RecurringGift/{id} with the full updated record.
Cancelling a recurring schedulePUT /api/RecurringGift/{id}/cancel — dedicated endpoint, not a status update.

Creating a recurring schedule

The same POST /api/Raise/give endpoint that processes one-time donations creates recurring schedules when isRecurring is true. This is the only API path for schedule creation.

Required fields for a recurring submission

In addition to the standard amount, paymentMethodId, and paymentMethodType:
FieldTypeDescription
isRecurringbooleanMust be true to create a recurring schedule.
frequencyinteger enumThe schedule’s cadence.
The frequency integer values come from the Frequency enum in the spec: 0, 1, 2, 4, 12, 26, 52, 100. The integer pattern suggests cadence-per-year semantics:
ValueLikely meaning
52Weekly (52 payments per year)
26Biweekly (26 payments per year)
12Monthly (12 payments per year)
4Quarterly (4 payments per year)
2Semi-annual (2 payments per year)
1Annual (1 payment per year)
0, 100Other cadences
⚠️ Spec gap: The Frequency enum integer-to-label mapping is not documented in the Raise OpenAPI spec. The values above are inferred from the integer patterns and common sense, but the authoritative mapping should be confirmed against the live API before relying on it in production.Use GET /api/Query/options/{queryType} to discover the correct values for the RecurringGift query type, or coordinate with the platform team to confirm the mapping.

Optional but commonly-set fields

FieldTypeDescription
startDatestringThe schedule’s start date (when the first charge should occur). If omitted, the first charge runs immediately as part of the submission.
startDateTimeUtcstringThe start date with explicit UTC timezone. Use this for unambiguous scheduling across timezones.
donorDonorRequest blockStandard donor block — same as for one-time donations.
projectsarrayProject allocation — same as for one-time donations. Allocations apply to each recurring payment.
premiumIdintegerPremium given at signup (one-time).
donorPaidCostsbooleanWhether the donor covers processing fees for each recurring payment.
tributeobjectTribute information that applies to the recurring schedule.

A complete recurring submission

cURL
curl -X POST https://prod-api.raisedonors.com/api/Raise/give \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 50.00,
    "currency": "USD",
    "paymentMethodId": "tok_abc123",
    "paymentMethodType": "CreditCard",
    "isRecurring": true,
    "frequency": 12,
    "startDate": "2025-02-15",
    "donor": {
      "firstName": "Bruce",
      "lastName": "Wayne",
      "email": "bruce@wayne.example"
    },
    "projects": [
      { "projectId": 12, "amount": 50.00 }
    ]
  }'
This creates the schedule and processes the first payment, producing two records:
  • A RecurringGift record representing the schedule.
  • A Gift record representing the first payment, with recurringGiftId pointing back to the schedule.
The response is the Gift record from the first payment. The recurringGiftId field on the Gift tells you the ID of the new schedule.

Reading the schedule after creation

JavaScript
async function createRecurringSchedule(request) {
  // 1. Submit the initial recurring donation
  const giftResponse = await fetch(
    'https://prod-api.raisedonors.com/api/Raise/give',
    {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${process.env.RAISE_API_TOKEN}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ ...request, isRecurring: true }),
    }
  );
  const gift = await giftResponse.json();

  // 2. Fetch the schedule using the recurringGiftId from the gift
  const scheduleResponse = await fetch(
    `https://prod-api.raisedonors.com/api/RecurringGift/${gift.recurringGiftId}`,
    { headers: { Authorization: `Bearer ${process.env.RAISE_API_TOKEN}` } }
  );
  const schedule = await scheduleResponse.json();

  return { gift, schedule };
}
The schedule’s nextChargeDate indicates when the next payment will be charged. The schedule advances this field automatically after each successful charge.

Updating a schedule

PUT /api/RecurringGift/{id} updates an existing schedule. The Raise spec doesn’t expose a PATCH variant — use the GET-then-PUT pattern for partial updates:
JavaScript
async function updateRecurringSchedule(recurringGiftId, changes) {
  // 1. Read the current state
  const current = await fetch(
    `https://prod-api.raisedonors.com/api/RecurringGift/${recurringGiftId}`,
    { headers: { Authorization: `Bearer ${process.env.RAISE_API_TOKEN}` } }
  ).then((r) => r.json());

  // 2. Merge the changes
  const updated = { ...current, ...changes };

  // 3. Write back
  const response = await fetch(
    `https://prod-api.raisedonors.com/api/RecurringGift/${recurringGiftId}`,
    {
      method: 'PUT',
      headers: {
        Authorization: `Bearer ${process.env.RAISE_API_TOKEN}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(updated),
    }
  );

  if (!response.ok) {
    throw new Error(`Update failed: ${response.status}`);
  }
  return response.json();
}

Common update scenarios

Change the recurring amount:
JavaScript
// New amount must match the projectAllocation total
await updateRecurringSchedule(9876, {
  amount: 75.00,
  projectAllocation: [{ projectId: 12, amount: 75.00 }],
});
When changing amount, the projectAllocation entries must sum to the new amount. The API rejects schedule updates where the allocation total doesn’t match. Update both together in a single PUT.
Change the frequency:
JavaScript
// Switch from monthly (12) to quarterly (4)
await updateRecurringSchedule(9876, {
  frequency: 4,
  // The next charge will use the new cadence
});
Change the next charge date:
JavaScript
// Postpone the next charge by a week
await updateRecurringSchedule(9876, {
  nextChargeDate: '2025-03-08',
});
Change the designation:
JavaScript
// Reallocate across two projects instead of one
await updateRecurringSchedule(9876, {
  projectAllocation: [
    { projectId: 12, amount: 30.00 },
    { projectId: 47, amount: 20.00 },
  ],
});

Handling payment failures

The most operational consideration for recurring schedules: payments fail. Cards expire, accounts close, balances run low. The schedule’s hasPaymentFailed flag becomes true when a charge attempt fails, and the schedule pauses until the donor’s payment method is updated.

Detecting failures

Query for schedules with failed payments on a regular cadence (typically daily):
JavaScript
async function findSchedulesWithFailedPayments() {
  const response = await fetch(
    'https://prod-api.raisedonors.com/api/RecurringGift/query',
    {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${process.env.RAISE_API_TOKEN}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        skip: 0,
        take: 1000,
        groups: [
          {
            conditions: [
              {
                parameter: 'hasPaymentFailed',
                operator: 0 /* equals — discover via QueryOptions */,
                value: 'true',
              },
            ],
          },
        ],
      }),
    }
  );
  return (await response.json()).items;
}

Notifying the donor

For each schedule with hasPaymentFailed: true, the typical recovery workflow:
1

Generate a personalized donor page

Use POST /api/Donor/{donorId}/generate-page to create a URL pre-filled with the donor’s identity. The donor will need to update their payment method on this page.
2

Send the donor an email

Include the personalized URL and a clear explanation of why the schedule is paused. Most donors respond quickly when the message is clear.
3

Track the outreach

Record that you’ve contacted the donor and when. Subsequent failed-schedule queries should skip donors you’ve already notified within a reasonable window to avoid spamming.
4

Re-check the schedule

After the donor updates their payment method (typically through the page link), the next scheduled charge will succeed and hasPaymentFailed will return to false.
The donor-update flow happens through the customer’s hosted Raise page — your integration doesn’t update the schedule’s payment method through the API. This is by design: card data tokenization happens client-side at the page, never on your servers.

Detecting upcoming expirations (proactive)

A more effective pattern than waiting for failures: check for soon-to-expire cards before they cause failures. The expMonthAndYear field on a RecurringGift exposes the card’s expiration:
JavaScript
async function findSchedulesExpiringSoon(monthsAhead = 2) {
  // Read all active schedules with paginated list
  const allActive = await listAllActiveRecurringGifts();
  const now = new Date();
  const cutoff = new Date(now.getFullYear(), now.getMonth() + monthsAhead, 1);

  return allActive.filter((schedule) => {
    if (!schedule.expMonthAndYear) return false;
    const [month, year] = schedule.expMonthAndYear.split('/').map(Number);
    const expDate = new Date(year, month - 1, 1);
    return expDate < cutoff;
  });
}
The exact format of expMonthAndYear (e.g., MM/YYYY, MM/YY) is not documented in the spec. Confirm the format against live data before parsing in production.
Notify donors with upcoming expirations through the same personalized-page flow. Response rates for proactive outreach are typically much higher than for after-the-fact failure recovery.

Cancelling a schedule

Cancellation has a dedicated endpoint. Don’t try to cancel by setting status directly via PUT:
cURL
curl -X PUT https://prod-api.raisedonors.com/api/RecurringGift/9876/cancel \
  -H "Authorization: Bearer YOUR_API_TOKEN"
After cancellation:
  • The schedule’s status changes to the cancelled state.
  • No further charge cycles run against the schedule.
  • Any already-processed Gifts produced by the schedule remain attached for historical reference.
  • The donor’s payment method is no longer used by this schedule.

When to cancel vs. pause

The Raise API exposes cancellation as a final operation, not a pause. There’s no PUT /api/RecurringGift/{id}/pause endpoint in the current spec. If a donor wants to temporarily stop giving:
  • For a known short pause: cancel and let the donor sign up for a new schedule when ready.
  • For a longer or indefinite pause: cancellation is the path.
Once cancelled, whether a schedule can be reactivated depends on the platform’s lifecycle rules. The API doesn’t document an “uncancel” or “resume” operation. Confirm with the platform team before designing any workflow that relies on reactivating cancelled schedules.

Reading a schedule’s payment history

The GET /api/RecurringGift/{id}/activities endpoint returns the schedule’s payment activity — successful charges, failed attempts, and other lifecycle events:
cURL
curl https://prod-api.raisedonors.com/api/RecurringGift/9876/activities \
  -H "Authorization: Bearer YOUR_API_TOKEN"
Use this to build donor-facing payment history views, to investigate schedules with persistent failures, or to reconcile recurring payments against your integration’s records. The activity record represents each cycle’s outcome. Successful cycles produce a Gift record; failed cycles produce an activity entry but no Gift.

Transferring a schedule to another donor

A schedule attached to the wrong donor can be transferred without cancellation:
cURL
curl -X PUT https://prod-api.raisedonors.com/api/Donor/transfer-recurring-gift \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "recurringGiftId": 9876,
    "nextDonorId": 67890
  }'
The schedule’s donorId updates to the new donor. Future Gifts produced by the schedule belong to the new donor. Historical Gifts already produced by the schedule remain attached to the original donor unless transferred separately via transfer-gift. See Donors: Transfer a gift to another donor for the parallel operation on individual Gifts.

A complete schedule-management snippet

Pulling the workflow together — create, monitor for failures, cancel:
JavaScript
class RecurringGiftManager {
  constructor(token) {
    this.token = token;
    this.headers = {
      Authorization: `Bearer ${token}`,
      'Content-Type': 'application/json',
    };
  }

  async create({ amount, paymentMethodId, paymentMethodType, frequency, donor, projects, startDate }) {
    const response = await fetch(
      'https://prod-api.raisedonors.com/api/Raise/give',
      {
        method: 'POST',
        headers: this.headers,
        body: JSON.stringify({
          amount, paymentMethodId, paymentMethodType,
          isRecurring: true, frequency, startDate, donor, projects,
        }),
      }
    );
    if (!response.ok) {
      const problem = await response.json();
      throw new Error(`Recurring creation failed: ${problem.detail || problem.title}`);
    }
    return response.json();
  }

  async findFailedSchedules() {
    const response = await fetch(
      'https://prod-api.raisedonors.com/api/RecurringGift/query',
      {
        method: 'POST',
        headers: this.headers,
        body: JSON.stringify({
          skip: 0,
          take: 1000,
          groups: [{
            conditions: [
              { parameter: 'hasPaymentFailed', operator: 0, value: 'true' },
            ],
          }],
        }),
      }
    );
    return (await response.json()).items;
  }

  async cancel(recurringGiftId) {
    const response = await fetch(
      `https://prod-api.raisedonors.com/api/RecurringGift/${recurringGiftId}/cancel`,
      { method: 'PUT', headers: this.headers }
    );
    if (!response.ok) {
      throw new Error(`Cancel failed: ${response.status}`);
    }
    return response.status === 204 ? null : await response.json();
  }
}

Where to go next

Process a Donation

The one-time donation flow that shares the POST /api/Raise/give path.

Handle Failed Payments

The full failure-handling workflow including one-time payment failures and gateway-level errors.

Recurring Gifts

The RecurringGift resource reference with all 50+ fields documented.

Webhooks Overview

React to recurring payment events in real time.
Last modified on May 20, 2026