Skip to content

Donations API

Create donations, look up receipts, and read a donor's own giving history.

Money is in cents

amount, tip_amount, and fee_amount are integers of the smallest currency unit (cents for USD). A $100.00 donation is amount: 10000.

Create Donation

POST /api/v1/donations 🔒 Requires Authentication (verified email)

Creates a donation row in pending status. Payment is captured separately via the Payments API / POST /payments/confirm; the donation's payment_status is only ever advanced to paid/refunded by the Stripe-verified confirm + webhook paths. For security, any donor_user_id in the body is ignored — the donor is always the authenticated user (or null for guests).

javascript
const API_BASE = 'https://api.fundlyhub.org/api/v1';

const response = await fetch(`${API_BASE}/donations`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  credentials: 'include',
  body: JSON.stringify({
    fundraiser_id: '123e4567-e89b-12d3-a456-426614174000',
    amount: 10000,          // $100.00 in cents (required, > 0)
    currency: 'USD',        // defaults to USD
    tip_amount: 1500,       // optional platform tip in cents (default 0)
    donor_name: 'Jane Smith',
    donor_email: 'jane@example.com',
    is_anonymous: false,    // default false
    comment: 'Great cause! Keep up the excellent work.' // optional, max 1000 chars
  })
});

const { data } = await response.json();
console.log('Donation created:', data.id, 'status:', data.payment_status);

Returns 201 with { data: <donation row> }. Validation failures return 400 with { error: 'Validation error', details: [...] }.

Look up a receipt

GET /api/v1/donations/receipt/:receiptId

Public receipt lookup keyed by the Stripe payment-intent id (pi_…) or invoice id (in_…) — both high-entropy capability tokens, so this stays unauthenticated for guest donors. Returns an explicit field allowlist (no donor PII beyond what the receipt needs).

javascript
const API_BASE = 'https://api.fundlyhub.org/api/v1';
const receiptId = 'pi_3AbCdEf...';

const response = await fetch(`${API_BASE}/donations/receipt/${receiptId}`);
const { data } = await response.json();
console.log('Amount:', data.amount / 100, data.currency);

Email a receipt

POST /api/v1/donations/receipt/email

Sends a receipt email. Optional auth — donors may not be logged in. Public rate-limited.

javascript
await fetch(`${API_BASE}/donations/receipt/email`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    recipient_email: 'jane@example.com',
    receipt_data: { /* receipt fields */ }
  })
});

Get a fundraiser's donations

GET /api/v1/fundraisers/:fundraiserId/donations

Public list of donations for a fundraiser (anonymous donors are redacted). Supports ?limit and ?offset.

javascript
const fundraiserId = '123e4567-e89b-12d3-a456-426614174000';
const response = await fetch(
  `${API_BASE}/fundraisers/${fundraiserId}/donations?limit=10`
);
const { data } = await response.json();

A donor's own giving history

Authenticated donors read their own history under /donor/me/*:

EndpointPurpose
GET /donor/me/summaryAggregate giving stats.
GET /donor/me/donationsPaginated giving history with year / status / fundraiserId filters.
GET /donor/me/annual-statementYear-end giving summary as PDF or CSV.
javascript
const response = await fetch(
  `${API_BASE}/donor/me/donations?page=1&limit=20&year=2026`,
  { credentials: 'include' }
);
const { data, pagination } = await response.json();

Removed endpoints

GET /donations/:id and PATCH /donations/:id/status were removed for security. The raw single-donation read leaked donor PII and ignored is_anonymous; the status PATCH had no auth and trusted a client-supplied payment status. Status transitions are owned exclusively by the Stripe-verified POST /payments/confirm path and the Stripe webhook. Admin reads go through GET /admin/donations/:id; donors use /donor/me/* and the receipt-token route above.

Response Codes

  • 201 — Donation created
  • 200 — Success
  • 400 — Validation error
  • 401 — Authentication required
  • 404 — Not found
  • 429 — Rate limit exceeded
  • 500 — Server error

Built with VitePress