How to determine financial product eligibility programmatically.

Eligibility is whether a person can access a financial product at all. It is decided from the institution’s published access rules — geography (including property you own, a facility your business maintains, and radius or school-district boundaries), employer, contractor, association, school, occupation, military status — evaluated deterministically against facts the person gives you, and returned as one of four verdicts with the evidence attached. It needs no credit pull. Prequalification and underwriting answer a different question (on what terms will this lender approve you) and belong to the lender. This guide is the method behind RateAPI’s eligibility API, with the live state of its rule graph and a worked example from its data.

Last updated 2026-08-28No credit data required43,724 live rules, 2,942 credit unions

The state of published eligibility, live

What the rule graph holds right now, read from the graph itself — not from a projection that can only see rules with geography. Every number sits next to the stage it belongs to.

Inventory generated 2026-09-27: RateAPI holds 43,724 live membership-eligibility rules across 2,967 inventory institutions; 2,942 of 3,721 active credit unions have at least one current rule (79%), each backed by a verbatim evidence quote and source URL; 1,481 name specific counties (2,438 counties across 51 states) and 1,345 name specific employers (8,035 resolved organizations). The newest rule confirmation is 2026-09-21; individual evidence dates vary. No rule references a credit score, income or debt.
Institutions with live rules
2,942 of 3,721 active US credit unions (79%).
Live rules
43,724, every one stored with the verbatim sentence that justified it — the schema refuses a rule without its quote. 1,566 human-verified, 18,537 machine-validated against a second read.
Freshness
Median rule last confirmed 31.2 days ago; 12,263 of 43,724 confirmed within the last 30 days. Most recent confirmation September 21, 2026.
Geographic reach
1,481 institutions name specific counties — 2,438 distinct counties across 51 states, stored as canonical keys (county:NC:mecklenburg) so a ZIP resolves to an exact index hit.
Employer and affinity reach
1,345 institutions name specific employers, resolved to 8,035 canonical organizations; 219 name schools, 868 name places of worship, 79 carry military paths, 11 are open to anyone by a published rule.
Credit data
0 conditions reference a credit score, income or debt. The condition schema has no kind for credit score, income or debt. Eligibility here is access (who may join), decided from published membership criteria only; it is not prequalification or underwriting.
Geography (live, work, worship or study in a named place)
25,411 rules across 1,636 institutions.
Employment (work for a named employer)
11,186 rules across 1,373 institutions.
Family of a member or eligible person
4,219 rules across 2,308 institutions.
Membership of a named association
1,692 rules across 664 institutions.
Student, alumni or employee of a named school
822 rules across 225 institutions.
Attend a named place of worship
239 rules across 93 institutions.
Military affiliation
139 rules across 79 institutions.
Open to anyone
11 rules across 11 institutions.
Other
5 rules across 4 institutions.
Why an institution is absent is itself data. Published — rules live in the graph: 1,651 · Captured, awaiting review: 1,109 · No membership page found yet: 617 · Queued for capture: 299 · Capture failed: 75. An institution not yet in the graph is undecided — the API returns it as unknown with unknown_reason: no_rules, never as ineligible. This Enterprise Routes aggregate is available at GET /v1/eligibility/coverage and refreshes daily.

Eligibility is not prequalification

Three questions get called “can I get this loan?”. They need different data, have different owners, and must not share an answer.

Eligibility (access)
Can this person hold this product at all? Decided from published access rules and self-reported facts. No credit data. The institution confirms at join time. This is what this guide decides.
Prequalification
Would this lender likely approve them, roughly on what terms? Needs a soft credit pull and the lender’s policy. Per lender, downstream of eligibility.
Underwriting
Will the lender approve, on exactly what terms? Hard pull, full application, the lender’s authority alone.
If you evaluate access and present it as approval, you are making a claim you have no authority to make. If you gate access behind a credit pull, you are hiding products from people who could have had them. Keep the vocabulary honest from the API to the button.

What a decision needs

Three inputs. The third is the one most systems skip, and it is why they cannot explain their answers.

