Skip to content

Donations​

Process and track donations


Create donation​

POST
/donations

Record a pending donation row against a fundraiser. The Stripe fee is computed server-side and the donor is the session's account.
Requires a bearer session and a verified email address, is gated by the features.donations flag, and is protected by reCAPTCHA v3 (action donation) — send the token as recaptcha_token in the body or in the x-recaptcha-token header. Because this route already requires a verified email, a Cognito session may omit the token; an API key or impersonation session without one answers 400. A token that is sent is always verified: below the score threshold answers 403.
This is the bookkeeping half of a gift. Money is moved by POST /payments/create-intent + POST /payments/confirm.

Authorizations​

BearerAuth

In the browser, authentication rides on the httpOnly session cookies set by /cognito/signin or the Google / Apple sign-in at /cognito/oauth/{provider}. For scripts and for Swagger UI testing, paste an API key (fh_live_…, created with POST /api-keys); a Cognito JWT is accepted too. Sign-in does not return a token in its body.

Type
HTTP (bearer)

Parameters​

Header Parameters

x-recaptcha-token

reCAPTCHA v3 token, action donation. Alternative to recaptcha_token in the body.

Type
string
x-app-attest-key-id

iOS only. The App Attest key id (standard base64, as DCAppAttestService.generateKey returns it) of a key registered with POST /app-attest/attest. Send with x-app-attest-assertion.

Type
string
x-app-attest-assertion

iOS only. base64 of the assertion from generateAssertion(keyId, clientDataHash: SHA256(raw request body)). The body must carry a fresh app_attest_challenge. A valid assertion replaces the reCAPTCHA token; the headers alone never do. An invalid one answers 403 with code APP_ATTEST_INVALID, APP_ATTEST_KEY_UNKNOWN (attest a new key) or APP_ATTEST_CHALLENGE_INVALID (fetch a new challenge), unless the request passes reCAPTCHA some other way.

Type
string

Request Body​

application/json
JSON
"string"

Responses​

Donation created

application/json
JSON
{
"data": "string"
}

Playground​

Server
Authorization
Headers
Body

Samples​


List donations for fundraiser​

GET
/fundraisers/{fundraiserId}/donations

Public donor wall for one fundraiser: paid donations only, newest first, with a true total count. Donor email and payment identifiers are never returned, and anonymous gifts have their donor name and avatar stripped. Amounts are net (donation minus the Stripe fee) so the list agrees with the creator's balance.
Only a campaign anyone may open by link (live, ended or paused; not private; not deleted) has a public donor wall. For any other campaign the answer is an empty data array with total: 0, the same as for an unknown id, unless the caller is signed in as the campaign's owner or an admin.

Authorizations​

BearerAuth

In the browser, authentication rides on the httpOnly session cookies set by /cognito/signin or the Google / Apple sign-in at /cognito/oauth/{provider}. For scripts and for Swagger UI testing, paste an API key (fh_live_…, created with POST /api-keys); a Cognito JWT is accepted too. Sign-in does not return a token in its body.

Type
HTTP (bearer)
or

Parameters​

Path Parameters

fundraiserId*
Type
string
Required
Format
"uuid"

Query Parameters

limit

Page size, clamped to 1–200. A missing, zero, negative or non-numeric value means 20.

Type
integer
Minimum
1
Maximum
200
Default
20
offset

A missing, negative or non-numeric value means 0.

Type
integer
Minimum
0
Default
0

Responses​

Paginated list of donations

application/json
JSON
{
"data": [
],
"pagination": {
"limit": 0,
"offset": 0,
"total": 0
}
}

Playground​

Server
Authorization
Variables
Key
Value

Samples​


Get donation by receipt ID​

GET
/donations/receipt/{receiptId}

The donor's receipt. Public — the receipt id is the Stripe PaymentIntent (pi_…) or invoice (in_…) id, a capability token only the donor holds, so treat it as a secret: the response includes the donor's email and card details. Answers for a donation in any payment state, including pending.

