curl --request POST \
--url https://{organization}.rescueconsole.com/api/fundraising/donations/import \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"dry_run": true,
"rows": [
{
"amount": 2500,
"donated_at": "2026-08-03",
"donor_email": "sam.okafor@contoso.com",
"campaign": "Winter kennel appeal",
"payment_gateway": "stripe",
"external_id": "ch_3PzK8e2eZvKYlo2C1abc"
},
{
"amount": 10000,
"donated_at": "2026-08-05",
"status": "completed"
}
]
}
'const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
dry_run: true,
rows: [
{
amount: 2500,
donated_at: '2026-08-03',
donor_email: 'sam.okafor@contoso.com',
campaign: 'Winter kennel appeal',
payment_gateway: 'stripe',
external_id: 'ch_3PzK8e2eZvKYlo2C1abc'
},
{amount: 10000, donated_at: '2026-08-05', status: 'completed'}
]
})
};
fetch('https://{organization}.rescueconsole.com/api/fundraising/donations/import', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://{organization}.rescueconsole.com/api/fundraising/donations/import"
payload = {
"dry_run": True,
"rows": [
{
"amount": 2500,
"donated_at": "2026-08-03",
"donor_email": "sam.okafor@contoso.com",
"campaign": "Winter kennel appeal",
"payment_gateway": "stripe",
"external_id": "ch_3PzK8e2eZvKYlo2C1abc"
},
{
"amount": 10000,
"donated_at": "2026-08-05",
"status": "completed"
}
]
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"dry_run": true,
"total_rows": 123,
"would_create": 123,
"created": 123,
"skipped": [
{
"line": 123,
"external_id": "<string>",
"reason": "<string>"
}
],
"errors": [
{
"line": 123,
"message": "<string>"
}
],
"total_cents": 123,
"can_detect_duplicates": true,
"preview": [
{
"line": 123,
"amount_cents": 123,
"donated_at": "<string>",
"donor_email": "<string>",
"campaign": "<string>"
}
]
}{
"error": "<string>"
}{
"error": "house_trained must be 1, 0 or null (not assessed)"
}{
"error": "house_trained must be 1, 0 or null (not assessed)"
}Import donations
Records up to 2,000 gifts at once, from rows you have read out of a spreadsheet. Every row is checked before any is written: if one is wrong, nothing is recorded and the answer lists every problem by line. Send dry_run: true first to see the same answer without recording anything. Donors are matched by email and campaigns by name, capitals ignored; an email or a campaign name that matches nothing is an error, and no person is created. A row whose external_id is already on one of your gifts is skipped. Without an external_id column there is nothing to recognize a repeat by, so importing the same rows twice records them twice. Imported gifts start no workflows. Needs write on Fundraising.
curl --request POST \
--url https://{organization}.rescueconsole.com/api/fundraising/donations/import \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"dry_run": true,
"rows": [
{
"amount": 2500,
"donated_at": "2026-08-03",
"donor_email": "sam.okafor@contoso.com",
"campaign": "Winter kennel appeal",
"payment_gateway": "stripe",
"external_id": "ch_3PzK8e2eZvKYlo2C1abc"
},
{
"amount": 10000,
"donated_at": "2026-08-05",
"status": "completed"
}
]
}
'const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
dry_run: true,
rows: [
{
amount: 2500,
donated_at: '2026-08-03',
donor_email: 'sam.okafor@contoso.com',
campaign: 'Winter kennel appeal',
payment_gateway: 'stripe',
external_id: 'ch_3PzK8e2eZvKYlo2C1abc'
},
{amount: 10000, donated_at: '2026-08-05', status: 'completed'}
]
})
};
fetch('https://{organization}.rescueconsole.com/api/fundraising/donations/import', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://{organization}.rescueconsole.com/api/fundraising/donations/import"
payload = {
"dry_run": True,
"rows": [
{
"amount": 2500,
"donated_at": "2026-08-03",
"donor_email": "sam.okafor@contoso.com",
"campaign": "Winter kennel appeal",
"payment_gateway": "stripe",
"external_id": "ch_3PzK8e2eZvKYlo2C1abc"
},
{
"amount": 10000,
"donated_at": "2026-08-05",
"status": "completed"
}
]
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"dry_run": true,
"total_rows": 123,
"would_create": 123,
"created": 123,
"skipped": [
{
"line": 123,
"external_id": "<string>",
"reason": "<string>"
}
],
"errors": [
{
"line": 123,
"message": "<string>"
}
],
"total_cents": 123,
"can_detect_duplicates": true,
"preview": [
{
"line": 123,
"amount_cents": 123,
"donated_at": "<string>",
"donor_email": "<string>",
"campaign": "<string>"
}
]
}{
"error": "<string>"
}{
"error": "house_trained must be 1, 0 or null (not assessed)"
}{
"error": "house_trained must be 1, 0 or null (not assessed)"
}Authorizations
An API key from Settings, API keys. It starts with rc_live_.
Body
Response
What was recorded, or on a dry run what would be. A dry run that finds problems still answers 200, with the problems in errors.
How many gifts the rows make, leaving out the skipped ones.
How many were recorded. 0 on a dry run and whenever there are errors.
Rows whose external_id is already on one of your gifts.
Show child attributes
Show child attributes
Show child attributes
Show child attributes
The sum of the gifts the rows make, in whole cents.
Whether any row has an external_id. When false, importing the same rows again records them again.
The first ten gifts the rows make.
Show child attributes
Show child attributes