Facts about the person
Home, work and payroll geography (independent facts — a live-or-work charter is a real thing), plus where the person owns property and where their business maintains a facility, which are separate doors again; employer, contractor relationship, school + relationship, place of worship, associations; occupation, military status and the state that service was in; the kind of applicant (a natural person, or a trust, organization, partnership or corporation); qualifiers the person asserts about an employer door; family relation to someone with any of the above. All self-reported. None require documents to evaluate.
Published access rules
What the institution itself says about who may join or hold the product, captured as rules (one way to qualify = one rule) with conditions (ANDed within a rule), each carrying its source URL, verbatim evidence quote and observation time. The condition vocabulary spans state, county, city and ZIP geography plus radius (“within 25 miles of this branch”), school-district and census-tract boundaries; residence, work, payroll, worship, study, business, property-ownership and facility doors; employer, contractor, alumni, association, worship and military doors, the last of which can be scoped to one state; and applicant kind and org qualifiers that narrow a door the employer name alone would overstate.
A deterministic evaluator
Code, not a model, decides. Given facts and rules it must return the same verdict every time and be able to name the rule and conditions that produced it. Models are for extracting rules from prose; they are not the thing that decides.

What a rule looks like, as stored

“Evidence on every verdict” is an abstraction until you see one. Three live rules from the graph, exactly as held: the verbatim quote, where it came from, how it was verified, and the canonical condition it became.

Piedmont Advantage Credit Union (NC)
“Mecklenburg County” — geography rule, auto-extracted, confirmed 2026-08-22; condition county:NC:mecklenburg; source: the credit union’s own membership page.
Ocean Financial Credit Union (NY)
“Employees and Members of Maria Regina School and Parish Office, Seaford, NY” — employment rule, human-verified; condition employee_of → resolved organization id; source: the credit union’s own membership page.
Miami Firefighters Credit Union (FL)
“Employees of the City of Miami Firefighters and Police Officers Retirement Trust” — employment rule, human-verified; condition employee_of → resolved organization id; source: the credit union’s own membership page.
The quote is not decoration. It is what lets a verdict be checked (open the source, find the sentence), corrected (the sentence changed, supersede the rule) and aged (the page has not been re-confirmed, lower the confidence). A rule that cannot be traced to a sentence cannot be defended to a user or a regulator.

