Skip to content

Categories API ​

Read the fundraising category taxonomy and its per-category statistics. All four endpoints are public and read-only — there is no create, update or delete on this resource.

Category ids are integers

categories.id is an integer, not a UUID. Every path parameter below accepts either that integer id or the category slug: a value made only of digits is looked up by id, anything else by slug.

List Categories ​

GET /api/v1/categories

All active categories, ordered by display_order.

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

const response = await fetch(`${API_BASE}/categories`);
const { data } = await response.json();
json
{
  "data": [
    {
      "id": 3,
      "slug": "medical",
      "name": "Medical",
      "description": "Healthcare and medical emergency campaigns",
      "icon": "local_hospital",
      "color": "#E53935",
      "display_order": 1
    }
  ]
}

Those seven keys are the whole projection. There is no emoji field (the field is icon), and no fundraiser_count or total_raised_cents — counts come from the stats endpoints below, which are separate queries for a reason.

Get a single Category ​

GET /api/v1/categories/:id

By integer id or by slug. Returns { data: { … } } with the same seven fields as the listing, or 404 if the category does not exist or is inactive.

javascript
const response = await fetch(`${API_BASE}/categories/medical`);
const { data } = await response.json();
console.log(data.name, data.icon);

Statistics for one category ​

GET /api/v1/categories/:id/stats

javascript
const response = await fetch(`${API_BASE}/categories/medical/stats`);
const { data } = await response.json();
json
{
  "data": {
    "category_name": "Medical",
    "organization_count": 6,
    "fundraiser_count": 145,
    "total_raised_cents": 284750000
  }
}

fundraiser_count counts active campaigns only, so it matches what a visitor sees in the listing. total_raised_cents sums paid donations from donations.amount_cents, so the figure is integer cents — divide by 100 before you display it.

Statistics for every category ​

GET /api/v1/categories/stats

One row per active category, in display_order. Returns an array under data — not a summary object.

javascript
const response = await fetch(`${API_BASE}/categories/stats`);
const { data } = await response.json();

const topByAmount = [...data]
  .sort((a, b) => b.total_raised_cents - a.total_raised_cents)
  .slice(0, 5);

topByAmount.forEach((cat, i) => {
  console.log(`${i + 1}. ${cat.category_name}: $${(cat.total_raised_cents / 100).toLocaleString()}`);
});
json
{
  "data": [
    {
      "category_name": "Medical",
      "category_slug": "medical",
      "organization_count": 6,
      "campaign_count": 145,
      "total_raised_cents": 284750000
    }
  ]
}

The two stats endpoints do not use the same key

The per-category endpoint returns fundraiser_count; the all-categories endpoint returns campaign_count for the same number, and adds category_slug. Neither returns platform totals such as total_categories or total_fundraisers — sum the array yourself if you need them.

Fundraisers in a category ​

There is no /categories/:slug/fundraisers route. Filter the campaign listing instead — category accepts the integer id, the slug, or the display name, and the resolver also matches the singular form of either:

javascript
const response = await fetch(
  `${API_BASE}/fundraisers?category=medical&status=active&limit=20`
);
const { data, pagination } = await response.json();
console.log(`Found ${pagination.total} medical fundraisers`);

See Fundraisers for the full parameter list and the response shape.

Building a category menu ​

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

const [{ data: categories }, { data: stats }] = await Promise.all([
  fetch(`${API_BASE}/categories`).then(r => r.json()),
  fetch(`${API_BASE}/categories/stats`).then(r => r.json())
]);

const countBySlug = Object.fromEntries(
  stats.map(s => [s.category_slug, s.campaign_count])
);

categories.forEach(cat => {
  console.log(`${cat.name} (${countBySlug[cat.slug] ?? 0})`);
});

Response Codes ​

  • 200 — Success
  • 404 — Category not found or inactive
  • 429 — Rate limit exceeded
  • 500 — Server error

Built with VitePress