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 90 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.

90
Workflows
44
Read-only
37
Write (gated / dry-run)
9
Repo plumbing

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:

  1. Read vs. write credentials. Read-only workflows load reader-scope credentials and cannot mutate anything; write workflows load writer-scope credentials.
  2. Environment approval gates. Write environments (and some sensitive read environments) require a human reviewer to approve the run before it proceeds.
  3. dry_run defaults to preview. Write workflows preview what they would change; going live requires explicitly passing dry_run=false.
  4. Typed confirmation for the highest-stakes actions (for example, domain registration requires retyping the exact domain).
  5. 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.

1xx

Cloudflare / DNS / Domain

22 workflows
#WorkflowAPIsSafetyRuns on
101
Domain - Status (All Sources)
101-domain-status.yml
CFM365
Reads
M365 job reads FFC-tenant status; 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); exo jobs act on FFC tenant — `skip_m365=true` for external-tenant charities
workflow_dispatch
104
Domain - Export Inventory (All Sources)
104-domain-export-inventory.yml
CFM365WHMCSWPMUDEV
Reads
waits on m365-prod and wpmudev-prod approvals (WHMCS job ungated)
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
121
DNS + GH Pages - Fleet Cutover Preflight (FFC-EX)
121-fleet-cutover-preflight.yml
Reads
read-only DoH/HTTPS probes; go/no-go for 120
workflow_dispatch
122
Cloudflare - Zone Member Add (Domain Admin)
122-cloudflare-zone-member-add.yml
CF
Writes (dry-run default)
`dry_run` (default true); preview job always runs read-only; idempotent (existing member = no change)
workflow_dispatch
2xx

WHMCS

27 workflows
#WorkflowAPIsSafetyRuns 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
214
WHMCS - Clients Metrics (aggregate, no PII)
214-whmcs-clients-metrics.yml
WHMCS
Reads
aggregate counts only — no PII in artifact or summary
workflow_dispatch
215
WHMCS - Nonprofit Clients Metrics (service evidence, no PII)
215-whmcs-nonprofit-clients-metrics.yml
WHMCS
Reads
aggregate counts + product names only (no PII)
workflow_dispatch
216
WHMCS - Activity Metrics (full history, no PII)
216-whmcs-activity-metrics.yml
WHMCS
Reads
aggregate counts only (no PII)
workflow_dispatch
217
WHMCS - Client Fields Survey (aggregate, no PII)
217-whmcs-client-fields-survey.yml
WHMCS
Reads
throttled per-client loop; vocabulary-matched values only (no PII)
workflow_dispatch
218
WHMCS - Sites-List Reconciliation + Product Alignment
218-whmcs-siteslist-reconciliation.yml
WHMCS
Reads
aggregate summary; artifact carries org-domain gap lists only
workflow_dispatch
219
WHMCS - Application Detail (Client + Orders, PII-masked)
219-whmcs-application-detail.yml
WHMCS
Reads
single-client projection; personal contact fields always masked
workflow_dispatch
220
WHMCS - Served-Per-Year Metrics (span evidence, no PII)
220-whmcs-served-metrics.yml
WHMCS
Reads
aggregate counts only (no PII)
workflow_dispatch
221
WHMCS - Application Search (find by domain/org)
221-whmcs-application-search.yml
WHMCS
Reads
GetClientsProducts sweep; personal contact fields masked; gated until whmcs-prod-read setup
workflow_dispatch
222
WHMCS - Cloudflare Registrar Product Alignment
222-whmcs-product-alignment.yml
WHMCSCF
Writes (dry-run default)
dry_run default true; $0 AddOrder of pid 39 onto registrar-domain clients; idempotent
workflow_dispatch
223
WHMCS - Import Cloudflare Registrar Domains as Domain Records
223-whmcs-import-cloudflare-domains.yml
WHMCSCF
Writes (dry-run default)
dry_run default true; creates/populates domain records (registrar None) from CF facts
workflow_dispatch
224
WHMCS - GitHub Pages Product Alignment
224-whmcs-github-pages-product-alignment.yml
WHMCSGH
Writes (dry-run default)
dry_run default true; $0 AddOrder of pid 40 onto FFC-EX GitHub-Pages-site clients; idempotent
workflow_dispatch
225
WHMCS - Domain Order URL Verify
225-whmcs-domain-order-url-verify.yml
WHMCS
Reads
report-only; GETs charity-supplied URLs; never accepts/cancels an order
schedule, workflow_dispatch
226
WHMCS - Application Triage (rank + accept onboarding orders)
226-whmcs-application-triage.yml
WHMCS
Writes (report default)
report + reconcile-report read-only; approve accepts ONLY listed $0 orderids; reconcile cancels ONLY listed orderids + emails those clients a re-file email (email-first, no cancel-all)
workflow_dispatch
227
cPanel - Deploy WHMCS Hooks (FTPS/lftp)
227-whmcs-hooks-deploy.yml
cPanel
Writes (verify default)
mode=verify (read-only) vs deploy; hooks input is basename-validated
workflow_dispatch
3xx

