Skip to content

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 ​

CapabilityEndpointAuth
List campaignsGET /fundraisersNone
Read one campaignGET /fundraisers/:id · GET /fundraisers/slug/:slugNone (owner-only for unapproved or private ones)
Create a campaignPOST /fundraisersSession + verified email (drafts excepted)
Take a paymentPOST /payments/create-intent → POST /payments/confirmOptional session; reCAPTCHA enforced
Record a donationPOST /donationsSession + verified email
Read the donor wallGET /fundraisers/:fundraiserId/donationsNone
Search everythingGET /search?q=…None
Read organizationsGET /organizations · GET /organizations/:idNone
Register an organizationPOST /organizationsSession + verified email
Run an organization/org-admin/:slug/*Organization role
Read your own accountGET /me/capabilities · GET /notifications · GET /me/campaign-updatesSession or API key
Comment and likePOST /fundraisers/:id/comments · PUT /comments/:commentId/like · PUT /projects/:fundraiserId/updates/:updateId/likeSession (commenting also needs a verified email); details
Read your referral portalGET /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.

bash
# 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/fundraisers

Rate 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 }.

BucketLimitApplies to
Global100 / min / IPEverything not covered below
Public browsing300 / min / IPCampaign, organization and category listings and reads
Authenticated100 / min / IPMoney, Stripe Connect, endorsement and like paths
Auth10 / min / IPSign-in, sign-up, password reset, unsubscribe, platform tips
Strict5 / min / IPSensitive 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 ​

Built with VitePress