2026-09-10
Keep the catalog in sync
Open-source projects are a subset of the orgs this catalog serves. OSS.Fund is the sibling directory for OSS funding platforms (Open Collective, GitHub Sponsors, Liberapay, and the rest). They do not offer an HTTP API or webhooks. Their public source is Markdown on GitHub (CC BY 4.0): oss-fund/directory.
This site therefore:
-
Lists OSS.Fund itself as a meta-directory.
-
Imports live OSS-friendly / nonprofit-tagged platforms into the Open source category (
pnpm catalog:sync-oss-fund). -
Publishes a pull API so they — or anyone — can consume our catalog.
-
Offers outbound
catalog.updatedwebhooks, plus an inbound GitHub receiver if OSS.Fund (or we) attach a repo webhook later. -
Re-verifies stale hand-authored listings with Gemini (
pnpm catalog:verify/ weekly Verify catalog workflow) and opens a review PR.
Pull the catalog
JSON feed (CORS enabled, 5-minute cache):
GET https://nonprofit-resources.org/api/catalog
Each item includes id, slug, name, href, category, tags, portalUrl, openSource, and verification flags. Filter items where openSource is true for the OSS subset.
Attribution
Imported OSS.Fund rows stay attributed in each listing and on /attributions. If you republish those rows, keep the CC BY 4.0 credit to OSS.Fund.
Outbound webhooks
Staff (signed-in admin email or CATALOG_WEBHOOK_ADMIN_TOKEN) can register a destination:
curl -X POST https://nonprofit-resources.org/api/webhooks \
-H "Authorization: Bearer $CATALOG_WEBHOOK_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.org/hooks/nr","note":"oss.fund mirror"}'
The response includes a secret once. Every later POST is HMAC-SHA256:
-
Header
X-Nonprofit-Resources-Event: catalog.updated -
Header
X-Nonprofit-Resources-Signature: sha256=<hex> -
JSON body with
catalog(URL of/api/catalog) andoccurredAt
Production deploys call /api/webhooks/dispatch with CATALOG_WEBHOOK_DISPATCH_SECRET. Polling /api/catalog is enough if you do not want a push.
Private/loopback URLs are rejected.
Inbound GitHub webhook (OSS.Fund)
If the maintainers of oss-fund/directory add a webhook:
-
Payload URL:
https://nonprofit-resources.org/api/webhooks/github -
Content type:
application/json -
Secret: the same value as
OSS_FUND_GITHUB_WEBHOOK_SECRET -
Events: Pushes
That ping starts GitHub Actions (repository_dispatch: oss-fund-sync), which regenerates src/lib/oss-fund-seed.generated.ts and commits directly to main when the import changed (org policy blocks the default GITHUB_TOKEN from opening PRs). A daily cron does the same when no webhook is attached.
Until they add that hook, we stay in sync by cloning their repo on a schedule.
Hand listings: Gemini verify
Vendor programs and apps in src/lib/services-seed.ts drift (caps, seats, eligibility). The Verify catalog workflow runs weekly (pnpm catalog:verify):
-
Selects hand rows with
lastVerifiedAtolder thanCATALOG_VERIFY_MAX_AGE_DAYS(skipspartner_oss). -
Fetches each
directPortalUrlfor evidence (Google Search only if the fetch fails). -
Asks Gemini (
GEMINI_MODEL, defaultgemini-2.5-flash-lite) for a JSON schema patch with citations. -
Opens a PR via
CATALOG_VERIFY_GITHUB_TOKEN(fine-grained PAT — see docs/ENV-VARIABLES.md). Never auto-merges.
Secrets: GEMINI_API_KEY (Bitwarden item nonprofit-resources, Google project 54406180065) and CATALOG_VERIFY_GITHUB_TOKEN in GitHub Actions.
Sending listings the other way
OSS.Fund accepts new platforms as a pull request against posts/ in oss-fund/directory. A listing for Nonprofit Resources belongs there as a directory, not a funding platform, and should wait on their editorial review. Prefer that they poll /api/catalog if they want the full feed.
Related
-
Browse locally:
/services?oss=1 -
Scripts:
pnpm catalog:sync-oss-fund,pnpm catalog:verify -
Env names: docs/ENV-VARIABLES.md
-
OSS.Fund about page: oss.fund/about