Microsoft (M365 / Azure / Graph)

7 workflows
#WorkflowAPIsSafetyRuns on
301
M365 (FFC Tenant) - Domain Preflight (Read-only)
301-m365-domain-preflight.yml
M365CF
Reads
M365 job waits on m365-prod approval
workflow_dispatch
302
M365 (FFC Tenant) - List Tenant Domains
302-m365-list-domains.yml
M365
Reads
waits on m365-prod approval
workflow_dispatch
303
M365 (FFC Tenant) - Domain Status + DKIM (Toolbox)
303-m365-domain-and-dkim.yml
M365
Reads
read-oriented toolbox; waits on m365-prod approval
workflow_dispatch
304
M365 (FFC Tenant) - Enable DKIM (Exchange Online)
304-m365-dkim-enable.yml
M365CF
Writes (gated)
workflow_dispatch
305
M365 (FFC Tenant) - Add Tenant Domain (INTERNAL ONLY)
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 (FFC Tenant M365, PII masked)
306-discover-uncaptured-comms.yml
M365
Reads
PII masked; dispatch-only; org mailboxes only; waits on m365-prod approval
workflow_dispatch
320
Azure - Key Vault Secret Inventory (audit)
320-azure-kv-secret-inventory.yml
MS
Reads
values never printed; placeholder/stale flags only
schedule, workflow_dispatch
4xx

Zeffy

3 workflows
#WorkflowAPIsSafetyRuns 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
5xx

Google

5 workflows
#WorkflowAPIsSafetyRuns 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, github-prod approval); PII-safe aggregates. Same PR also syncs the workflow catalog and the Agentic OS status feed (`agentic-os-status.json`, generated by `scripts/generate-agentic-os-status.py`, REST-only)
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
504
Google - GTM Container Backups (weekly export)
504-google-gtm-backup.yml
GOOGLE
Reads
read-only exports; live-version JSON artifacts (90d)
schedule, workflow_dispatch
505
Google - GA4 Property Provision (per-charity)
505-google-ga-property-provision.yml
GOOGLE
Writes (dry-run default)
dry_run default true; one property per charity; idempotent by stream defaultUri
workflow_dispatch
6xx

WPMUDEV

1 workflows
#WorkflowAPIsSafetyRuns on
601
WPMUDEV - Export Sites/Domains (Read-only)
601-wpmudev-export-sites.yml
WPMUDEV
Reads
waits on wpmudev-prod approval
workflow_dispatch
7xx

GitHub (Website + Repo)

