{"openapi":"3.0.3","info":{"title":"SEEADS Foundation API","version":"1.1.0","description":"Public REST API for the SEEADS Foundation platform.\n\n**Read endpoints** (campaigns, nonprofits, sponsors, stats) are open and emit `Cache-Control: public, max-age=60`.\n\n**Write endpoints** require an API key. Send it as `Authorization: Bearer <api_key>`. Generate keys at /api-keys (sign in required). Keys inherit your account's roles:\n- `nonprofit` role — create campaigns for nonprofits you own\n- Campaign owner / `admin` — update or delete a campaign","contact":{"name":"SEEADS Foundation","url":"https://seeadsfoundation.org"},"license":{"name":"CC BY 4.0","url":"https://creativecommons.org/licenses/by/4.0/"}},"servers":[{"url":"https://seeadsfoundation.org/api/public/v1","description":"Production"}],"tags":[{"name":"System"},{"name":"Campaigns"},{"name":"Nonprofits"},{"name":"Sponsors"},{"name":"Donations","description":"Donation read endpoints. These are served via the platform's typed RPC layer (`createServerFn`) rather than the public `/api/public/v1` REST surface, but the response shapes documented here are the stable contract — frontend teams can rely on the `Donation` schema fields (including `future_fund`, `base_amount`, `total_amount`, and `monthly_total`) being present on every donation response."}],"paths":{"/health":{"get":{"tags":["System"],"summary":"Health check","responses":{"200":{"description":"OK"}}}},"/stats":{"get":{"tags":["System"],"summary":"Aggregate platform donation totals","responses":{"200":{"description":"OK"}}}},"/campaigns":{"get":{"tags":["Campaigns"],"summary":"List active campaigns","parameters":[{"name":"state","in":"query","schema":{"type":"string"}},{"name":"school","in":"query","schema":{"type":"string"}},{"name":"q","in":"query","description":"Search by campaign name","schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","default":20,"minimum":1,"maximum":100}},{"name":"offset","in":"query","schema":{"type":"integer","default":0,"minimum":0}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Campaign"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}}}},"post":{"tags":["Campaigns"],"summary":"Create a campaign (nonprofit role required; must own the nonprofit)","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignWrite"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Campaign"}}}},"400":{"description":"Validation failed"},"401":{"description":"Missing or invalid API key"},"403":{"description":"Forbidden — requires nonprofit role and ownership"}}}},"/campaigns/{id}":{"get":{"tags":["Campaigns"],"summary":"Get a single campaign","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Campaign"}}}},"404":{"description":"Not found"}}},"patch":{"tags":["Campaigns"],"summary":"Update a campaign (campaign owner or admin)","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"Any subset of CampaignWrite fields, plus 'active' (boolean)."}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Campaign"}}}},"401":{"description":"Missing or invalid API key"},"403":{"description":"Forbidden — must own the campaign"}}},"delete":{"tags":["Campaigns"],"summary":"Delete a campaign (campaign owner or admin)","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Deleted"},"401":{"description":"Missing or invalid API key"},"403":{"description":"Forbidden — must own the campaign"}}}},"/nonprofits":{"get":{"tags":["Nonprofits"],"summary":"List approved nonprofits","parameters":[{"name":"q","in":"query","schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","default":20,"minimum":1,"maximum":100}},{"name":"offset","in":"query","schema":{"type":"integer","default":0,"minimum":0}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Nonprofit"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}}}}},"/nonprofits/{id}":{"get":{"tags":["Nonprofits"],"summary":"Get a nonprofit and its active campaigns","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Nonprofit"}}}},"404":{"description":"Not found"}}}},"/sponsors":{"get":{"tags":["Sponsors"],"summary":"List approved corporate sponsors","parameters":[{"name":"category","in":"query","schema":{"type":"string"}},{"name":"tier","in":"query","schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","default":20,"minimum":1,"maximum":100}},{"name":"offset","in":"query","schema":{"type":"integer","default":0,"minimum":0}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Sponsor"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}}}}},"/sponsor-categories":{"get":{"tags":["Sponsors"],"summary":"List active sponsor categories","responses":{"200":{"description":"OK"}}}},"/donations/receipt":{"get":{"tags":["Donations"],"summary":"Fetch a donation receipt by Stripe Checkout session id","description":"Returns the full receipt payload for the donation tied to a Stripe Checkout session. Public — no auth required (the session id acts as the capability token). The `donation` field follows the shared `Donation` schema and always includes the Future Fund breakdown (`future_fund`, `future_fund_amount`, `base_amount`, `total_amount`, `monthly_total`).","parameters":[{"name":"sessionId","in":"query","required":true,"schema":{"type":"string","maxLength":255}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"donation":{"allOf":[{"$ref":"#/components/schemas/Donation"}],"nullable":true},"campaign":{"type":"object","nullable":true,"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string","nullable":true},"school":{"type":"string","nullable":true},"nonprofit_id":{"type":"string","format":"uuid","nullable":true}}},"nonprofit":{"type":"object","description":"Always present (id/name may be null when no nonprofit is linked).","properties":{"id":{"type":"string","format":"uuid","nullable":true},"name":{"type":"string","nullable":true}}},"pledge":{"type":"object","nullable":true,"properties":{"id":{"type":"string","format":"uuid"},"match_type":{"type":"string"},"status":{"type":"string"},"funded_amount":{"type":"number","nullable":true},"funded_at":{"type":"string","format":"date-time","nullable":true},"contact_name":{"type":"string","nullable":true},"contact_email":{"type":"string"}}}}},"example":{"donation":{"id":"8f2a1b9e-4c3d-4e7f-9a1b-2c3d4e5f6a7b","amount":55,"currency":"usd","status":"succeeded","is_recurring":false,"recurrence_interval":null,"future_fund":true,"future_fund_amount":5,"base_amount":50,"total_amount":55,"monthly_total":null,"created_at":"2025-05-01T14:22:31.000Z","campaign_id":"11111111-2222-3333-4444-555555555555","pledge_id":null},"campaign":{"id":"11111111-2222-3333-4444-555555555555","name":"Library Books for Pine Ridge","school":"Pine Ridge Elementary","nonprofit_id":"22222222-3333-4444-5555-666666666666"},"nonprofit":{"id":"22222222-3333-4444-5555-666666666666","name":"SEEADS Foundation"},"pledge":null}}}}}}},"/donations/{id}":{"get":{"tags":["Donations"],"summary":"Get a single donation","description":"Returns one donation. Donor-scoped (donors can read their own gifts) or admin-scoped. Response always includes the Future Fund breakdown via the shared `Donation` schema.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Donation"},"examples":{"oneTime":{"summary":"One-time gift with Future Fund","value":{"id":"8f2a1b9e-4c3d-4e7f-9a1b-2c3d4e5f6a7b","amount":55,"currency":"usd","status":"succeeded","is_recurring":false,"recurrence_interval":null,"future_fund":true,"future_fund_amount":5,"base_amount":50,"total_amount":55,"monthly_total":null,"created_at":"2025-05-01T14:22:31.000Z","campaign_id":"11111111-2222-3333-4444-555555555555","pledge_id":null}},"recurring":{"summary":"Monthly recurring gift","value":{"id":"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d","amount":30,"currency":"usd","status":"active_recurring","is_recurring":true,"recurrence_interval":"month","future_fund":false,"future_fund_amount":0,"base_amount":30,"total_amount":30,"monthly_total":30,"created_at":"2025-04-15T09:10:00.000Z","campaign_id":"11111111-2222-3333-4444-555555555555","pledge_id":null}}}}}},"401":{"description":"Missing or invalid API key"},"403":{"description":"Forbidden — donation belongs to another donor"},"404":{"description":"Not found"}}}},"/donors/me/donations":{"get":{"tags":["Donations"],"summary":"List the authenticated donor's donations","description":"Returns donations belonging to the authenticated donor, newest first. Each item is a `Donation` with the full Future Fund breakdown — use `monthly_total` to render per-month amounts for recurring gifts.","security":[{"bearerAuth":[]}],"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","default":20,"minimum":1,"maximum":100}},{"name":"offset","in":"query","schema":{"type":"integer","default":0,"minimum":0}},{"name":"recurring","in":"query","description":"If true, only return active recurring donations.","schema":{"type":"boolean"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Donation"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}},"example":{"data":[{"id":"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d","amount":30,"currency":"usd","status":"active_recurring","is_recurring":true,"recurrence_interval":"month","future_fund":false,"future_fund_amount":0,"base_amount":30,"total_amount":30,"monthly_total":30,"created_at":"2025-04-15T09:10:00.000Z","campaign_id":"11111111-2222-3333-4444-555555555555","pledge_id":null},{"id":"8f2a1b9e-4c3d-4e7f-9a1b-2c3d4e5f6a7b","amount":55,"currency":"usd","status":"succeeded","is_recurring":false,"recurrence_interval":null,"future_fund":true,"future_fund_amount":5,"base_amount":50,"total_amount":55,"monthly_total":null,"created_at":"2025-05-01T14:22:31.000Z","campaign_id":"11111111-2222-3333-4444-555555555555","pledge_id":null}],"pagination":{"limit":20,"offset":0,"total":2}}}}},"401":{"description":"Missing or invalid API key"}}}},"/admin/donations":{"get":{"tags":["Donations"],"summary":"Admin — list recent donations (most recent 500)","description":"Admin-only listing of recent donations with embedded campaign info. Each row extends the base donation columns with the same Future Fund breakdown fields exposed by the donor receipt API: `future_fund_amount`, `base_amount`, `total_amount`, and `monthly_total`. Use `total_amount` for the headline charge, `base_amount` + `future_fund_amount` for the line-item breakdown, and `monthly_total` (non-null only when `is_recurring`) for per-month display.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"allOf":[{"$ref":"#/components/schemas/Donation"},{"type":"object","properties":{"donor_name":{"type":"string","nullable":true},"donor_email":{"type":"string","nullable":true},"donor_id":{"type":"string","format":"uuid","nullable":true},"campaigns":{"type":"object","nullable":true,"description":"Embedded campaign summary (joined via campaign_id).","properties":{"name":{"type":"string","nullable":true},"school":{"type":"string","nullable":true}}}}}]}},"example":[{"id":"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d","amount":30,"currency":"usd","status":"active_recurring","is_recurring":true,"recurrence_interval":"month","future_fund":false,"future_fund_amount":0,"base_amount":30,"total_amount":30,"monthly_total":30,"created_at":"2025-04-15T09:10:00.000Z","campaign_id":"11111111-2222-3333-4444-555555555555","pledge_id":null,"donor_name":"Jordan Rivera","donor_email":"jordan@example.com","donor_id":"9a8b7c6d-5e4f-4a3b-2c1d-0e9f8a7b6c5d","campaigns":{"name":"Library Books for Pine Ridge","school":"Pine Ridge Elementary"}},{"id":"8f2a1b9e-4c3d-4e7f-9a1b-2c3d4e5f6a7b","amount":55,"currency":"usd","status":"succeeded","is_recurring":false,"recurrence_interval":null,"future_fund":true,"future_fund_amount":5,"base_amount":50,"total_amount":55,"monthly_total":null,"created_at":"2025-05-01T14:22:31.000Z","campaign_id":"11111111-2222-3333-4444-555555555555","pledge_id":null,"donor_name":"Alex Chen","donor_email":"alex@example.com","donor_id":"1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d","campaigns":{"name":"Library Books for Pine Ridge","school":"Pine Ridge Elementary"}}]}}},"401":{"description":"Missing or invalid API key"},"403":{"description":"Forbidden — requires admin role"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Personal API key. Create at /api-keys."}},"schemas":{"Pagination":{"type":"object","properties":{"limit":{"type":"integer"},"offset":{"type":"integer"},"total":{"type":"integer"}}},"Campaign":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"goal_amount":{"type":"number"},"raised_amount":{"type":"number"},"start_date":{"type":"string","format":"date","nullable":true},"end_date":{"type":"string","format":"date","nullable":true},"state":{"type":"string","nullable":true},"county":{"type":"string","nullable":true},"district":{"type":"string","nullable":true},"school":{"type":"string","nullable":true},"image_url":{"type":"string","nullable":true},"active":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"CampaignWrite":{"type":"object","required":["nonprofit_id","name","goal_amount"],"properties":{"nonprofit_id":{"type":"string","format":"uuid"},"name":{"type":"string","minLength":3,"maxLength":200},"description":{"type":"string","maxLength":5000,"nullable":true},"goal_amount":{"type":"number","minimum":0,"maximum":10000000},"start_date":{"type":"string","format":"date","nullable":true},"end_date":{"type":"string","format":"date","nullable":true},"state":{"type":"string","maxLength":64,"nullable":true},"county":{"type":"string","maxLength":120,"nullable":true},"district":{"type":"string","maxLength":200,"nullable":true},"school":{"type":"string","maxLength":200,"nullable":true},"image_url":{"type":"string","format":"uri","maxLength":2048,"nullable":true}}},"Nonprofit":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"logo_url":{"type":"string","nullable":true},"website":{"type":"string","nullable":true},"created_at":{"type":"string","format":"date-time"}}},"Sponsor":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"company_name":{"type":"string"},"blurb":{"type":"string","nullable":true},"category":{"type":"string","nullable":true},"tier":{"type":"string"},"logo_url":{"type":"string","nullable":true},"website":{"type":"string","nullable":true},"created_at":{"type":"string","format":"date-time"}}},"Donation":{"type":"object","description":"Donation record returned by receipt and donor-facing endpoints. `amount` is the total charged (gift + Future Fund when applicable). Use `base_amount` / `future_fund_amount` to render a line-item breakdown, and `monthly_total` for recurring per-month display.","properties":{"id":{"type":"string","format":"uuid"},"amount":{"type":"number","description":"Total charged amount (alias of `total_amount`). For recurring donations this is the per-interval charge."},"currency":{"type":"string","example":"usd"},"status":{"type":"string","description":"pending | succeeded | completed | paid | active_recurring | failed | canceled"},"is_recurring":{"type":"boolean"},"recurrence_interval":{"type":"string","nullable":true,"example":"month"},"future_fund":{"type":"boolean","description":"True when the donor designated this gift to the Future Fund (adds a fixed contribution to `amount`)."},"future_fund_amount":{"type":"number","description":"Fixed Future Fund contribution included in `amount`. 0 when `future_fund` is false; otherwise 5 (USD)."},"base_amount":{"type":"number","description":"Gift portion of the donation: `total_amount - future_fund_amount`."},"total_amount":{"type":"number","description":"Full charge amount (alias of `amount`). Always present for clarity in client breakdowns."},"monthly_total":{"type":"number","nullable":true,"description":"Per-month total for recurring donations (equals `total_amount` when `is_recurring`); null for one-time gifts."},"created_at":{"type":"string","format":"date-time"},"campaign_id":{"type":"string","format":"uuid","nullable":true},"pledge_id":{"type":"string","format":"uuid","nullable":true}}}}}}