The method

  1. Collect one high-value fact, then decide

    Home ZIP resolves server-side to county and state, and community charters are the most common access rule. Ask for it first and evaluate immediately. Every further fact sharpens the verdicts; none is required to start.

    $POST /v1/eligibility/search
    curl -X POST "https://api.rateapi.dev/v1/eligibility/search" \
    -H "X-API-Key: $RATE_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{ "home_zip": "28202", "home_state": "NC", "home_county": "Mecklenburg" }'
    # No credit data in the request. Nothing about income, score or debt.
  2. Narrow candidates by index, not by scan

    Turn the facts into canonical keys — county:NC:mecklenburg, org:<employer id> — and look them up in an inverse index over rule conditions. Add the institutions whose rules are open to anyone. Cap the set: a bounded candidate set with an explicit candidate_set_truncated flag is honest; an unbounded scan that times out is not.

  3. Evaluate each candidate against its FULL rule set

    The trap: an institution surfaced by one matching condition is not thereby eligible. Its rule may require that condition AND another the person fails. Scoring on the condition that surfaced the candidate only ever over-reports.

    TSDeciding one institution
    // The shape of a correct eligibility decision. Deterministic all the way down.
    type Verdict = 'eligible' | 'conditionally_eligible' | 'possibly_eligible' | 'unknown';
    function decide(rules: Rule[], facts: Facts): { status: Verdict; evidence: Evidence[]; unknown_reason?: string } {
    if (rules.length === 0) return { status: 'unknown', evidence: [], unknown_reason: 'no_rules' };
    const outcomes = rules.map((rule) => evaluateRule(rule, facts)); // AND across the rule's conditions
    const matched = outcomes.find((o) => o.status === 'eligible');
    if (matched) return { status: 'eligible', evidence: [matched.evidence] };
    const oneStep = outcomes.find((o) => o.status === 'conditionally_eligible');
    if (oneStep) return { status: 'conditionally_eligible', evidence: [oneStep.evidence] }; // carries join cost + URL
    // A rule that COULD match if we knew one more fact is not a no.
    const undecidable = outcomes.find((o) => o.missing_fact);
    if (undecidable) return { status: 'unknown', evidence: [], unknown_reason: undecidable.missing_fact };
    // Every held rule was evaluated and failed. Still not "ineligible": we cannot
    // prove the institution has no other door we have not captured yet.
    return { status: 'unknown', evidence: [], unknown_reason: 'fom_completeness_unaffirmed' };
    }
  4. Return four verdicts, never a boolean

    eligible
    A rule matched the facts. Carry the rule id, matched conditions and a reason sentence.
    conditionally_eligible
    One documented step away — and the response must NAME the step and its cost (join an association for $3, open a $5 share account).
    possibly_eligible
    A rule plausibly applies but a requirement could not be confirmed from the facts held.
    unknown
    Could not decide. NEVER rendered as ineligible. Always carries a machine-readable unknown_reason.
    and a question back
    A verdict short of “yes” is half an answer. Every returned possibly_eligible and unknown item carries missing_facts[] when a question would move it: the question to ask in the second person, the fact_field an answer populates, and the institution’s verbatim evidence_quote. The response rolls those up into next_questions[] — sorted so the question that opens the most institutions comes first — and states, in unanswerable[], what it deliberately will not ask about and why.
  5. Attach the evidence to every verdict

    The response below is what that looks like in practice — real, dated, trimmed. Note the eligible entry: a county match, with the rule id and the exact condition key that fired, and how many rules the institution holds in total.

    JSResponse excerpt (observed 2026-08-27)
    {
    "counts": { "eligible": 15, "conditionally_eligible": 2, "possibly_eligible": 0, "unknown": 383 },
    "candidates_evaluated": 400,
    "candidate_set_truncated": true,
    "eligible": [
    {
    "name": "Piedmont Advantage Credit Union", "state": "NC",
    "status": "eligible", "confidence": 1, "engine": "graph",
    "reasons": ["You appear to be eligible to join. Qualifies: Lives in Mecklenburg County, NC"],
    "paths": [{
    "rule_id": 18281, "kind": "geography",
    "conditions_matched": ["county:NC:mecklenburg"], "status": "eligible"
    }],
    "coverage": { "state": "published", "rules_held": 7, "last_confirmed_at": "2026-08-22T18:07:43Z" }
    }
    ],
    "conditionally_eligible": [
    {
    "name": "American 1 Credit Union", "state": "MI",
    "status": "conditionally_eligible", "join_cost_usd": 3,
    "reasons": ["You can join by completing one additional step. Qualifies after one step: Open to anyone and Qualifying payment of $3"],
    "paths": [{ "rule_id": 2959, "kind": "open", "conditional_on": { "type": "deposit_or_donation", "cost_usd": 3 } }]
    }
    ],
    "unknown": [
    {
    "name": "Alta Vista Credit Union", "status": "unknown",
    "unknown_reason": "unmodeled_condition",
    "coverage": { "state": "under_review", "rules_held": 0 }
    }
    ],
    "disclosure": "Membership eligibility is guidance based on public charter data and institution websites; final determination is made by the institution."
    }
    // Observed 2026-08-27. Trimmed to one entry per bucket; the real response carries up to `limit` per bucket,
    // plus the `missing_facts` / `next_questions` / `unanswerable` block shown below.
  6. Ask the question the response hands you

    unknown is only useful if it says why — and better still if it says what to ask. In the response above, 383 of 400 candidates are unknown, most because the one fact supplied (a county) cannot decide an employer- or association-based rule. Rather than making the caller infer the remedy from unknown_reason, the response carries next_questions[]: read the first entry, ask it, call again. What no question can fix is listed separately in unanswerable[] — we hold no rules for the institution, the page names an organization we could not resolve, or the boundary is one we can state but not decide (a census tract, a school district, a radius around an office the page never identifies).

    JSThe refinement loop
    // The response tells you what to ask next. You do not have to infer it.
    // next_questions[] is the cross-institution rollup, already sorted: the question that
    // opens the most credit unions, with the fewest facts, first.
    {
    "next_questions": [
    {
    "question": "Who do you work for?",
    "fact_field": ["employer", "relative_employers"],
    "kind": "employee_of",
    "unlocks_cu_count": 27,
    "unlocks_cu_ids": ["...", "..."],
    "example_evidence_quote": "Employees of St. Francis Hospital and members of their immediate families..."
    }
    ],
    // What no question can fix. Say this out loud rather than rendering it as a "no".
    "unanswerable": [
    { "reason": "no_rules", "cu_count": 12,
    "note": "We hold no published membership rules for these institutions." },
    { "reason": "unmodeled_condition", "cu_count": 4, "condition_kinds": ["geo_residence"],
    "note": "A boundary we can state but cannot decide - a census tract, a school district, or a radius around an office the page does not identify." }
    ]
    }
    // The loop, in code:
    const next = result.next_questions?.[0];
    if (next) {
    const answer = await ask(next.question); // one question, in the user's words
    return search({ ...facts, [next.fact_field[0]]: answer });
    }
    // Nothing to ask: render result.unanswerable, never a bare "ineligible".

