FundlyHub API
Build fundraising integrations with the FundlyHub REST API.
Building an app?
Building a native or mobile client covers Cognito sign-in with PKCE for Apple and Google, bearer tokens and refresh, where the machine-readable spec lives (/openapi.json · /openapi.yaml), and the paging, error, rate-limit and retry conventions.
Base URL
https://api.fundlyhub.org/api/v1
The health probes are the exception — GET /health (liveness) and GET /ready (readiness) are served at the host root, not under /api/v1.
What you can do
| Capability | Endpoint | Auth |
|---|---|---|
| List campaigns | GET /fundraisers | None |
| Read one campaign | GET /fundraisers/:id · GET /fundraisers/slug/:slug | None (owner-only for unapproved or private ones) |
| Create a campaign | POST /fundraisers | Session + verified email (drafts excepted) |
| Take a payment | POST /payments/create-intent → POST /payments/confirm | Optional session; reCAPTCHA enforced |
| Record a donation | POST /donations | Session + verified email |
| Read the donor wall | GET /fundraisers/:fundraiserId/donations | None |
| Search everything | GET /search?q=… | None |
| Read organizations | GET /organizations · GET /organizations/:id | None |
| Register an organization | POST /organizations | Session + verified email |
| Run an organization | /org-admin/:slug/* | Organization role |
| Read your own account | GET /me/capabilities · GET /notifications · GET /me/campaign-updates | Session or API key |
| Comment and like | POST /fundraisers/:id/comments · PUT /comments/:commentId/like · PUT /projects/:fundraiserId/updates/:updateId/like | Session (commenting also needs a verified email); details |
| Read your referral portal | GET /me/referrals/* | Ambassador role |
Field names are snake_case; every amount is integer cents
Request and response fields are snake_case (fundraiser_id, client_secret, goal_amount_cents, receipt_id) — a camelCase spelling is not an alias, it is an unrecognised key that validation drops. A handful of endpoints answer in camelCase at the top level (GET /organizations/:id/stats, GET /donor/me/summary); those are noted on their own pages.
Every amount is an integer number of cents, on every endpoint, in requests and responses alike — amount_cents: 5000 is $50.00. Fields carry the unit in their names (amount_cents, tip_amount_cents, goal_amount_cents), so a figure can be copied between endpoints without conversion.
Amounts used to differ by endpoint, with donations and fundraisers in dollars because those columns were NUMERIC(12,2); they are integer cents now and the fields were renamed to say so.
Response envelopes
Most handlers wrap their payload: { "data": … } for a single resource, and { "data": [...], "pagination": { … } } for a list. Not all of them do — GET /fundraisers/check-slug/:slug, GET /organizations/:id/stats, GET /donor/me/summary and DELETE /fundraisers/:id answer unwrapped. The per-resource pages show the exact res.json shape for each endpoint; treat that as the contract rather than assuming the envelope.
Errors are { "error": "…" }, sometimes with message, details (Zod issues) or blockers (publish gates) alongside.
Authentication
Write endpoints require an authenticated session via AWS Cognito. Sessions use httpOnly cookies — include credentials: 'include' in fetch calls, or a cookie jar with cURL. Several write routes additionally require a verified email address.
# Sign in first to set session cookies, then reuse the jar
curl -X POST -c cookies.txt -H "Content-Type: application/json" \
https://api.fundlyhub.org/api/v1/cognito/signin \
-d '{"email":"user@example.com","password":"…"}'
curl -b cookies.txt https://api.fundlyhub.org/api/v1/fundraisersRate limits
Limits are per minute, and RateLimit-* headers are returned on every response. Exceeding one returns 429 with { "error": "Too many requests, please try again later.", "retryAfter": 60 }.
| Bucket | Limit | Applies to |
|---|---|---|
| Global | 100 / min / IP | Everything not covered below |
| Public browsing | 300 / min / IP | Campaign, organization and category listings and reads |
| Authenticated | 100 / min / IP | Money, Stripe Connect, endorsement and like paths |
| Auth | 10 / min / IP | Sign-in, sign-up, password reset, unsubscribe, platform tips |
| Strict | 5 / min / IP | Sensitive public writes, e.g. POST /donations/receipt/email, POST /ambassador-applications |
See Rate Limits for the full policy.
reCAPTCHA
POST /payments/create-intent (action payment) and POST /donations (action donation) verify a reCAPTCHA v3 token. Send it as recaptcha_token in the body or as the x-recaptcha-token header. A missing token is a 400; a token below the score threshold is a 403.
A signed-in session whose email is verified may omit the token on these two routes (native apps cannot produce one). Guests, unverified accounts, API keys and impersonation sessions still need it. A token that is sent is always verified, signed in or not.
The iOS app can instead send an Apple App Attest assertion (X-App-Attest-Key-Id + X-App-Attest-Assertion, signing the raw body). A valid one replaces the token for guests too. See Native clients.
Caching
Read paths are cached briefly — about 30 seconds for listings, 60 seconds for individual records and 5 minutes for profile and permission data. Writes invalidate the entries they affect (a campaign update expires that campaign's entry), so a saved change is visible on the next read rather than at TTL expiry.
Next Steps
- Quick Start — Make your first API call in under 2 minutes
- Native & mobile clients — Sign-in, tokens, the spec files, paging, errors, limits and retries for an app
- Authentication — Cognito sign-in, Google SSO, and session management
- Fundraisers · Donations · Organizations · Categories
- Account (/me) · Notifications & campaign updates · Comments, updates & likes · Drafts before email verification
- Ambassador program · Organization admin
- Payouts & the money lifecycle — How funds move from held → settled → released → paid out
- Reference sections — The generated reference, one section per tag
- Interactive Explorer — Try every endpoint with Swagger UI
- Code Examples — Copy-paste JavaScript and cURL snippets