23 workflows
#WorkflowAPIsSafetyRuns 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)
ungated preflight (repo must exist; refuses live sites and sibling-domain repos unless `force=true`); opens a draft PR (never pushes); serialized
workflow_dispatch
703
Sites List - Generate (CSV + JSON)
703-sites-list-generate.yml
GH
plumbingschedule, workflow_dispatch
704
Website - Analytics Wire
704-website-analytics-wire.yml
GH
Writes (dry-run default)
opens a draft PR (never pushes); `dry_run` (default true); idempotent
workflow_dispatch
720
Repo - Create GitHub Repo
720-create-repo.yml
Repo
plumbingworkflow_dispatch
721
Repo - Deploy GitHub Pages
721-deploy-pages.yml
Repo
plumbingpush, workflow_dispatch
722
Repo - CI Validate and Test
722-ci.yml
Repo
plumbingmerge_group, pull_request, push
723
Repo - CodeQL Security Analysis
723-codeql-analysis.yml
Repo
plumbingmerge_group, pull_request, push, schedule, workflow_dispatch
724
Repo - Initialize Labels
724-initialize-labels.yml
Repo
plumbingworkflow_dispatch
725
Repo - Sync Labels
725-sync-labels.yml
Repo
plumbingpush, workflow_dispatch
726
Repo - Rulesets + Settings Drift Audit
726-repo-rulesets-drift-audit.yml
Org
Reads
report only; CBM_TOKEN via github-prod approval
schedule, workflow_dispatch
727
Repo - Phantom Revert Guard
727-phantom-revert-guard.yml
Repo
plumbingmerge_group, pull_request, workflow_dispatch
728
Repo - AI Agent Hooks Validate
728-ai-agent-hooks-validate.yml
Repo
plumbingpull_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
731
Repo - Actions Run Metrics (30d per-workflow stats)
731-actions-run-metrics.yml
GH
Reads
GITHUB_TOKEN read-only; JSON artifact
schedule, workflow_dispatch
732
Repo - Google Workflow Failure Alert (rolling issue)
732-google-workflow-failure-alert.yml
GH
Writes (issues only)
rolling issue upsert/close; no external API
workflow_run
733
Repo - Credential Rotation Reminders (quarterly)
733-credential-rotation-reminders.yml
GH
Writes (issues only)
quarterly reminder issues; rotations stay human/gated
schedule, workflow_dispatch
734
Repo - Stale Waiting-Run Janitor
734-stale-waiting-run-janitor.yml
Repo
Writes (cancels runs)
cancels runs left waiting >N days at a gate; never approves; dispatch dry-run supported
schedule, workflow_dispatch
735
Repo - Dependabot Affected Repos
735-repo-dependabot-affected-repos.yml
Org
Reads
weekly org inventory (feeds smoke-protected waves); PR-only + auto-merge
schedule, workflow_dispatch
736
Repo - Archive / Application Denied (Admin)
736-repo-archive.yml
Repo
Writes (dry-run default)
ungated preflight (live archive requires a matching successful dry-run within 48h; fails fast on missing/already-archived; warns on recent push/Pages/open-issue references); archive-only (reversible, never deletes); `dry_run` default true; typed `confirm_repo`; org-locked + denylist
workflow_dispatch
737
Repo - Claim Sync
737-claim-sync.yml
Repo
Writes (issues/labels only)
syncs `claimed` label from linked PRs (pull_request event, GITHUB_TOKEN) + daily sweep of expired claims (no open linked PR + 48h idle) in this repo (ambient GITHUB_TOKEN — CBM_TOKEN is gated-env-only and empty on schedule); no external API, ungated; sweep `dry_run` via dispatch
pull_request, schedule, workflow_dispatch
738
Repo - Fleet Smoke Engine Drift Audit
738-fleet-smoke-engine-drift-audit.yml
Org
Reads
weekly SHA-256 byte-identity audit of `post-deploy-smoke.yml` across the fleet vs canonical (FFC-IN-FFC_Single_Page_Template@main); rolling issue upsert/close on divergence; GITHUB_TOKEN (public reads + own-repo issue), no external API, ungated
schedule, workflow_dispatch
8xx

Candid (GuideStar)

2 workflows
#WorkflowAPIsSafetyRuns on
801
Candid - Charity Check (EIN)
801-candid-charity-check.yml
CANDID
Reads
read-only IRS-status lookup; public org data only
workflow_dispatch
802
Candid - Essentials Search
802-candid-essentials-search.yml
CANDID
Reads
read-only profile search; public org data only
workflow_dispatch

For AI agents & new admins

A machine-readable version of this catalog is published at https://ffcadmin.org/data/workflow-catalog.json (stable URL; source of truth: docs/workflow-catalog.json in the automation repository, regenerated on every workflow change — CI fails if it drifts). Schema: workflows[] with number, title, apis, file, triggers, environments, safetyLevel, approvalEnv, guard, category. 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