Parameters​

Path Parameters

receiptId*

The receipt id, or the donation's PaymentIntent id.

Type
string
Required

Responses​

Receipt details

application/json
JSON
{
"data": {
"id": "string",
"fundraiser_id": "string",
"amount_cents": 0,
"net_amount_cents": 0,
"fee_amount_cents": 0,
"tip_amount_cents": 0,
"currency": "string",
"donor_name": "string",
"donor_email": "string",
"is_anonymous": true,
"payment_status": "string",
"payment_method_type": "string",
"card_brand": "string",
"card_last4": "string",
"receipt_id": "string",
"created_at": "string",
"campaign_title": "string",
"campaign_slug": "string",
"beneficiary_name": "string",
"fundraiser": {
"title": "string",
"slug": "string"
}
}
}

Playground​

Server
Variables
Key
Value

Samples​


Send donation receipt​

POST
/donations/receipt/email

Email the receipt for one donation.

The donation is named by receipt_id and nothing else — every figure
in the email is read from that row, so the caller cannot dictate the
contents. receipt_id is the Stripe PaymentIntent id, which is not
guessable.

recipient_email is deliberately free-form, because "send it to my
accountant" is the feature. What bounds it instead is a cap of 5
sends per donation
, after which this returns 429 for that donation
permanently.

Authentication is optional; the endpoint is rate limited.

Authorizations​

BearerAuth

In the browser, authentication rides on the httpOnly session cookies set by /cognito/signin or the Google / Apple sign-in at /cognito/oauth/{provider}. For scripts and for Swagger UI testing, paste an API key (fh_live_…, created with POST /api-keys); a Cognito JWT is accepted too. Sign-in does not return a token in its body.

Type
HTTP (bearer)
or

Request Body​

application/json
JSON
{
"receipt_id": "pi_3abc123def456",
"recipient_email": "string"
}

Responses​

Receipt sent.

application/json
JSON
{
"success": true,
"message": "string"
}

Playground​

Server
Authorization
Body

Samples​


Get a campaign's top donors​

GET
/fundraisers/{fundraiserId}/top-donors

The campaign's biggest donors, one row per person across all their paid gifts. Anonymous donors keep their total but not their name; they are identified by an opaque anonymous_key. When any donor gave in more than one currency, meta.mixed_currency is true and the totals should not be shown. Public.
Only a campaign anyone may open by link (live, ended or paused; not private; not deleted) has a public ranking. For any other campaign the answer is an empty data array, the same as for an unknown id, unless the caller is signed in as the campaign's owner or an admin.

Authorizations​

BearerAuth

In the browser, authentication rides on the httpOnly session cookies set by /cognito/signin or the Google / Apple sign-in at /cognito/oauth/{provider}. For scripts and for Swagger UI testing, paste an API key (fh_live_…, created with POST /api-keys); a Cognito JWT is accepted too. Sign-in does not return a token in its body.

Type
HTTP (bearer)
or

Parameters​

Path Parameters

fundraiserId*
Type
string
Required
Format
"uuid"

Query Parameters

limit

Rows to return, clamped to 1–25.

Type
integer
Minimum
1
Maximum
25
Default
5

Responses​

Top donors

application/json
JSON
{
"data": [
{
"id": "string",
"anonymous_key": "string",
"is_anonymous": true,
"donor_name": "string",
"donor_avatar": "string",
"total_cents": 0,
"donation_count": 0,
"last_donation_at": "string",
"currency": "string"
}
],
"meta": {
"mixed_currency": true
}
}

Playground​

Server
Authorization
Variables
Key
Value

Samples​


List recent gifts (public feed)​

GET
/donations/recent

Paid gifts for the homepage hero, newest first, in one of two modes:

  • Unscoped (no slugs): the newest limit gifts platform-wide.
  • Per campaign (slugs): the newest perCampaign gifts for
    each named campaign, ranked within that campaign.