Worked example, live: Mecklenburg County, NC

The place-based inverse of the same question: which institutions’ published membership criteria reach this county? Answered graph-first — live geographic rules, legacy records only where the graph is silent — at request time.

Institutions with positive evidence
15 credit unions publish criteria that reach Mecklenburg County — 1 name the county specifically, 1 serve all of NC, 13 are open to anyone.
Result semantics
positive_evidence_only — the list is non-exhaustive. An institution that is absent is not ineligible; it is undecided.
Evidence observed
Most recent membership page verification August 27, 2026.
Piedmont Advantage Credit Union
Names 6 counties — evidence: the credit union’s own membership page (verified August 22, 2026).
Every row above traces to a verbatim quote on the institution’s own page. That is the whole difference between an eligibility answer and a directory listing: the answer can be checked, corrected and aged out. Browse the full list at who can join a credit union in North Carolina.

Where the rules come from

A decision is only as good as its rules. Here is how RateAPI’s are captured — the same standard you should hold any source to, including your own.

Verbatim evidence
A rule is stored only with the sentence that justified it, machine-checked to appear in the captured page text. No quote, no rule — enforced at the database, not by convention.
Identity guard
The evidence page must belong to the institution it is filed under, cross-checked against the NCUA charter record. Shared vendor pages and look-alike names are the most common way eligibility data goes wrong.
Closed-world geography
Counties resolve against the US Census list for that state. A county that does not exist cannot be matched, and a record that names the state but no county serves the whole state rather than being dropped.
Two-vendor gate on “open to anyone”
The highest-stakes claim needs agreement from a second model at a different vendor before it is published — which is why so few institutions carry it by rule.
Supersede, never overwrite
Rules carry observed_at and superseded_at. A verdict from six months ago stays explainable with the rule that produced it.
LLMs extract; code decides. Extraction from prose is a fuzzy problem with a human-checkable output. Deciding must be deterministic, reproducible and explainable — properties a model cannot promise. Populate the rule graph with models; evaluate it with code. Full detail in the eligibility methodology.

Endpoints

GET/v1/eligibility/coverage

The fleet-wide aggregate rendered above: live rules, institutions, verification, freshness, reach, and a dated headline sentence. Enterprise Routes; no row-level data.

POST/v1/eligibility/search

The person-based search: facts in, four buckets of institutions out, each with reasons, rule paths, confidence, coverage and unknown_reason — plus missing_facts on the undecided items, and the response-level next_questions and unanswerable block.

POST/v1/eligibility/facts

The person’s own prose in, the fields the search accepts out — each with the exact words it was read from and a needs_confirmation flag. An extractor, not a decider: the response has no property in which a status, a bucket or an institution could be returned. Show the person what was read, then send person_search_body to the search.

GET/v1/eligibility/search

The place-based inverse: ?state=NC&county=Mecklenburg returns institutions whose published criteria reach that place, with per-row provenance. The data behind the worked example above.

POST/v1/eligibility/check

Verdicts for up to 50 named institutions against one person — when you already have the list, for instance from a rate search.

Frequently asked