Skip to content

Organization Admin (/org-admin) ​

The /org-admin/:slug/* surface is how an organization's own team runs it: members, settings and branding, DBAs and locations, verification documents, child companies, Stripe payouts, updates, and read-only views of fundraisers, donations and donors. It is the API behind fundlyhub.org/org-admin/<slug>.

It is the organization's own surface. Verification, suspension and other lifecycle decisions are made by FundlyHub and are not part of this API.

Reference sectionOperationsAbout
Organization Admin34The org-scoped admin panel at `/org-admin/{slug}/*`. Every operation needs a bearer session, resolves `{slug}` to an organization (an unknown or malformed slug answers `404 Organization not found`, never `403`), and then checks an **org-scoped permission** held through the caller's `org_owner`, `org_admin` or `org_viewer` role on that organization. A caller without the permission gets `403` with `message: "Requires permission: <name>"`.

How access works ​

Every route needs a signed-in caller and an org-scoped permission. The API:

  1. Resolves :slug to an organization. A malformed or unknown slug is 404 Organization not found — never 403, so the endpoint does not confirm that a slug exists.
  2. Checks the permission in the organization scope: your role assignments on that organization.

A missing permission is 403 with message: "Requires permission: org.…". To find out up front what a user may do, call GET /me/capabilities?scopeType=organization&scopeId=<org-uuid>.

Organization roles ​

Permissionorg_viewerorg_adminorg_owner
org.read, org.read_donations, org.read_donors, org.read_payouts✓✓✓
org.update_settings, org.manage_members, org.manage_dbas, org.manage_locations, org.upload_documents, org.export_csv✓✓
org.manage_payouts (connect Stripe)✓
org.manage_children (create child companies)✓

The registering user becomes org_owner automatically.

Endpoints ​

Overview and reads ​

EndpointPermissionNotes
GET /org-admin/:slug/overvieworg.readKPIs. For a conglomerate the totals roll up its child companies.
GET /org-admin/:slug/fundraisersorg.read?status=, ?limit=, ?offset=.
GET /org-admin/:slug/donationsorg.read_donations?status=, ?fundraiser_id=, paging.
GET /org-admin/:slug/donorsorg.read_donorsDistinct donors with totals.

Each donor row has an opaque donor_key: user:<profile id> for a signed-in donor, guest:<24 hex characters> for a guest, or anonymous for the single row that collects every anonymous gift. It is stable across pages, so use it to tell rows apart; it carries no contact details.

Members ​

EndpointPermissionBody
GET /org-admin/:slug/membersorg.manage_members—
GET /org-admin/:slug/users/search?q=org.manage_membersConfirm the account behind a complete email address before adding it.
POST /org-admin/:slug/membersorg.manage_members{ email, role, org_role_title_id?, custom_role_title? }
PATCH /org-admin/:slug/members/:userIdorg.manage_members{ role }
DELETE /org-admin/:slug/members/:userIdorg.manage_members—
  • Only an approved or verified organization can look up or add members. Otherwise both users/search and POST …/members answer 403 with code: "ORG_NOT_APPROVED".
  • users/search takes a complete email address in q, matched case-insensitively against account login emails. It answers { "results": [ … ] } with at most one entry, { id, type: "user", name, avatar, masked_email }, where masked_email looks like s***@example.org. Anything that is not a full address returns an empty list; this is not a directory search.
  • Lookups and adds share a limit of 30 requests per minute per user; over it is 429.
  • The person must already have a FundlyHub account: an unknown email is 404 "No FundlyHub user with that email. Ask them to sign up first." There is no email invitation for people without an account. An existing member is 409.
  • A new member's role is org_admin or org_viewer. org_owner is reached only by promotion with PATCH, and only an owner can promote to owner.
  • Roles rank org_owner > org_admin > org_viewer. Only an owner can change or remove another owner. Nobody can change or remove a member whose role is equal to or above their own, or grant a role above their own. Each of these is 403. Stepping down or leaving yourself is always allowed.
  • The only owner cannot be demoted or removed (400) — promote someone else first.
  • custom_role_title (up to 100 characters) or org_role_title_id is the public position shown on the team list; GET /org-role-titles lists the catalogue.

Settings, branding and updates ​

EndpointPermissionNotes
PATCH /org-admin/:slug/settingsorg.update_settingslegal_name, website, description, country, logo, banner_image, mission, contact_email, contact_email_public, founded_year, social_links. Fields you omit are left alone.
POST / DELETE /org-admin/:slug/avatarorg.update_settingsOrganization logo (base64 JSON, like the user avatar).
POST / DELETE /org-admin/:slug/bannerorg.update_settingsBanner image.
POST /org-admin/:slug/updatesorg.update_settings{ title, body, cover_image? } — a post on the public profile's "Latest news".
PATCH / DELETE /org-admin/:slug/updates/:updateIdorg.update_settingsEdit or delete a post.

The public profile is read through /organizations/*, a different surface from these writes. FundlyHub's web client expires its cached /organizations/* reads whenever an /org-admin/* write succeeds; a client of your own that caches the public profile should do the same, or a saved change keeps showing the old copy until its cache expires.

DBAs and locations ​

EndpointPermissionBody
GET /org-admin/:slug/dbasorg.read—
POST /org-admin/:slug/dbas · PATCH …/:dbaId · DELETE …/:dbaIdorg.manage_dbas{ dba_name, is_default? }
GET /org-admin/:slug/locationsorg.read—
POST /org-admin/:slug/locations · PATCH …/:locationId · DELETE …/:locationIdorg.manage_locations{ label?, address, is_primary?, is_publicly_visible? }

The default DBA is the organization's public display name. Only the primary location is shown on the public profile.

Verification documents ​

EndpointPermissionNotes
GET /org-admin/:slug/documentsorg.readEvery document and its review status (financial documents need more; see below).
POST /org-admin/:slug/documentsorg.upload_documents{ doc_type, content_type, original_filename?, file_base64 }
GET /org-admin/:slug/documents/:docId/downloadorg.read—
DELETE /org-admin/:slug/documents/:docIdorg.upload_documents—
PATCH /org-admin/:slug/documents/:docId/public-visibilityorg.upload_documents{ is_publicly_visible } — show an approved document on the public profile.

doc_type is one of ein_letter, 501c3_determination, w9, voided_check, other; content_type is application/pdf, image/png or image/jpeg; the decoded file is at most 10 MB. Uploading a new document of a type supersedes the previous one. An approved ein_letter or501c3_determination is what verification requires. FundlyHub reviews each uploaded document.

Financial documents — w9, voided_check and other — are visible only to callers who also hold org.upload_documents (org_admin and org_owner). For anyone else, such as an org_viewer, they are left out of the list, and downloading one answers 404. ein_letter and 501c3_determination need org.read only.

Child companies (conglomerates) ​

EndpointPermissionNotes
GET /org-admin/:slug/childrenorg.read—
POST /org-admin/:slug/childrenorg.manage_children + verified emailRegisters a company under this conglomerate. The parent must be kind: conglomerate; the hierarchy is two levels deep.

Payouts ​

EndpointPermissionNotes
POST /org-admin/:slug/payouts/connectorg.manage_payoutsCreates the organization's Stripe Connect account if needed and returns 201 { data: { accountId, onboardingUrl, status } }; send the user to onboardingUrl. The organization must be verified (400 otherwise).
GET /org-admin/:slug/payouts/statusorg.read_payoutsCharges/payouts enabled, country, currency, and what Stripe still needs.

Donations to the organization's fundraisers pay out to this account, separately from any member's personal payouts. See Payouts.

Example ​

bash
# What may I do in this organization?
curl -b cookies.txt \
  "https://api.fundlyhub.org/api/v1/me/capabilities?scopeType=organization&scopeId=$ORG_ID"

# Add a teammate who already has an account
curl -X POST -b cookies.txt -H 'Content-Type: application/json' \
  https://api.fundlyhub.org/api/v1/org-admin/local-food-bank-a1b2c3/members \
  -d '{ "email": "sam@example.com", "role": "org_viewer", "custom_role_title": "Volunteer coordinator" }'

Auditing ​

Built with VitePress