Skip to content

Organizations API ​

Register organizations, read their public profile, team and updates.

The organization's name field is legal_name

An organization has no name field. The registered name is legal_name; trading names live in the dbas collection (dba_name). The verification state is verification_status — a string, not a verified boolean.

List Organizations ​

GET /api/v1/organizations

Public listing. Only approved and verified, non-soft-deleted organizations are returned — pending, rejected and suspended orgs are never exposed here.

QueryNotes
verification_statusNarrows within the public set: approved or verified. Any other value is ignored rather than rejected, and the default public filter still applies.
countryExact match on the country field.
limitDefault 20, clamped to 1–100.
offsetDefault 0.
javascript
const API_BASE = 'https://api.fundlyhub.org/api/v1';

const response = await fetch(`${API_BASE}/organizations?verification_status=verified&limit=20`);
const { data, pagination } = await response.json();
json
{
  "data": [
    {
      "id": "123e4567-e89b-12d3-a456-426614174000",
      "legal_name": "Local Food Bank Inc.",
      "dba_name": "Local Food Bank",
      "slug": "local-food-bank-a1b2c3",
      "country": "US",
      "address": { "city": "Austin", "region": "TX" },
      "website": "https://localfoodbank.org",
      "logo": "https://example.com/logo.jpg",
      "banner_image": null,
      "description": "Serving families in need since 1985",
      "categories": ["community"],
      "verification_status": "verified",
      "kind": "company",
      "parent_organization_id": null,
      "mission": "End hunger in our community",
      "social_links": {},
      "founded_year": 1985,
      "verification_completed_at": "2026-02-01T00:00:00Z",
      "contact_email": "hello@localfoodbank.org",
      "contact_email_public": true,
      "created_at": "2026-01-15T10:30:00Z",
      "updated_at": "2026-02-01T00:00:00Z"
    }
  ],
  "pagination": { "limit": 20, "offset": 0, "total": 42 }
}

Those fields are the whole public shape; tax ids, review notes and payment account ids are never returned by a public read. There is no fundraiser_count or total_raised on a listing row — those numbers come from GET /organizations/:id/stats.

contact_email is redacted to null unless contact_email_public is true or the caller is an active member of the organization.

Get Organization ​

GET /api/v1/organizations/:id

The path parameter accepts either the UUID or the slug. Anything that is neither is a 404, as is a suspended, rejected or pending organization — for anonymous callers. An authenticated member of the organization can also read it in those states (and additionally receives suspension_reason, rejected_reason and suspended_until, which are null for everyone else).

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

const response = await fetch(`${API_BASE}/organizations/local-food-bank-a1b2c3`);
const { data } = await response.json();

console.log(data.legal_name, data.verification_status);
console.log(data.dbas, data.primary_location, data.children);

The response is { data: { …public fields…, dbas, primary_location, children } }:

  • dbas — [{ id, dba_name, is_default }], default first.
  • primary_location — { id, label, address, is_publicly_visible } or null. The full street is only meant to be rendered when is_publicly_visible is true.
  • children — child organizations, populated only when kind is conglomerate; [] otherwise.

Create Organization ​

POST /api/v1/organizations 🔒 Session + verified email

Self-service registration. Rate-limited to 5 creations per hour per (IP, user). The new row always lands with verification_status: 'pending' — you cannot self-approve — and the registering user is auto-granted the org_owner role scoped to it.

FieldNotes
legal_nameRequired. The only required field.
einMust match 12-3456789 when present.
website, description, countryOptional strings, length-capped.
categoriesArray of non-empty strings.
kindcompany (default) or conglomerate.
dbas[{ dba_name }]. The first entry becomes the default. Duplicate names are a 400.
locations[{ label?, address }] where address is an object of string fields.

parent_organization_id is rejected: linking a new org under an existing conglomerate goes through the org-admin surface, which can authorize against the parent.

javascript
const response = await fetch(`${API_BASE}/organizations`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  credentials: 'include',
  body: JSON.stringify({
    legal_name: 'Local Food Bank Inc.',
    ein: '12-3456789',
    website: 'https://localfoodbank.org',
    description: 'Serving families in need since 1985',
    country: 'US',
    categories: ['community'],
    dbas: [{ dba_name: 'Local Food Bank' }],
    locations: [{ label: 'HQ', address: { line1: '100 Main St', city: 'Austin', region: 'TX' } }]
  })
});

const { data } = await response.json();
console.log('Created, pending verification:', data.id, data.slug);

Returns 201 with { data: { id, legal_name, kind, parent_organization_id, slug, verification_status, created_at, dbas, locations, … } }. The slug is generated server-side from the legal name — you do not supply one. A validation problem is a 400 carrying the specific message; a duplicate is a 409.

Deleting an organization ​

DELETE /organizations/:id no longer exists and answers 404. To close an organization, contact FundlyHub support; deletion is handled by FundlyHub staff, with a grace period during which it can be restored.

Organization statistics ​

GET /api/v1/organizations/:id/stats

Accepts a UUID or a slug. Responds with camelCase fields at the top level — there is no data wrapper:

json
{
  "campaignCount": 4,
  "totalCampaignCount": 7,
  "totalFundsRaisedCents": 45600000,
  "uniqueDonorCount": 312,
  "followerCount": 88
}

campaignCount counts only active campaigns; totalCampaignCount counts every campaign ever attached to the org. totalFundsRaisedCents sums paid donations from donations.amount_cents, so it is integer cents — divide by 100 before you display it. Donor uniqueness falls back to donor_email for guests, so guest gifts count without double-counting.

Team members ​

GET /api/v1/organizations/:id/members

Read-only, public, UUID-or-slug. Returns { data: [...] } of publicly-visible active memberships: user_id, name, profile_slug, avatar, kyc_verified_at, org_role_title_id, role_title_label, role_title_category, role_title_sort_order, start_date, end_date, is_current, affiliation_description, rbac_role_name, rbac_hierarchy_level.

Members whose profile visibility is private, whose membership is hidden or expired, or who hold a plain org_viewer role with no role title are omitted.

There is no POST /organizations/:id/members

Membership is not managed through this endpoint family — adding, changing or removing a member goes through the org-admin surface, which authorizes per-organization. A POST here returns 404.

Updates and documents ​

EndpointPurpose
GET /organizations/{id}/updatesPublic updates feed, newest first, offset-paginated (limit clamped to 1–50).
GET /organizations/{id}/documents/publicDocuments an admin has explicitly flagged public on a verified org.
GET /organizations/meThe organizations the authenticated caller holds an active role on. Flat array, no pagination.
GET /org-role-titlesThe catalogue of public team positions (CEO, Volunteer, …) used on team lists.
POST /organizations/:id/reportReport an organization to the moderators (verified email).

Response Codes ​

  • 200 — Success
  • 201 — Created (pending verification)
  • 400 — Validation error, or no valid fields to update
  • 401 — Authentication required
  • 403 — Permission denied
  • 404 — Not found, or not publicly visible
  • 409 — Organization already exists
  • 429 — Rate limit exceeded (5 registrations/hour)
  • 500 — Server error

Built with VitePress