HOA & CDFI Verticals
One guide, two adjacent verticals: HOA dues reporting for property managers, and the CDFI Nonprofit furnishing tier for 501(c)(3) lenders, CDFIs, and tribal-lending programs. They share infrastructure (industry templates, validation profiles) but serve very different audiences.
Overview
Metro2 ships two new verticals on a single platform release: HOA dues reporting for community-association management companies, and the CDFI Nonprofit furnishing tier for community development financial institutions, 501(c)(3) lenders, and tribal-lending programs. We bundled them in one guide because they share the same plumbing under the hood — the industry-templates system, validation profiles, and per-company industry_slug gating — even though the audiences and workflows look nothing alike.
Read whichever half applies to you:
- HOA section (below) — property managers, HOA boards, condo associations, CINC / Vantaca / FrontSteps / AppFolio / Buildium operators.
- CDFI section (further down) — community development financial institutions, nonprofit CDFI loan funds, credit-builder programs, tribal-lending programs, and 501(c)(3) consumer lenders.
Both verticals are opt-in per company — you select one via the industry_slug field on your company profile (hoa-management or cdfi-nonprofit). If your company has neither slug set, none of the gates or extra workflows described here apply — your existing pipeline is unchanged.
Three principles run through the implementation:
- Shared infrastructure, distinct surfaces. The industry-templates engine, validation-profile system, and the
generate-fileendpoint know about both verticals, but each gets its own marketing page, help guide, training content, and API namespace. - Non-breaking by default. All gates are scoped to the relevant
industry_slug. A non-HOA company never sees the disclosure-verify gate; a non-CDFI company never sees the nonprofit-verification flow. Existing customers get zero behavior change. - Backend-first. Phase A is API + content. The dashboard UI components (AssociationManager, DisclosureVerifyDialog, HomeownerConsentImporter, training viewer) are scaffolded but deferred to Phase C. The APIs are fully usable today via
curlor your own UI.
HOA: dues reporting overview
The HOA vertical lets a property-management company (or, less commonly, an HOA board running its own books) furnish monthly dues assessments to the credit bureaus as a tradeline. The idea is the same as rent-reporting: paying your HOA dues on time becomes a positive item on the homeowner's credit file, and the association gets a non-confrontational lever for delinquency collection.
Account Type 89 (default)
We default HOA dues to Account Type 89 — Rental Agreement. This is the closest CDIA-defined account type to a recurring residential payment obligation that isn't a mortgage. It is a deliberate, documented choice — recorded in DECISIONS.md — pending CDIA confirmation of a dedicated HOA account type.
Account Type 89 is enforced as a soft warning in the validator, not a hard error. If your bureau contact tells you they're accepting a different code (some accept Type 47 — Credit Line, others have informal guidance to use Type 22 — Secured Loan), you can override per-association without us pushing back at file-generation time. The warning persists in the validation response so the choice stays visible to your QA workflow.
Portfolio Type O
HOA tradelines use Portfolio Type O — Open Account. This pairs naturally with Account Type 89: dues are an ongoing, recurring obligation with no fixed payoff date, which is exactly what Portfolio Type O describes. Bureaus that bucket files by portfolio type will route the file to the same lane they use for other open-account furnishers (utilities, telecoms, rent reporters).
Homeowner opt-in
HOA reporting is opt-in by homeowner, not opt-out. A homeowner must have a signed consent record in hoa_homeowner_consents before any record for their address is emitted. The disclosure-verify gate (covered below) operates at the association level — it asks "does the association as a whole permit credit reporting" — but the consent table operates at the homeowner level. You need both.
Positive-only is typical (but not enforced)
Most HOAs that report start out positive-only — they report the current-status homeowners and quietly omit the delinquent ones. This is by far the gentlest legal posture for the early months of a program, and it's the default we recommend in the marketing page. Metro2 does not enforce positive-only at the file level — if your board has explicitly authorized adverse reporting and you have the per-homeowner consent and the state notice periods to back it up, you can report delinquency status codes the same way any other furnisher would.
HOA: state-by-state compliance
Every U.S. state has its own statute (or patchwork of statutes) governing what an HOA or condo association is allowed to do when a homeowner falls behind on dues. We seed 10 high-density HOA states with detailed rules and leave the remaining 41 jurisdictions (40 states + DC) as "contact us" placeholders pending paralegal review.
The 10 detailed states
These are the states with the largest HOA footprint by housing-unit count, and where we have direct statutory citations and recent (2024–2026) legislative updates in the seed. The rule summaries live in the hoa_state_compliance database table and are mirrored statically in src/content/metro2/hoa-state-compliance.ts so marketing pages render without a Supabase round-trip.
| State | CC&R disclosure required | Homeowner consent required | Notice period | Key statute |
|---|---|---|---|---|
| California (CA) | Yes | Yes | 30 days | Civil Code 4950–5450 (Davis-Stirling), SB 410 / SB 625 |
| New York (NY) | Yes | Yes | 30 days | Real Property Law §339, NPCL §602 |
| Florida (FL) | Yes | No | 45 days | Chapter 720 (HOA Act), Chapter 718 (Condo Act) |
| Texas (TX) | Yes | No | 30 days | Property Code Chapter 209 |
| Arizona (AZ) | Yes | No | 30 days | A.R.S. §33-1801, §33-1241 |
| Nevada (NV) | Yes | Yes | 60 days | NRS Chapter 116 (Common-Interest Ownership Act) |
| Colorado (CO) | Yes | Yes | 30 days | CCIOA C.R.S. §38-33.3-101, HB 22-1137 |
| Washington (WA) | Yes | No | 30 days | RCW Chapter 64.34 / 64.38 |
| Georgia (GA) | Yes | No | 30 days | O.C.G.A. §44-3-220 (POAA) |
| North Carolina (NC) | Yes | Yes | 30 days | NCGS Chapter 47F, 47C |
The full rule summary, citation URL, and last-reviewed date for each state is available on metro2.switchlabs.dev/metro2/hoa-compliance. That page is the authoritative customer-facing surface — keep it bookmarked.
The other 41 jurisdictions
Every other state and DC has a placeholder row in the compliance table. The placeholder defaults to the most conservative posture (CC&R disclosure required + homeowner consent required) so customers in those states don't accidentally under-comply while we're still finalizing the legal review. The rule_summary field on those rows says "Contact us — state-specific HOA disclosure requirements have not been verified in this jurisdiction yet. Email support@metro2.switchlabs.dev for a compliance check."
We're working through these in order of customer demand. If you're in a placeholder state and ready to launch, email support and we'll prioritize the paralegal review for that jurisdiction.
What the is_seeded flag means
Each row carries an is_seeded boolean:
is_seeded: true— the rule summary is paralegal-reviewed and current. Safe to surface in customer-facing UI as the authoritative answer.is_seeded: false— this is a placeholder row. The defaults (consent required, disclosure required) are conservative but unverified. UI should render this as a "contact us" card rather than a definitive ruling.
HOA: the CC&R disclosure-verify gate
Before any HOA file gets emitted, every association referenced by records in the file must have an explicit boolean attestation on record that the association's CC&Rs (Covenants, Conditions & Restrictions) permit credit reporting. We call this the disclosure-verify gate, and it's wired directly into the generate-file endpoint.
Why this gate exists
The legal foundation for HOA credit reporting is the association's governing documents. If the CC&Rs were recorded in 1997 and don't mention credit reporting, the board can't just start reporting tradelines next month — they need a CC&R amendment (or, in some states, a board resolution backed by recorded notice). Forgetting this step is the #1 way an HOA program gets a homeowner-side FCRA complaint, and the #1 way a property-management company ends up named in that complaint.
The gate makes the attestation explicit and impossible to forget: you can't generate a file without it, and once you check the box, we record when it was checked, who checked it, and a free-text note (typically a reference to the recorded amendment or board resolution).
Exact gate semantics
From src/lib/hoa-gate/index.ts, the rules are:
- Scoped to HOA companies only. The gate only fires when the calling company's
industry_slug = 'hoa-management'. For every other vertical it's a no-op — non-HOA companies see zero behavioral change. - Per-association check. If the company is
hoa-managementand has any associations registered, every association referenced by records in the current file generation must have a non-nullccr_disclosure_verified_attimestamp AND must havereporting_paused = false. - Soft-pass for HOA companies with no associations yet. If the company has the HOA slug but hasn't populated
hoa_associationsat all, generation is allowed with a warning. This is the "you're evaluating the vertical" mode. - Unassigned records are a warning, not an error. Records with no
metadata.hoa_association_idare emitted unchanged in v1 with a count surfaced in the warnings array. This lets customers adopt the workflow incrementally.
What you see when the gate fails
If any association referenced by the file is missing disclosure verification, the /api/metro2-records/generate-file endpoint returns HTTP 422 Unprocessable Entity with a JSON body like:
{
"ok": false,
"error": "HOA disclosure gate failed: one or more associations are not cleared for reporting. Association \"Maplewood Heights HOA\" (a1b2c3d4-...) is missing CC&R disclosure verification. POST /api/hoa/associations/a1b2c3d4-.../disclosure-verify with a board-resolution note to clear.",
"blocked_association_ids": ["a1b2c3d4-...", "e5f6g7h8-..."],
"warnings": []
}The blocked_association_ids array makes it cheap to build a UI that shows the customer exactly which associations are blocking the file and offers a one-click clear-the-gate flow.
Clearing the gate
To clear the gate for an association, POST to /api/hoa/associations/{id}/disclosure-verify with a board-resolution note in the body:
curl -X POST https://metro2.switchlabs.dev/api/hoa/associations/a1b2c3d4-.../disclosure-verify \
-H "Authorization: Bearer <API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"note": "CC&R Amendment #2025-04 recorded with Maricopa County on 2025-08-12, Doc #20250812-0094127. Board resolution attached.",
"verified_by_user_id": "..."
}'The endpoint sets ccr_disclosure_verified_at = now() on the row and records the note in ccr_disclosure_verification_note. That's it — the next generate-file call sees the timestamp and lets the file through.
If reporting needs to be paused later (homeowner complaint, board decision, legal hold), set reporting_paused = true on the association via the regular PATCH endpoint and the gate will block again.
Failure modes that are not blocked
The gate is deliberately permissive about a few things so existing customers can adopt the vertical incrementally:
- Read error on the company row — if Supabase returns an error trying to read
companies.industry_slug, the gate returnsok: truewith a warning and lets the file through. We do not hard-fail on infrastructure errors. - Read error on the associations table — same posture: warn, don't block.
- HOA company with zero associations — generation is allowed (this is the "previewing the vertical" case).
- Records with no
hoa_association_id— emitted unchanged with a warning count.
HOA: data model
hoa_associations
One row per HOA managed by your company. In v1 this table is metadata-only — there are no sub-tenant users attached to an association. The property-management company has full access to all of its associations through its own admin users; we don't (yet) support sub-accounts for individual board members.
| Column | Type | Notes |
|---|---|---|
id | uuid (pk) | Server-generated. |
company_id | uuid (fk → companies) | Owning property-management company. Required. |
name | text | Display name (e.g. "Maplewood Heights HOA"). |
state_code | text (2 chars) | For routing to the right state-compliance row. |
ccr_disclosure_verified_at | timestamptz | Null until the disclosure-verify gate is cleared. Set by POST .../disclosure-verify. |
ccr_disclosure_verification_note | text | Free-text note: recorded amendment ID, resolution date, etc. |
reporting_paused | boolean | If true, gate blocks even with a valid timestamp. |
account_type_override | text (2 chars) | Optional override of the default Account Type 89. |
created_at / updated_at | timestamptz | Audit fields. |
hoa_homeowner_consents
One row per homeowner who has consented to having their dues tradeline reported. Supports bulk import — most management companies onboard hundreds or thousands of homeowner consents collected via the annual meeting / portal sign-up flow.
| Column | Type | Notes |
|---|---|---|
id | uuid (pk) | Server-generated. |
company_id | uuid (fk) | Owning company. Required. |
association_id | uuid (fk → hoa_associations) | The HOA the homeowner belongs to. |
homeowner_id | text | Your internal homeowner ID — primary join key for CSV imports. |
address | text | The property address (single-line or multi-line, both accepted). |
signed_at | timestamptz | When the homeowner signed the consent form. |
signature_capture_url | text | URL to the signed PDF / image (DocuSign envelope, S3 link, etc.). |
revoked_at | timestamptz | Null = active. Non-null = consent withdrawn; record is excluded from future files. |
revocation_reason | text | Optional free-text reason. |
created_at / updated_at | timestamptz | Audit fields. |
hoa_state_compliance
Reference table — one row per state + DC. The runtime source of truth for the /api/hoa/state-compliance endpoint and the seed mirror in src/content/metro2/hoa-state-compliance.ts for SEO surfaces. The 10 seeded states have detailed rule_summary + citation_url; the other 41 are placeholders with is_seeded = false.
The 4 new columns on companies
Both verticals share four new columns on the existing companies table:
industry_slug— controls which vertical's gates and templates apply. Valid values includehoa-management,cdfi-nonprofit, and the existing slugs for other verticals.cdfi_grant_funded— boolean stub for the future grant-funded billing tier. Visible in admin, no billing logic wired up in v1 (see CDFI section below).- Two adjacent metadata columns (
industry_metadataJSONB and a regulator-tier hint) used by the industry-templates engine to render the right marketing copy and validation profile for each vertical.
HOA: bulk consent import
The most common onboarding pattern for an HOA program is to collect homeowner consents at the annual meeting (or via the portal launch email) and then bulk-import them in one shot before flipping on reporting. The /api/hoa/homeowner-consents/bulk-import endpoint accepts a CSV upload and creates one consent row per data line.
CSV schema
Four columns, in order:
homeowner_id— your internal ID for the homeowner. This is the join key — if the same(association_id, homeowner_id)pair already exists, the import updates the existing row instead of creating a duplicate.address— single-line or multi-line address. We don't parse it; we store it verbatim and use it for UI display.signed_at— ISO 8601 timestamp (2026-04-15T10:30:00-07:00) or date-only (2026-04-15, parsed at noon UTC).signature_capture_url— URL to the signed evidence. Typically a DocuSign envelope ID, an S3 link, or a Drive link. Required even if it's a placeholder — we want every consent row to point to something auditable.
Example CSV
homeowner_id,address,signed_at,signature_capture_url
H-00012,"1234 Maple Way, Phoenix, AZ 85031",2026-03-12,https://docs.example.com/consents/H-00012.pdf
H-00013,"1236 Maple Way, Phoenix, AZ 85031",2026-03-12,https://docs.example.com/consents/H-00013.pdf
H-00014,"1238 Maple Way, Phoenix, AZ 85031",2026-03-14,https://docs.example.com/consents/H-00014.pdf
H-00015,"1240 Maple Way, Phoenix, AZ 85031",2026-03-14,https://docs.example.com/consents/H-00015.pdf
H-00016,"1242 Maple Way, Phoenix, AZ 85031",2026-03-15,https://docs.example.com/consents/H-00016.pdfWhat the import does
- Parses the CSV with PapaParse (auto-detects delimiter — comma, semicolon, tab, pipe).
- Validates each row:
homeowner_idnon-empty,addressnon-empty,signed_atparseable,signature_capture_urla valid URL. - Upserts on
(company_id, association_id, homeowner_id)— re-running the same CSV is idempotent. - Returns a JSON summary:
created,updated,skipped,errors[]with row numbers and reasons.
Example curl
curl -X POST https://metro2.switchlabs.dev/api/hoa/homeowner-consents/bulk-import \
-H "Authorization: Bearer <API_KEY>" \
-F "association_id=a1b2c3d4-..." \
-F "file=@consents.csv"Revoking a consent
Homeowners can revoke consent at any time. Set revoked_at on the row via PATCH and the homeowner's records will be excluded from future files automatically. Already-emitted tradelines stay on the bureau side until the next monthly file (use the consumer portal's deletion flow if you need to accelerate that).
HOA: marketing page
The vertical's sales pitch lives at metro2.switchlabs.dev/metro2/industry/hoa-dues. It covers the value proposition for property managers, screenshots of the workflow, a state-coverage map, and the opt-in homeowner positioning. Send prospects here rather than improvising the pitch in email — it's the authoritative customer-facing surface and we keep it current as the vertical evolves.
The page also doubles as the SEO landing for "HOA credit reporting" and adjacent queries. If you want to refer a prospect with a UTM-tagged link for attribution, use:
https://metro2.switchlabs.dev/metro2/industry/hoa-dues?utm_source=referral&utm_medium=email&utm_campaign=<your_name>HOA: state compliance reference
The full state matrix is at metro2.switchlabs.dev/metro2/hoa-compliance. Every row is rendered with:
- State name and 2-letter code
- CC&R disclosure required (yes / no)
- Homeowner consent required (yes / no)
- Notice period in days (or "not specified")
- Full statutory citation + URL (where seeded)
- Rule summary
- A "contact us" CTA on placeholder rows (the 41 non-seeded jurisdictions)
The runtime data comes from the hoa_state_compliance table via the /api/hoa/state-compliance endpoint; the static mirror in src/content/metro2/hoa-state-compliance.ts is used for ISR-friendly rendering and preview deploys where the migration may not be applied yet.
If you spot a stale rule (a new statute was passed, a court ruling shifted the standard) — email support@metro2.switchlabs.dev with the citation and we'll get it routed to the paralegal review queue.
HOA: PMS integration stubs
We've scaffolded UI for five property-management-software (PMS) integrations. These are stubs in v1 — the auth fields and connect buttons render, but the actual data sync is gated behind our partner program. Each integration is currently marked "coming soon" with a notify-me CTA.
| PMS | Auth fields surfaced | Status |
|---|---|---|
| CINC Systems | API key, Tenant ID | Partner program — coming soon |
| Vantaca | OAuth client ID + secret, account subdomain | Partner program — coming soon |
| FrontSteps | API key, community ID | Partner program — coming soon |
| AppFolio HOA | OAuth, property-manager subdomain | Partner program — coming soon |
| Buildium HOA | API key, association ID | Partner program — coming soon |
The reason these are partner-gated rather than just "we'll build them" is that each PMS vendor controls who gets API access, and most of them require a formal partnership agreement before they'll issue production credentials. If you're actively using one of these and want us to prioritize the integration, click the notify-me on the integration card or email support — customer demand is what drives the partner-program conversation.
In the meantime, the bulk-import CSV flow (covered above) works with exports from any of these PMSes — you just have to map their column names to ours during the export step.
HOA: API reference
All HOA endpoints are namespaced under /api/hoa/. Auth is the same as the rest of Metro2 — API key in the Authorization header, or session cookie if you're calling from a browser context.
Associations — /api/hoa/associations
GET /api/hoa/associations— list all associations for the calling company.POST /api/hoa/associations— create a new association.GET /api/hoa/associations/{id}— fetch one by ID.PATCH /api/hoa/associations/{id}— update (rename, change state, setreporting_paused, setaccount_type_override).DELETE /api/hoa/associations/{id}— soft-delete.POST /api/hoa/associations/{id}/disclosure-verify— clear the CC&R disclosure-verify gate. Body:{ "note": "...", "verified_by_user_id": "..." }.
Homeowner consents — /api/hoa/homeowner-consents
GET /api/hoa/homeowner-consents?association_id={id}— list consents, filterable by association.POST /api/hoa/homeowner-consents— create a single consent row.GET /api/hoa/homeowner-consents/{id}— fetch one.PATCH /api/hoa/homeowner-consents/{id}— update (revoke by settingrevoked_at+ arevocation_reason).DELETE /api/hoa/homeowner-consents/{id}— hard-delete (use only for data-entry errors; for genuine revocations, use the PATCH flow so the audit trail is preserved).POST /api/hoa/homeowner-consents/bulk-import— CSV upload (multipart form). See the CSV schema above.
State compliance — /api/hoa/state-compliance
GET /api/hoa/state-compliance— return all rows (51 total). The static mirror insrc/content/metro2/hoa-state-compliance.tsis the same data; the API endpoint is for cases where you need the latest seeded review (the static mirror updates with each deploy).GET /api/hoa/state-compliance/{state_code}— fetch one state by 2-letter code (e.g./api/hoa/state-compliance/CA).
Example: end-to-end with curl
# 1. Create the association.
curl -X POST https://metro2.switchlabs.dev/api/hoa/associations \
-H "Authorization: Bearer $METRO2_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Maplewood Heights HOA",
"state_code": "AZ"
}'
# Response: { "id": "a1b2c3d4-...", ... }
# 2. Clear the disclosure-verify gate.
curl -X POST https://metro2.switchlabs.dev/api/hoa/associations/a1b2c3d4-.../disclosure-verify \
-H "Authorization: Bearer $METRO2_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"note": "CC&R Amendment #2025-04, Doc #20250812-0094127. Recorded with Maricopa County 2025-08-12."
}'
# 3. Bulk-import homeowner consents.
curl -X POST https://metro2.switchlabs.dev/api/hoa/homeowner-consents/bulk-import \
-H "Authorization: Bearer $METRO2_API_KEY" \
-F "association_id=a1b2c3d4-..." \
-F "file=@consents.csv"
# 4. Generate the file (this is where the gate fires).
curl -X POST https://metro2.switchlabs.dev/api/metro2-records/generate-file \
-H "Authorization: Bearer $METRO2_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "environment": "live" }'HOA: common questions
Why Account Type 89 (Rental Agreement) by default?
Per DECISIONS.md, Account Type 89 is the closest CDIA-defined account type to a recurring residential payment obligation that isn't a mortgage. It maps cleanly to the legal posture of HOA dues (recurring, non-secured, ongoing) and aligns with how rent reporters are treated by the bureaus. We treat it as the default pending CDIA confirmation of a dedicated HOA account type; if and when that confirmation lands, we'll migrate existing furnishers via a one-time per-association flip.
The default is a soft warning, not a hard error — if your bureau contact has approved a different code for your program, you can override per-association via the account_type_override column without us blocking the file.
What if my state isn't in the 10 detailed?
Use the state compliance page to find your state — every jurisdiction has at least a placeholder row. The placeholder defaults to the most conservative posture (CC&R disclosure required + homeowner consent required), so following it puts you in a safe legal position while we finish the paralegal review.
Email support@metro2.switchlabs.dev to bump your jurisdiction up the review queue. Customer demand is how we prioritize.
Can homeowners opt out?
Yes — two paths:
- Via the homeowner consents endpoint. PATCH the consent row with
revoked_at+ arevocation_reason. The homeowner will be excluded from the next monthly file. - Via the consumer portal. Homeowners can revoke consent themselves through the consumer portal — see the consumer portal guide for the homeowner-facing flow. Revocations flow back into
hoa_homeowner_consentswith the samerevoked_atmechanism.
For already-emitted tradelines, the revocation only affects future files — if you need to actively delete an already-furnished tradeline from the bureau side, use the consumer portal's deletion request flow.
Do I need to register every association before I can report anything?
No — the gate is permissive about HOA companies with zero associations registered (it returns a warning, not an error). This is the "evaluating the vertical" mode. But you do need to register an association before you can run a file that references that association via metadata.hoa_association_id — referencing a non-existent association is a hard block.
Can one company manage multiple HOAs?
Yes — that's the standard property-management case. Each HOA is a row in hoa_associations, all owned by the same company_id. The disclosure-verify gate fires per association: you can have HOA A cleared and HOA B blocked, and the file generator will only emit records for HOA A's homeowners (records for HOA B's homeowners will be cited in the 422 response).
CDFI: Nonprofit furnishing tier overview
The CDFI Nonprofit tier is designed for 501(c)(3) lenders, certified CDFIs, community development credit unions, tribal-lending programs, and nonprofit credit-builder programs. It bundles two verification flows (IRS Tax Exempt Org Search + CDFI Fund certified-list sync), a training-modules library, and a concessionary pricing posture that positions Metro2 as a viable alternative — or, more commonly, a complement — to CBA Reporter.
Who this is for
- U.S. Treasury-certified CDFIs — loan funds, credit unions, banks, and venture funds that show up on the CDFI Fund's certified list.
- 501(c)(3) consumer lenders — nonprofit credit-builder loan funds, community-based microlenders, faith-based loan programs.
- Tribal-lending programs — both standalone tribal entities and CDFI-certified Native CDFIs.
- Hybrid programs — university revolving loan funds, hospital community-benefit lending programs, etc.
Positioning vs CBA Reporter
The big existing player in this space is the Credit Builders Alliance (CBA) Reporter, which runs roughly $2,895–$3,895 per year depending on your member tier. CBA Reporter is purpose-built for nonprofit lenders, has deep training resources, and is the de facto standard.
We're positioned as a complement, not a replacement. Per DECISIONS.md, most CDFIs using Metro2 will keep their CBA membership for the training and peer-network access, and use Metro2 for the actual file generation, validation, and bureau submission workflow — where our pipeline is faster, more programmable, and integrates directly with their loan-origination system. Some smaller CDFIs will use Metro2 standalone; that's fine, but it's not our pitch.
Pricing for the CDFI tier is concessionary — billing is handled via Wix Plans (the same billing system the rest of Metro2 uses) with a dedicated CDFI tier set up at significantly less than our standard SaaS pricing. The exact dollar figure isn't baked into the codebase — it's managed in the Wix plan configuration so finance can adjust it without a deploy.
CDFI: verifying nonprofit status
To enroll in the CDFI Nonprofit tier, a company has to prove it's actually a nonprofit lender. Two verification paths are supported, and most customers will use both:
IRS Tax Exempt Org Search
The /api/nonprofit-verification route is a proxy in front of the IRS Tax Exempt Organization Search API. You POST an EIN and the route returns the IRS's record: legal name, status, exemption type, NTEE code, and effective date.
curl -X POST https://metro2.switchlabs.dev/api/nonprofit-verification \
-H "Authorization: Bearer $METRO2_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "ein": "13-1234567" }'
# Response (live mode):
# {
# "ok": true,
# "ein": "13-1234567",
# "legal_name": "Example Community Lending Fund Inc.",
# "status": "Active",
# "exemption_type": "501(c)(3)",
# "ntee_code": "S20",
# "effective_date": "1998-04-01",
# "source": "irs-tax-exempt-org-search"
# }The route is env-gated: if the IRS API credentials aren't configured for the current environment (the case in preview deploys and CI), the route returns a stub response with a source: "stub" field so your client can detect it. This means your integration code can be written once and work in both live and stub mode without branching.
The IRS Tax Exempt API can be slow (multi-second tail latencies are common) and has rate limits — don't put it on a hot path. We cache verified results for 90 days; re-verifications within that window return cached data without hitting the IRS.
CDFI Fund certified-list sync
The CDFI Fund (within U.S. Treasury) publishes a certified-list of every entity that holds active CDFI certification. We sync this list nightly via the /api/cron/cdfi-sync route, which is wired to a Vercel cron schedule.
Once synced, the certified list is queryable via /api/certification-lookup:
curl https://metro2.switchlabs.dev/api/certification-lookup?ein=13-1234567 \
-H "Authorization: Bearer $METRO2_API_KEY"
# Response:
# {
# "ok": true,
# "found": true,
# "ein": "13-1234567",
# "name": "Example Community Lending Fund Inc.",
# "cert_id": "CDFI-12345",
# "cert_type": "Loan Fund",
# "cert_date": "2014-06-12",
# "state": "NY",
# "last_synced_at": "2026-05-18T03:00:00Z"
# }Like the IRS endpoint, the CDFI Fund sync is env-gated. If the download URL isn't configured for the current environment, the cron route logs a no-op and the lookup endpoint falls back to a stub response with source: "stub". Production runs the full sync nightly; preview deploys use the stub.
Why two paths? Some lenders are nonprofit but not CDFI-certified (faith-based microlenders, university revolving loan funds, recently founded 501(c)(3)s that haven't applied for CDFI yet). Some are CDFI-certified but not 501(c)(3) (CDFI-certified for-profit loan funds with a CDFI mission). The two-path verification covers both cases.
CDFI: grant-funded flag
The companies.cdfi_grant_funded column is a boolean stub. It exists so we can flag grant-funded CDFIs in the admin UI today and have the data ready when the grant-tier billing logic ships in a future phase, but it currently does not drive any billing or feature gating — it's strictly visible-in-admin metadata.
What it's for, eventually
Many CDFIs receive grant funding from the CDFI Fund's financial assistance (FA) or technical assistance (TA) programs, from state-level CDFI programs, or from private grantmakers (Wells Fargo NEXT Awards, JPMorgan PRO Neighborhoods, etc.). Several of these grants specifically subsidize tech-stack spending for the grantee, which means a grant-funded CDFI may qualify for a different pricing tier entirely (often free or heavily discounted, billed back to the granting institution rather than the CDFI).
When the grant-tier billing ships, we'll add eligibility rules, grant-institution attribution, and the billing-bypass logic. Until then, the flag just makes the population visible to our team so we can have one-off conversations with grant-funded customers about pricing.
Setting the flag
Admin-only. Toggle via the company-edit form in the admin panel, or PATCH directly:
curl -X PATCH https://metro2.switchlabs.dev/api/admin/companies/<id> \
-H "Authorization: Bearer $METRO2_ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{ "cdfi_grant_funded": true }'Self-serve is not exposed — we want a brief conversation with the customer before flipping the flag.
CDFI: training modules
The training library at metro2.switchlabs.dev/metro2/cdfi-training is modeled after the CBA Training Institute's curriculum structure. Eight modules cover the full lifecycle of nonprofit credit reporting, from "what is Metro 2" through dispute escalation. The intent is to make CDFI Metro2 self-serviceable for staff who don't come from a banking-tech background.
| # | Module | Audience |
|---|---|---|
| 1 | Metro 2 basics — file format, RDW, base segment, trailers | Everyone new to credit reporting |
| 2 | Furnisher onboarding — getting an Equifax / Experian / TransUnion / Innovis subscriber code | Compliance lead |
| 3 | CDFI-specific account types — installment, secured, credit-builder, microloan handling | Loan ops |
| 4 | Status codes & the Payment History Profile | Loan ops |
| 5 | Compliance Condition Codes (CCC) — DA, dispute, bankruptcy, deceased | Compliance lead |
| 6 | Dispute handling — receiving a CDV/ACDV, the 30-day response clock | Compliance lead, customer-facing staff |
| 7 | Dispute escalation — when to escalate, working with the bureau's e-OSCAR process | Compliance lead |
| 8 | Member-protection rules — FCRA accuracy, ECOA, SCRA, the CDFI lender's heightened duty of care | Compliance lead, board reporting |
Each module has a written explainer, a short knowledge-check quiz, and a downloadable summary PDF. Completion is tracked per user so program managers can see who's through which module.
The training-viewer UI shipped in Phase B; new modules are added quarterly. Customers can't author their own modules in v1 — let us know if that's a feature you'd use and we'll wire it in.
CDFI: marketing page
The CDFI Nonprofit pitch page is at metro2.switchlabs.dev/metro2/industry/cdfi-nonprofit. Refer prospects here for the value proposition, the CBA comparison framing, the training-library teaser, and the verification-flow walkthrough. The page is the SEO landing for "CDFI Metro 2 reporting" and "nonprofit credit furnisher" queries.
UTM template for attribution:
https://metro2.switchlabs.dev/metro2/industry/cdfi-nonprofit?utm_source=referral&utm_medium=email&utm_campaign=<your_name>CDFI: API reference
The CDFI-tier endpoints share auth with the rest of Metro2 — API key in the Authorization header, or session cookie from a browser context.
Nonprofit verification — /api/nonprofit-verification
POST /api/nonprofit-verification— verify an EIN against the IRS Tax Exempt Org Search. Body:{ "ein": "13-1234567" }.GET /api/nonprofit-verification?ein=...— fetch a previously cached result without re-querying the IRS (returns the cached row + last-verified timestamp).
Env-gated. Returns source: "stub" when IRS credentials aren't configured.
Certification lookup — /api/certification-lookup
GET /api/certification-lookup?ein=...— look up an EIN in the cached CDFI Fund certified list.GET /api/certification-lookup?name=...— fuzzy lookup by legal name (returns top 10 matches by edit distance).POST /api/certification-lookup/manual-upload— attach a manually uploaded CDFI certification document (PDF of your CDFI Fund certification letter) for customers whose EIN can't be found in the synced list (recent certifications take a few months to propagate to the published list).
CDFI Fund sync — /api/cron/cdfi-sync
POST /api/cron/cdfi-sync— triggered by Vercel cron nightly. Downloads the latest CDFI Fund certified list, diffs it against the cached version, and updates the lookup table. Returns a summary:{ added: N, updated: N, removed: N }.
Env-gated. Authenticated via the Vercel cron secret; can be called manually with the admin key for testing.
Example: verifying a new CDFI customer
# 1. Verify their EIN against IRS.
curl -X POST https://metro2.switchlabs.dev/api/nonprofit-verification \
-H "Authorization: Bearer $METRO2_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "ein": "13-1234567" }'
# 2. Check the CDFI Fund certified list.
curl "https://metro2.switchlabs.dev/api/certification-lookup?ein=13-1234567" \
-H "Authorization: Bearer $METRO2_API_KEY"
# 3. If CDFI lookup misses (recent certification), accept manual upload.
curl -X POST https://metro2.switchlabs.dev/api/certification-lookup/manual-upload \
-H "Authorization: Bearer $METRO2_API_KEY" \
-F "ein=13-1234567" \
-F "cert_file=@cdfi-cert-letter.pdf"CDFI: common questions
Do you replace CBA Reporter?
Per DECISIONS.md, no — Metro2 complements CBA Reporter. Most CDFIs we work with keep their CBA membership for the training, peer network, and the institutional-knowledge access, and use Metro2 for the file generation, validation, dispute workflow, and bureau submission pipeline. The two systems coexist well: CBA Reporter is the brand-of-record in the nonprofit-lending world, Metro2 is the deeper engineering platform.
That said, smaller CDFIs sometimes use Metro2 standalone — if you don't need the CBA training and you have an internal credit-reporting expert already, Metro2 can be the only system you need. We don't push either configuration; whatever works for your program.
Is there a grant tier?
Not yet — the cdfi_grant_funded flag is in place but the grant-billing tier isn't wired up. We track which customers self-identify as grant-funded so we can have a targeted conversation about pricing once the tier ships. If you're grant-funded and ready to talk pricing now, email support@metro2.switchlabs.dev and we'll work out a per-customer arrangement.
Can I upload my CDFI cert manually?
Yes — POST to /api/certification-lookup/manual-upload with your CDFI Fund certification letter as a PDF. We'll attach it to your company record and treat it as authoritative until the next nightly sync finds your EIN in the published list (usually a few months after a fresh certification).
Which account type should I use for credit-builder loans?
The standard answer is Account Type 04 (Home Improvement / Personal Loan) for unsecured credit-builder loans and Account Type 12 (Education) for education-related loans. Module 3 in the training library ("CDFI-specific account types") walks through the full mapping, including the edge cases for shared secured credit-builder products and revolving microloans.
Does the IRS verification work for newly registered 501(c)(3)s?
It works as soon as the IRS posts your determination letter to the Tax Exempt Org Search public dataset, which is typically 2–6 weeks after the determination is issued. If you've been newly approved and the verification is returning "not found," email support with a copy of your determination letter and we'll flip your record to verified manually while the public dataset catches up.
What if I'm a CDFI and a 501(c)(3)?
Both verifications run independently. The CDFI Nonprofit tier treats either one as sufficient — you don't need both to enroll. Most certified CDFIs that are loan funds are also 501(c)(3); most CDFI-certified credit unions are not. Either way, one passing verification unlocks the tier.
What's not yet shipped (Phase C+)
Phase A is API + content. Three things were intentionally deferred to Phase C and beyond:
- Dashboard React components. The full UI for association management (
AssociationManager), disclosure-verify (DisclosureVerifyDialog), and homeowner-consent import (HomeownerConsentImporter) is scaffolded but not wired into the dashboard yet. The backend APIs (covered above) are fully usable today — you can build your own UI on top, or call them withcurlfor one-off ops. Phase C ships the polished dashboard surface. - Live PMS connectors. All five PMS integration cards (CINC, Vantaca, FrontSteps, AppFolio HOA, Buildium HOA) are stubs gated behind our partner program. Each PMS vendor controls API access on their side, and most require a formal partnership before issuing production credentials. We're actively working the partner-program conversation; in the meantime, CSV bulk-import works with exports from any of them.
- CAI / OFN distribution. The Community Associations Institute (CAI) for HOA and the Opportunity Finance Network (OFN) for CDFI are the trade associations we're targeting for distribution partnerships. This is non-engineering work (sponsorships, conference presence, co-marketing) and doesn't affect anything in the codebase, but it's on the roadmap for the verticals to graduate from "shipped" to "adopted at scale."
Related guides
- Industry templates — the shared infra that drives per-vertical marketing copy, validation profiles, and template defaults. Both HOA and CDFI plug into this system.
- Multi-portfolio routing — how to split a company's records across multiple portfolios when (e.g.) one HOA management company services both Portfolio Type O (HOA dues) and Portfolio Type I (installment) accounts.
- Consumer portal — the homeowner-facing / borrower-facing portal where consumers can view their reported tradelines, submit disputes, and revoke consent. Both verticals route consumer-facing flows through the same portal.
Still stuck? Contact support or browse the Help Center.