How FFC Automation Works
Free For Charity runs its infrastructure — Cloudflare DNS, WHMCS billing, Microsoft 365, Zeffy donations, Google Analytics, and the GitHub sites themselves — through 60 auditable GitHub Actions workflows. This page is the living catalog: what each workflow does, how they are numbered and named, and the safety model that protects every run. It is generated from the workflows themselves, so it cannot drift.
The numbering convention — and why
Every workflow has a 3-digit number whose first digit is the system the workflow targets. A new admin (or an AI agent) can tell from the number alone which external API a workflow drives, and new workflows can never collide — each category has its own numbering space.
Naming: workflows are named NNN. Target - Description [TAG] and their files are NNN-slug.yml. The [TAG] lists every API the workflow calls, joined with + (for example [CF+M365]).
What counts as “an API”: the API the workflow actually calls — not the service the records are for (creating Microsoft 365 DNS records via the Cloudflare API is [CF]), and not plumbing (fetching credentials from Azure Key Vault, or posting a result comment to GitHub, never counts).
Multi-system workflows keep one number from the system that owns the deliverable (writes beat reads). They exist as single workflows only when they are aggregators (a report that joins several sources) or transactions (two writes that must succeed together to avoid a half-configured state).
The safety model
A live change generally has to get past several independent layers, not just one:
- Read vs. write credentials. Read-only workflows load reader-scope credentials and cannot mutate anything; write workflows load writer-scope credentials.
- Environment approval gates. Write environments (and some sensitive read environments) require a human reviewer to approve the run before it proceeds.
dry_rundefaults to preview. Write workflows preview what they would change; going live requires explicitly passingdry_run=false.- Typed confirmation for the highest-stakes actions (for example, domain registration requires retyping the exact domain).
- No stored secrets. Credentials are fetched from Azure Key Vault at run time via OIDC and masked line-by-line; nothing sensitive lives in the repository.
The complete catalog
Generated from the workflow files in FreeForCharity/FFC-Cloudflare-Automation. Safety levels: Reads = no external mutation; Writes (dry-run default) = mutates only with dry_run=false; Writes (gated) = mutates when run, protected by an approval gate.
Cloudflare / DNS / Domain
20 workflows| # | Workflow | APIs | Safety | Runs on |
|---|---|---|---|---|
| 101 | Domain - Status (All Sources) 101-domain-status.yml | CFM365 | Reads M365 job waits on m365-prod approval | workflow_dispatch |
| 102 | Domain - Add to FFC Cloudflare + WHMCS Nameservers (Admin) 102-domain-add-ffc-cloudflare-and-whmcs.yml | CFWHMCS | Writes (gated) — | workflow_dispatch |
| 103 | Domain - Enforce Standard (GitHub Apex + M365) 103-enforce-domain-standard.yml | CFM365 | Writes (dry-run default) `dry_run` (default true) | workflow_dispatch |
| 104 | Domain - Export Inventory (All Sources) 104-domain-export-inventory.yml | CFM365WHMCSWPMUDEV | Reads waits on whmcs-prod, m365-prod, and wpmudev-prod approvals | workflow_dispatch |
| 105 | DNS - Manage Record (Manual / Issue Label) 105-manage-record.yml | CF | Writes (dry-run default) `dry_run` (default true); issue-label trig | issues, workflow_dispatch |
| 106 | DNS - Enforce Standard (DNS-only) 106-enforce-standard.yml | CF | Writes (dry-run default) `dry_run` (default true) | workflow_dispatch |
| 107 | DNS - Audit Compliance (Report) 107-audit-compliance.yml | CF | Reads — | workflow_dispatch |
| 108 | DNS - Export Cloudflare Zones (Report) 108-export-summary.yml | CF | Reads — | workflow_dispatch |
| 109 | DNS - Export All Records (Full, per-record) 109-dns-export-all-records.yml | CF | Reads — | workflow_dispatch |
| 110 | DNS - Create Zone (Admin) 110-cloudflare-zone-create.yml | CF | Writes (gated) — | workflow_dispatch |
| 111 | DNS - Create Redirect Rule (Admin) 111-dns-create-redirect-rule.yml | CF | Writes (dry-run default) `dry_run` (default true) | workflow_dispatch |
| 112 | DNS - Bulk Replace A-record IP (All Zones) 112-dns-bulk-replace-a-ip.yml | CF | Writes (gated) high blast radius; serialized | workflow_dispatch |
| 113 | Domain - Registrar Search / Check / Register (Admin, DRAFT) 113-cloudflare-domain-register.yml | CF | Writes (gated) `mode` (default `check`); register needs `mode=execute-register` + `confirm_domain` | issues, workflow_dispatch |
| 114 | Domain - Validate Cloudflare Registrar API Access (Read-only) 114-cloudflare-registrar-access-check.yml | CF | Reads never charges | workflow_dispatch |
| 115 | Domain - Transfer Readiness Preflight (Report) 115-domain-transfer-preflight.yml | WHMCS | Reads — | workflow_dispatch |
| 116 | Domain - Transfer EPP/Auth Code Probe (Admin) 116-domain-transfer-epp-probe.yml | WHMCS | Writes (dry-run default) `dry-run` vs `execute` | workflow_dispatch |
| 117 | Domain - Post-Transfer Verification (Report) 117-domain-transfer-verify.yml | CF | Reads — | workflow_dispatch |
| 118 | Domain - Registrar Lock / Unlock 118-whmcs-domain-lock.yml | WHMCS | Writes (dry-run default) `dry_run` (default true) | workflow_dispatch |
| 119 | DNS - Bulk Staging CNAME -> GitHub Pages (FFC-EX) 119-bulk-staging-cname-github-pages.yml | CF | Writes (dry-run default) `dry_run` (default true); serialized | workflow_dispatch |
| 120 | DNS + GH Pages - Bulk Cutover staging -> Apex (FFC-EX) 120-bulk-cutover-to-github-pages.yml | CFGH | Writes (dry-run default) `dry_run` (default true); serialized | workflow_dispatch |
WHMCS
13 workflows| # | Workflow | APIs | Safety | Runs on |
|---|---|---|---|---|
| 201 | WHMCS - Export Domains (Report) 201-whmcs-export-domains.yml | WHMCS | Reads gated only by the env approval | workflow_dispatch |
| 202 | WHMCS - Export Products (Report) 202-whmcs-export-products.yml | WHMCS | Reads gated only by the env approval | workflow_dispatch |
| 203 | WHMCS - Export Payment Methods (Research) 203-whmcs-export-payment-methods.yml | WHMCS | Reads gated only by the env approval | workflow_dispatch |
| 204 | WHMCS - Charity Onboard (client + contacts + order) 204-whmcs-charity-onboard.yml | WHMCS | Writes (dry-run default) `dry_run` (default true); idempotent | workflow_dispatch |
| 205 | WHMCS - Open Ticket (manual) 205-whmcs-ticket-open.yml | WHMCS | Writes (gated) one-way GitHub→WHMCS | workflow_dispatch |
| 206 | WHMCS - Issue to Ticket (one-way) 206-whmcs-issue-to-ticket.yml | WHMCS | Writes (gated) one-way GitHub→WHMCS | issues |
| 207 | WHMCS - Ticket Respond (templated) 207-whmcs-ticket-respond.yml | WHMCS | Writes (dry-run default) `dry_run` (default true); live reply needs admin username | workflow_dispatch |
| 208 | WHMCS - Export Tickets (Report) 208-whmcs-tickets-export.yml | WHMCS | Reads — | workflow_dispatch |
| 209 | WHMCS - Tickets Triage (Open/Customer-Reply) 209-whmcs-tickets-triage.yml | WHMCS | Reads summary masks PII | schedule, workflow_dispatch |
| 210 | WHMCS - Orders Triage (Pending/Fraud/Active) 210-whmcs-orders-triage.yml | WHMCS | Reads summary masks PII | schedule, workflow_dispatch |
| 211 | WHMCS - Order Update (accept/cancel/fraud) 211-whmcs-order-update.yml | WHMCS | Writes (dry-run default) `dry_run` (default true); one order at a time | workflow_dispatch |
| 212 | WHMCS - Product Add (catalog) 212-whmcs-product-add.yml | WHMCS | Writes (dry-run default) `dry_run` (default true); idempotent | workflow_dispatch |
| 213 | WHMCS -> Zeffy Payments Import (Draft) 213-whmcs-zeffy-payments-import-draft.yml | WHMCS | Reads (builds a file) output is a draft; serialized | workflow_dispatch |
Microsoft (M365 / Azure / Graph)
6 workflows| # | Workflow | APIs | Safety | Runs on |
|---|---|---|---|---|
| 301 | M365 - Domain Preflight (Read-only) 301-m365-domain-preflight.yml | M365CF | Reads M365 job waits on m365-prod approval | workflow_dispatch |
| 302 | M365 - List Tenant Domains 302-m365-list-domains.yml | M365 | Reads waits on m365-prod approval | workflow_dispatch |
| 303 | M365 - Domain Status + DKIM (Toolbox) 303-m365-domain-and-dkim.yml | M365 | Reads read-oriented toolbox; waits on m365-prod approval | workflow_dispatch |
| 304 | M365 - Enable DKIM (Exchange Online) 304-m365-dkim-enable.yml | M365CF | Writes (gated) — | workflow_dispatch |
| 305 | M365 - Add Tenant Domain (Admin) 305-m365-add-tenant-domain.yml | M365 | Writes (dry-run default) `dry_run` (default true); also gated by m365-prod approval | workflow_dispatch |
| 306 | Discover - Uncaptured Comms (M365, PII masked) 306-discover-uncaptured-comms.yml | M365 | Reads PII masked; dispatch-only; org mailboxes only; waits on m365-prod approval | workflow_dispatch |
Zeffy
3 workflows| # | Workflow | APIs | Safety | Runs on |
|---|---|---|---|---|
| 401 | Zeffy - Campaigns Export 401-zeffy-campaigns-export.yml | ZEFFY | Reads PII masked; never `-IncludePii` | workflow_dispatch |
| 402 | Zeffy - Payments Export (PII masked) 402-zeffy-payments-export.yml | ZEFFY | Reads PII masked; never `-IncludePii` | workflow_dispatch |
| 403 | Zeffy - Contacts Export (PII masked) 403-zeffy-contacts-export.yml | ZEFFY | Reads PII masked; never `-IncludePii` | workflow_dispatch |
| # | Workflow | APIs | Safety | Runs on |
|---|---|---|---|---|
| 501 | Google - API Smoke (GA4 connectivity) 501-google-api-smoke.yml | GOOGLE | Reads read-only; fails closed; reusable via `workflow_call` | workflow_call, workflow_dispatch |
| 502 | Google - Analytics Report (GA4 -> JSON) 502-google-analytics-report.yml | GOOGLE | Reads delivers JSON to ffcadmin via PR (CBM_TOKEN); PII-safe aggregates | schedule, workflow_dispatch |
| 503 | Google - GTM Provision (per-charity container) 503-google-gtm-provision.yml | GOOGLE | Writes (dry-run default) dry_run default true; seeds GA4/Clarity/Meta; delegates POC access | workflow_dispatch |
WPMUDEV
1 workflows| # | Workflow | APIs | Safety | Runs on |
|---|---|---|---|---|
| 601 | WPMUDEV - Export Sites/Domains (Read-only) 601-wpmudev-export-sites.yml | WPMUDEV | Reads waits on wpmudev-prod approval | workflow_dispatch |
GitHub (Website + Repo)
14 workflows| # | Workflow | APIs | Safety | Runs on |
|---|---|---|---|---|
| 701 | Website - Provision (Issue Assigned) 701-website-provision.yml | CFRepo | Writes (gated) `repo` chained behind `dns` approval | issues |
| 702 | Domain - Deploy Static Clone to FFC-EX Repo 702-ffc-ex-clone-deploy.yml | Writes (gated) opens a draft PR (never pushes); serialized | workflow_dispatch | |
| 703 | Sites List - Generate (CSV + JSON) 703-sites-list-generate.yml | GH | plumbing | schedule, workflow_dispatch |
| 720 | Repo - Create GitHub Repo 720-create-repo.yml | Repo | plumbing | workflow_dispatch |
| 721 | Repo - Deploy GitHub Pages 721-deploy-pages.yml | Repo | plumbing | push, workflow_dispatch |
| 722 | Repo - CI Validate and Test 722-ci.yml | Repo | plumbing | merge_group, pull_request, push |
| 723 | Repo - CodeQL Security Analysis 723-codeql-analysis.yml | Repo | plumbing | merge_group, pull_request, push, schedule, workflow_dispatch |
| 724 | Repo - Initialize Labels 724-initialize-labels.yml | Repo | plumbing | workflow_dispatch |
| 725 | Repo - Sync Labels 725-sync-labels.yml | Repo | plumbing | push, workflow_dispatch |
| 726 | Repo - Rulesets + Settings Drift Audit 726-repo-rulesets-drift-audit.yml | Org | Reads report only | schedule, workflow_dispatch |
| 727 | Repo - Phantom Revert Guard 727-phantom-revert-guard.yml | Repo | plumbing | merge_group, pull_request, workflow_dispatch |
| 728 | Repo - AI Agent Hooks Validate 728-ai-agent-hooks-validate.yml | Repo | plumbing | pull_request, push |
| 729 | Repo - Add Collaborator 729-repo-add-collaborator.yml | Repo | Writes (**live default**) ⚠️ `dry_run` defaults to **false** | workflow_dispatch |
| 730 | Repo - Audit Environment Approval Gates 730-repo-audit-environment-gates.yml | Repo | Reads report only (environment reviewer config) | push, workflow_dispatch |
For AI agents & new admins
A machine-readable version of this catalog lives at docs/workflow-catalog.json in the automation repository and is regenerated on every workflow change (CI fails if it drifts). To pick a workflow: match the first digit to the system you need to act on, prefer Reads before Writes, and always run write workflows with the default dry_run preview first.
Related: Technology Stack · Sites List