Only gifts to public campaigns in active or ended status appear, and gifts a moderator pulled from the feed never do. An anonymous gift has its name, avatar, city and ordinal nulled. amount_cents is the net amount (after the Stripe fee) — the same figure the campaign page shows. Campaign titles are localised as for GET /users/{id}/campaigns.

Publicly cached for 15 seconds. No authentication. Rate limited at 300 requests/minute per IP.

Parameters​

Query Parameters

limit

Unscoped mode only.

Type
integer
Minimum
1
Maximum
24
Default
12
slugs

Comma-separated campaign slugs (or a repeated parameter). At most 6 are used; extras are ignored.

Type
string
perCampaign

Per-campaign mode only.

Type
integer
Minimum
1
Maximum
40
Default
3
lang
Type
string
Valid values
"en""ru""uk""es"

Responses​

The gifts

application/json
JSON
{
"data": [
]
}

Playground​

Server
Variables
Key
Value

Samples​


Get the donor's note​

GET
/donations/receipt/{receiptId}/note

The note already on a paid donation, so the composer can show it and its remaining writes (3 − note_edit_count). data is null when there is no note.

No authentication: the receipt id is the capability. Rate limited at 300 requests/minute per IP.

Parameters​

Path Parameters

receiptId*

The receipt id, or the donation's Stripe PaymentIntent id.

Type
string
Required

Responses​

The note, or null

application/json
JSON
{
"data": {
"id": "string",
"content": "string",
"gif": {
"id": "xT4uQulxzV39haRFjG",
"title": "string",
"width": 480,
"height": 270,
"mp4_url": "https://media2.giphy.com/media/xT4uQulxzV39haRFjG/giphy.mp4",
"webp_url": "https://media2.giphy.com/media/xT4uQulxzV39haRFjG/giphy.webp",
"gif_url": "https://media2.giphy.com/media/xT4uQulxzV39haRFjG/giphy.gif",
"still_url": "https://media2.giphy.com/media/xT4uQulxzV39haRFjG/giphy_s.gif",
"preview_mp4_url": "https://media2.giphy.com/media/xT4uQulxzV39haRFjG/200w.mp4",
"preview_webp_url": "https://media2.giphy.com/media/xT4uQulxzV39haRFjG/200w.webp",
"preview_width": 200,
"preview_height": 113
},
"created_at": "string",
"updated_at": "string",
"note_edit_count": 0,
"retracted": true,
"hidden": true,
"campaign_slug": "string"
}
}

Playground​

Server
Variables
Key
Value

Samples​


Post, edit or retract the donor's note​

POST
/donations/receipt/{receiptId}/note

Writes the note a donor leaves on their receipt, which appears in the campaign's comments. The receipt id is the authorisation — guest donors have no session — and the donation must be paid and to a campaign. A missing, unpaid or person-targeted donation gets the same 404.

Each donation allows 3 writes in total (post, edits and retract combined); after that every write is 429. Read the current note with the GET first so a reload does not spend one. The note is attributed to an account only when the caller is signed in as the donor.

Authentication is optional. Behind features.comments. Rate limited at 5 requests/minute per IP.

Authorizations​

BearerAuth

In the browser, authentication rides on the httpOnly session cookies set by /cognito/signin or the Google / Apple sign-in at /cognito/oauth/{provider}. For scripts and for Swagger UI testing, paste an API key (fh_live_…, created with POST /api-keys); a Cognito JWT is accepted too. Sign-in does not return a token in its body.

Type
HTTP (bearer)
or

Parameters​

Path Parameters

receiptId*

The receipt id, or the donation's Stripe PaymentIntent id.

Type
string
Required

Request Body​

application/json
JSON
{
"content": "string",
"retract": true
}

Responses​

Note retracted

application/json
JSON
{
"data": {
"id": "string",
"retracted": true
}
}

Playground​

Server
Authorization
Variables
Key
Value
Body

Samples​


Powered by VitePress OpenAPI

Built with VitePress