Donations API
The Donations API lets your own tools record and look up gifts: enter a donation the moment it happens in an external system, search a donor's giving history, or pull gifts by date range for a dashboard. It shares everything with the Constituents API and the Volunteerism API — the same API keys, the same base URL, the same error shape, and the same field vocabulary your donation webhooks already speak.
Authentication
Use the same key you use for the other APIs (Subscription Settings → API Keys, team admins only), sent as a bearer token on every request:
Authorization: Bearer argenta_sk_...
The base URL is https://app.argentasoftware.com/api/v1. All requests and responses are JSON.
Endpoints
| Method & path | What it does |
|---|---|
POST /donations | Record a donation for a constituent. FkConstituent and DonationAmount are required; everything else is optional. |
GET /donations/{id} | Fetch one donation. |
GET /donations?constituentId=... | A constituent's giving history, newest first. |
GET /donations?search=...&startDate=...&endDate=...&minAmount=...&maxAmount=... | Search by donor name or email, filtered by date range and amount range. Paged, max page size 100. |
Record a donation
A donation always belongs to an existing constituent. Create or find the constituent first (the Constituents API upsert is built for exactly this), then:
curl -X POST "https://app.argentasoftware.com/api/v1/donations" \
-H "Authorization: Bearer argenta_sk_..." \
-H "Content-Type: application/json" \
-d '{
"FkConstituent": 424037,
"DonationAmount": 250.00,
"DateOfDonation": "2026-07-24",
"FkCampaign": 4505,
"DesignatedPurpose": "General Fund",
"DonationDescription": "Monthly partner gift via HubSpot"
}'
You get 201 with {"created": true, "donation": {...}}. Argenta treats the
entry exactly like a hand-entered gift: the donor record is found or created for that constituent,
the donor's giving level recalculates, the gift appears in the Donations grid and the donor's
history immediately, and your donation.created webhook fires. The date defaults to
today if you leave it off.
FkCampaign and they count toward that campaign's total alongside
everything raised on your Argenta donation form, so the campaign reports what it actually raised.
If you sold something rather than received a gift (tickets, sponsorships, tee packages), record it
as a campaign sale instead.
Donation fields
| Field | Type | Notes |
|---|---|---|
FkConstituent | int | Required. The constituent who gave. Must exist in your account. |
DonationAmount | decimal | Required. Positive, up to 10,000,000. Immutable once recorded. |
DateOfDonation | date | Defaults to today. |
DonationDescription | string | Free-text note about the gift. |
DesignatedPurpose | string | What the donor designated the gift for. |
CheckNumber | string | For check gifts. |
FkDonationType, FkCategory, FkPaymentMethod | int | Ids from your account's lookup lists, fetched from GET /lookups; null for the default. Unknown ids are rejected. |
FkCampaign | int | The campaign this gift belongs to, so it counts toward that campaign's total. Get the id from GET /lookups/campaigns. Leave it off, or send null or 1, for no campaign. A campaign from another account, or one that is locked or view-only, is rejected. |
IsAnonymous, IsTribute, IsThankYouSent | bool | Flags, all default false. Tribute gifts can carry TributeName and TributeNote. |
Employer, Occupation, EmployerLocation | string | For political teams recording contribution compliance details. 50 characters max — over-length is rejected, never truncated. |
Everything else a donation carries in Argenta — general-ledger posting, funds and accounts,
event attribution, recurring schedules, receipts, refunds — is managed in-app and is not
writable through the API. Unknown fields are rejected with 400 unknown_field
rather than ignored, so a typo can never silently drop data.
On the way back out, a single donation carries FkCampaign as an id, so you can
confirm what a create actually attributed instead of taking the 201 on trust. The
search and history lists carry CampaignTitle as text, so you can reconcile a
campaign without a second call for every row.
Errors
Same shape as the other APIs: an HTTP status plus a JSON body with a machine code and a human message.
| Status | Code | When |
|---|---|---|
| 400 | missing_field / invalid_field / unknown_field | The body failed validation. Nothing was written — a bad field never leaves a blank donation behind. |
| 401 | unauthorized | Missing, malformed, revoked, or unknown key. |
| 403 | insufficient_scope | A read-only key tried to record a donation. |
| 403 | record_protected | The constituent, or the campaign you named, is locked or view-only. |
| 404 | not_found | No donation with that id in your account. |
| 413 | payload_too_large | Body over 64 KB. |
| 429 | rate_limited | Slow down and retry with backoff. |
Rate limits
The same per-key limits as the rest of /api/v1. Batch entry is fine — pace large imports and
honor 429 responses with a backoff and they will sail through.
donation.created,
donation.refunded, donation.receipted, and friends) to keep an external
system in sync in both directions — see Webhooks.