---
title: "Person-based membership discovery: which credit unions can I join?"
method: POST
path: "/v1/eligibility/search"
tags: ["Reference Data"]
---

# Person-based membership discovery: which credit unions can I join?

`POST /v1/eligibility/search`

## Request

- Base URL: `https://api.rateapi.dev`
- URL: `https://api.rateapi.dev/v1/eligibility/search`
- Auth: API key in header `X-API-Key`

The inverse of `/v1/eligibility/check`. Instead of a list of candidate credit unions, you supply the person — where they live or work, who they work for, their occupation or military affiliation, where they studied, and what they belong to — and the eligibility graph returns matching institutions bucketed by certainty.

At least one discriminating fact is required (`home_zip`, `home_state`, `home_county`, `home_city`, `work_zip`, `work_state`, `work_county`, `work_city`, `occupation`, `employer`, `school`, `worship`, `military_status`, `associations`, or `willing_to_join_association: true`); otherwise the honest answer would be "every credit union", which is not a search result. City facts require either their same-role state or a recognized same-role ZIP that uniquely identifies one state, and are accepted only when the closed Census gazetteer resolves them.

How it works: canonical geo keys, resolved organization ids, and bounded positive condition-kind scans NARROW to candidates; every candidate is then EVALUATED against its full rule set (never on the strength of the one condition that surfaced it), and results are BUCKETED. Credit unions with no graph rules yet fall back to the same annotation engine the rest of the API serves, marked `engine: "legacy"` per item. Candidate discovery is capped, so this result set is positive-evidence-only and non-exhaustive; absence from it never means ineligibility.

**Never publishes ineligibility**: a credit union whose held paths do not match is returned in `unknown` with an actionable `unknown_reason`. A web-derived rule set is open-world: failure to match it cannot prove that every membership path is absent.

The same capability ships as the MCP tool `find_eligible_credit_unions`.

**Authentication required**: Pass your API key in the `X-API-Key` header.

## Request body

- object: The person's facts. At least one geographic, occupation, employer, school, worship, military, association, or join-intent fact is required. Payroll geography is independent from worksite geography.
  - `home_zip` string: US 5-digit home ZIP or ZIP+4. When home_state is also supplied, the ZIP crosswalk must agree with it.
  - `home_state` string: 2-letter US state code.
  - `home_county` string: Home county. Wins over the ZIP-derived counties.
  - `home_city` string: Census-recognized home city. Requires home_state or a recognized home_zip that uniquely identifies one state; unresolved place labels are rejected rather than guessed.
  - `work_zip` string: US 5-digit work ZIP or ZIP+4. When work_state is also supplied, the ZIP crosswalk must agree with it.
  - `work_state` string: 2-letter state where the person works.
  - `work_county` string: Work county. Wins over work-ZIP-derived counties.
  - `work_city` string: Census-recognized city where the person works. Requires work_state or a recognized work_zip that uniquely identifies one state.
  - `payroll_zip` string: US ZIP from which the employer issues payroll. When payroll_state is supplied, the ZIP crosswalk must agree with it.
  - `payroll_state` string: 2-letter state from which the employer issues payroll; distinct from work_state.
  - `payroll_county` string: County from which the employer issues payroll; distinct from work_county.
  - `payroll_city` string: Census-recognized city from which the employer issues payroll. Requires payroll_state or a recognized payroll_zip that uniquely identifies one state.
  - `occupation` string: Occupation or trade for occupation/TIP membership paths.
  - `employer` string: Employer, resolved to a canonical organization for SEG matching.
  - `employer_org_ids` string[]: Caller-confirmed canonical employer ids from entity discovery. Every id is revalidated against a positive condition in the current active publication before use.
  - `school` string: School, college, or university, resolved to a canonical organization.
  - `school_org_ids` string[]: Caller-confirmed canonical school ids, revalidated against current active publications.
  - `school_relationship` 'student' | 'alumni' | 'employee': How the person relates to the school; required to affirm typed student, alumni, or school-employee graph paths.
  - `associations` string[]: Associations, unions, or member organizations the person ALREADY belongs to.
  - `association_org_ids` string[]: Canonical ids for associations the person already belongs to. Selecting a discovered association does not itself confirm membership.
  - `military_status` 'active_duty' | 'veteran' | 'reservist' | 'dod_civilian' | 'military_family': Military affiliation, matched against military fields of membership.
  - `worship` string: Place of worship, resolved to a canonical organization.
  - `worship_org_ids` string[]: Caller-confirmed canonical place-of-worship ids, revalidated against current active publications.
  - `family_relation` string: How the user relates to a RELATIVE whose own affiliations may qualify them ('spouse', 'child', 'immediate family'). Required alongside the relative_* lists: without it there is nobody for those organizations to belong to. This answers SEG lead-ins of the form "Employees of the following companies AND THEIR IMMEDIATE FAMILIES are eligible".
  - `relative_employers` string[]: Employers of the RELATIVE named by family_relation — not the user's own employer. Optional; omitted, a compound family door evaluates to `unknown`, never to a negative.
  - `relative_employer_org_ids` string[]: Caller-confirmed canonical employers belonging to the relative named by family_relation.
  - `relative_schools` string[]: Schools the RELATIVE named by family_relation attends. Same unknown-by-default contract as relative_employers.
  - `relative_school_org_ids` string[]: Caller-confirmed canonical schools belonging to the relative named by family_relation.
  - `relative_associations` string[]: Associations the RELATIVE named by family_relation belongs to. Same unknown-by-default contract as relative_employers.
  - `relative_association_org_ids` string[]: Caller-confirmed canonical associations belonging to the relative named by family_relation.
  - `relative_memberships` object[]: Confirmed relative memberships scoped to one exact credit union. Membership at one institution is never reused for another; omit unconfirmed memberships and that family door remains unknown.
    - `credit_union_id` string, required: The exact target credit union id.
    - `relation` string, required: The relative's relationship to the applicant.
    - `status` 'current_member' | 'deceased_member', required: Whether the confirmed relationship is to a current or deceased member.
  - `willing_to_join_association` boolean: When true, paths that require joining an association are treated as reachable and surface in `conditionally_eligible` with the cost and join URL when known.
  - `property_zip` string: ZIP where the person OWNS REAL PROPERTY. A door of its own (migration 054): never inferred from where they live, and an absentee owner qualifies.
  - `property_state` string: State where the person owns real property. Distinct from their home state.
  - `property_county` string: County where the person owns real property. Distinct from their home county.
  - `property_city` string: Census-recognized city where the person owns real property. Requires property_state or a recognized property_zip that uniquely identifies one state.
  - `facility_zip` string: ZIP where the applicant's business maintains a location. Distinct from the worksite ZIP.
  - `facility_state` string: State where the applicant's business maintains a location.
  - `facility_county` string: County where the applicant's business maintains a location.
  - `facility_city` string: Census-recognized city where the applicant's business maintains a location. Requires facility_state or a recognized facility_zip that uniquely identifies one state.
  - `applicant_kind` 'person' | 'trust' | 'organization' | 'partnership' | 'corporation' | 'other_legal_entity': What the applicant IS. Absent means a natural person, which is what every caller has always been. Some charters admit entities; a person is never rejected by an entity-only door, only left unknown.
  - `military_service_state` string: The state the person serves, or served, in. A state-scoped military door ("Oklahoma National Guard") needs this; without it such a door reads unknown rather than eligible.
  - `asserted_org_qualifiers` string[]: Narrowings the person asserts about themselves, quoted back from a missing_facts question (e.g. "assigned as Recruiters or ROTC Instructors within the State of Arkansas"). Absence CAPS a narrowed door at possibly_eligible rather than closing it.
  - `limit` integer: Maximum entries returned PER BUCKET. Default 50.

## Response `200`

Credit unions this person can join, bucketed by certainty. Evaluated non-matches remain visible in `unknown`; no web-derived record is published as ineligible.

- object
  - `counts` object, required: Bucket sizes BEFORE `limit` is applied.
    - `eligible` integer: Credit unions the borrower already qualifies for.
    - `conditionally_eligible` integer: Credit unions the borrower can qualify for by taking a documented step, such as joining an association.
    - `possibly_eligible` integer: Credit unions where a rule partially matched but could not be confirmed.
    - `unknown` integer: Credit unions we could not decide for this person — whether because we hold no rules for them or because the supplied facts could not decide the rules we do hold. Read `unknown_reason` on each entry to tell those apart. Unknown never means ineligible.
  - `candidates_evaluated` integer, required: How many credit unions were evaluated for this request.
  - `candidate_limit` integer, required: Hard cap on unique institutions admitted to this request's candidate set.
  - `candidate_set_truncated` boolean, required: True when the overall candidate cap or a bounded source scan was reached. The response is always non-exhaustive; true makes observed truncation explicit.
  - `candidate_sources` object, required: Unique admitted-candidate counts by narrowing source; counts reflect priority and deduplication within the bounded set.
    - `exact_graph` integer, required: Unique candidates admitted from exact geo/org keys or input-specific occupation/military scans.
    - `legacy` integer, required: Unique candidates admitted from input-gated flat affinity/state records.
    - `open_graph` integer, required: Unique candidates admitted by the broad open-to-anyone graph scan.
    - `legacy_open` integer, required: Unique candidates admitted by the broad flat open-to-anyone scan.
    - `association_graph` integer, required: Unique candidates admitted by the broad member-of graph scan.
  - `eligible` PersonEligibilityMatch[], required: A verified path already applies.
    - `credit_union_id` string, required: Canonical identifier of the matched credit union.
    - `name` string, required: Name of the matched credit union.
    - `state` string, required: Two-letter US state code the credit union is chartered in.
    - `status` 'eligible' | 'conditionally_eligible' | 'possibly_eligible' | 'unknown', required: The bucket this entry sits in. `ineligible` never appears; evaluated non-matches are returned as `unknown`.
    - `confidence` number, required: Confidence in this match, 0–1, carried from the extracted eligibility rule.
    - `reasons` string[], required: Deterministic sentences explaining the verdict. Never model prose.
    - `paths` EligibilityGraphPath[]: Structured graph paths. Absent on legacy-annotation fallbacks.
      - `rule_id` integer, required: Id of the published rule this path came from.
      - `kind` string, required: Rule family (e.g. community_geo, employer, association, military, school, worship, family).
      - `reason` string, required: Deterministic sentence describing the path.
      - `conditions_matched` string[], required: Canonical condition ids the supplied facts satisfied.
      - `conditions_unknown` string[]: Condition ids left unresolved — what to supply to decide this path.
      - `status` 'eligible' | 'conditionally_eligible' | 'possibly_eligible' | 'ineligible' | 'unknown': Set on non-positive paths so callers can explain the gap. `ineligible` is reserved for compatibility; current customer-safe projections normalize unattested path negatives to `unknown`.
      - `conditional_on` object: The one further step that would make this path apply.
        - `type` 'join_association' | 'family_link' | 'deposit_or_donation': The kind of action the borrower must take to satisfy this path.
        - `org_id` string: Identifier of the association, employer, or organization the borrower must join or link to.
        - `cost_usd` number: Up-front cost of the step, when published.
        - `join_url` string, uri: Page where the borrower can complete the joining step.
    - `join_cost_usd` number: Up-front cost of the cheapest conditional step, when published.
    - `join_url` string, uri: Where to take the conditional step (e.g. the association's join page).
    - `engine` 'graph' | 'legacy', required: `graph` when the verdict came from published eligibility-graph rules, `legacy` when it came from the annotation fallback for a credit union with no rules yet.
    - `publication` EligibilityPublicationRef: Exact immutable active eligibility generation evaluated for this response.
      - `publication_id` integer, required: Database identifier of the active publication.
      - `ruleset_sha256` string, required: SHA-256 digest of the complete ordered rule set.
      - `activated_at` string, date-time, required: When this generation became serving authority.
      - `proposal_coverage` EligibilityProposalCoverage: How many model-proposed claims from the source page survived evidence verification. This is not an attestation that every real-world field-of-membership path is known.
        - `scope` 'source_page_model_proposals', required: Metric scope, explicitly limited to model proposals extracted from reviewed source pages.
        - `proposed_claims` integer, required: Claims proposed by extraction from the reviewed source pages.
        - `verified_claims` integer, required: Proposed claims that passed evidence verification.
        - `published_rules` integer, required: Verified graph rules published from the candidate.
        - `withheld_claims` integer, required: Proposed claims withheld because verification did not pass.
        - `proposal_survival` number, nullable, required: Verified claims divided by proposed claims, or null when no claims were proposed.
        - `withheld_reasons` object, required: Withheld claim counts grouped by machine-readable verification reason.
    - `unknown_reason` 'no_rules' | 'geo_inputs_missing' | 'org_inputs_missing' | 'inputs_missing' | 'low_confidence_rules' | 'incomplete_fom' | 'fom_completeness_unaffirmed' | 'unmodeled_condition': WHY this entry is not a positive match. Present only on non-positive statuses. This is what makes a flat `unknown` array actionable: `geo_inputs_missing` / `org_inputs_missing` / `inputs_missing` mean the CALLER can resolve it by supplying one more fact; `no_rules` means we hold nothing for this institution; `incomplete_fom`, `fom_completeness_unaffirmed`, `low_confidence_rules` and `unmodeled_condition` mean we hold rules but will not turn a failure against them into a no. Legacy-annotation entries (`engine: "legacy"`) report their own reason mapped onto this same vocabulary.
    - `coverage` object, required: What we HOLD about this institution, as distinct from what we concluded about the person. `status` answers "what did we decide"; this answers "what were we deciding from".
      - `state` 'published' | 'no_rules_held' | 'under_review', required: `published` — we hold live rules and evaluated them. `no_rules_held` — we hold none, so this answer rests on the legacy annotation. `under_review` — we hold none and the institution is queued for a human look. Deliberately coarse: our internal pipeline states are not part of this contract.
      - `rules_held` integer, required: Live rules actually held and evaluated for this institution in this response. This count is coverage telemetry, not proof that every possible membership path is known.
      - `last_confirmed_at` string, date-time, nullable, required: Most recent confirmation across those rules. Null when nothing was held, and null when the rules carry no confirmation timestamp — an unconfirmed rule does not borrow the credibility of a confirmed one.
      - `fom_affirmed` boolean, nullable, required: Reserved coverage telemetry. False for current graph rules because no typed closed-world attestation exists; null when there were no rules to assess. Geographic source agreement alone never turns this true or authorizes a negative.
    - `missing_facts` EligibilityMissingFact[]: The question(s) to ask this person next about THIS institution, ranked fewest-facts-most-doors and capped at 3. Present only on `unknown` and `possibly_eligible` entries, and only when a question would help.
      - `cu_id` string, required: The institution this question would unlock.
      - `condition_id` integer, required: The membership condition that evaluated unknown. Stable across calls for a given publication.
      - `kind` string, required: The evaluator condition kind, verbatim (`geo_residence`, `employee_of`, `military_affiliation`, …), so a caller can group questions without parsing prose.
      - `question` string, required: The question to ask the person, in the second person, answerable in one breath. Composed in code from a fixed template; never model prose.
      - `fact_field` string[], required: The request field(s) an answer populates. Always at least one, and always a field this endpoint accepts — merge the answers into the same request body and call again.
      - `why` string, required: Why we are asking, in one deterministic sentence naming the door.
      - `evidence_quote` string: Verbatim from the INSTITUTION's own page, via the rule's evidence quote, truncated on a word boundary at 240 characters. Absent when the backing rule carries no quote. This is never the person's own words.
      - `resolves_reason` 'no_rules' | 'geo_inputs_missing' | 'org_inputs_missing' | 'inputs_missing' | 'low_confidence_rules' | 'incomplete_fom' | 'fom_completeness_unaffirmed' | 'unmodeled_condition', required: The `unknown_reason` an answer to this question would resolve.
  - `conditionally_eligible` PersonEligibilityMatch[], required: One more step away — e.g. joining an association — with `join_cost_usd` and `join_url` when known.
    - `credit_union_id` string, required: Canonical identifier of the matched credit union.
    - `name` string, required: Name of the matched credit union.
    - `state` string, required: Two-letter US state code the credit union is chartered in.
    - `status` 'eligible' | 'conditionally_eligible' | 'possibly_eligible' | 'unknown', required: The bucket this entry sits in. `ineligible` never appears; evaluated non-matches are returned as `unknown`.
    - `confidence` number, required: Confidence in this match, 0–1, carried from the extracted eligibility rule.
    - `reasons` string[], required: Deterministic sentences explaining the verdict. Never model prose.
    - `paths` EligibilityGraphPath[]: Structured graph paths. Absent on legacy-annotation fallbacks.
      - `rule_id` integer, required: Id of the published rule this path came from.
      - `kind` string, required: Rule family (e.g. community_geo, employer, association, military, school, worship, family).
      - `reason` string, required: Deterministic sentence describing the path.
      - `conditions_matched` string[], required: Canonical condition ids the supplied facts satisfied.
      - `conditions_unknown` string[]: Condition ids left unresolved — what to supply to decide this path.
      - `status` 'eligible' | 'conditionally_eligible' | 'possibly_eligible' | 'ineligible' | 'unknown': Set on non-positive paths so callers can explain the gap. `ineligible` is reserved for compatibility; current customer-safe projections normalize unattested path negatives to `unknown`.
      - `conditional_on` object: The one further step that would make this path apply.
        - `type` 'join_association' | 'family_link' | 'deposit_or_donation': The kind of action the borrower must take to satisfy this path.
        - `org_id` string: Identifier of the association, employer, or organization the borrower must join or link to.
        - `cost_usd` number: Up-front cost of the step, when published.
        - `join_url` string, uri: Page where the borrower can complete the joining step.
    - `join_cost_usd` number: Up-front cost of the cheapest conditional step, when published.
    - `join_url` string, uri: Where to take the conditional step (e.g. the association's join page).
    - `engine` 'graph' | 'legacy', required: `graph` when the verdict came from published eligibility-graph rules, `legacy` when it came from the annotation fallback for a credit union with no rules yet.
    - `publication` EligibilityPublicationRef: Exact immutable active eligibility generation evaluated for this response.
      - `publication_id` integer, required: Database identifier of the active publication.
      - `ruleset_sha256` string, required: SHA-256 digest of the complete ordered rule set.
      - `activated_at` string, date-time, required: When this generation became serving authority.
      - `proposal_coverage` EligibilityProposalCoverage: How many model-proposed claims from the source page survived evidence verification. This is not an attestation that every real-world field-of-membership path is known.
        - `scope` 'source_page_model_proposals', required: Metric scope, explicitly limited to model proposals extracted from reviewed source pages.
        - `proposed_claims` integer, required: Claims proposed by extraction from the reviewed source pages.
        - `verified_claims` integer, required: Proposed claims that passed evidence verification.
        - `published_rules` integer, required: Verified graph rules published from the candidate.
        - `withheld_claims` integer, required: Proposed claims withheld because verification did not pass.
        - `proposal_survival` number, nullable, required: Verified claims divided by proposed claims, or null when no claims were proposed.
        - `withheld_reasons` object, required: Withheld claim counts grouped by machine-readable verification reason.
    - `unknown_reason` 'no_rules' | 'geo_inputs_missing' | 'org_inputs_missing' | 'inputs_missing' | 'low_confidence_rules' | 'incomplete_fom' | 'fom_completeness_unaffirmed' | 'unmodeled_condition': WHY this entry is not a positive match. Present only on non-positive statuses. This is what makes a flat `unknown` array actionable: `geo_inputs_missing` / `org_inputs_missing` / `inputs_missing` mean the CALLER can resolve it by supplying one more fact; `no_rules` means we hold nothing for this institution; `incomplete_fom`, `fom_completeness_unaffirmed`, `low_confidence_rules` and `unmodeled_condition` mean we hold rules but will not turn a failure against them into a no. Legacy-annotation entries (`engine: "legacy"`) report their own reason mapped onto this same vocabulary.
    - `coverage` object, required: What we HOLD about this institution, as distinct from what we concluded about the person. `status` answers "what did we decide"; this answers "what were we deciding from".
      - `state` 'published' | 'no_rules_held' | 'under_review', required: `published` — we hold live rules and evaluated them. `no_rules_held` — we hold none, so this answer rests on the legacy annotation. `under_review` — we hold none and the institution is queued for a human look. Deliberately coarse: our internal pipeline states are not part of this contract.
      - `rules_held` integer, required: Live rules actually held and evaluated for this institution in this response. This count is coverage telemetry, not proof that every possible membership path is known.
      - `last_confirmed_at` string, date-time, nullable, required: Most recent confirmation across those rules. Null when nothing was held, and null when the rules carry no confirmation timestamp — an unconfirmed rule does not borrow the credibility of a confirmed one.
      - `fom_affirmed` boolean, nullable, required: Reserved coverage telemetry. False for current graph rules because no typed closed-world attestation exists; null when there were no rules to assess. Geographic source agreement alone never turns this true or authorizes a negative.
    - `missing_facts` EligibilityMissingFact[]: The question(s) to ask this person next about THIS institution, ranked fewest-facts-most-doors and capped at 3. Present only on `unknown` and `possibly_eligible` entries, and only when a question would help.
      - `cu_id` string, required: The institution this question would unlock.
      - `condition_id` integer, required: The membership condition that evaluated unknown. Stable across calls for a given publication.
      - `kind` string, required: The evaluator condition kind, verbatim (`geo_residence`, `employee_of`, `military_affiliation`, …), so a caller can group questions without parsing prose.
      - `question` string, required: The question to ask the person, in the second person, answerable in one breath. Composed in code from a fixed template; never model prose.
      - `fact_field` string[], required: The request field(s) an answer populates. Always at least one, and always a field this endpoint accepts — merge the answers into the same request body and call again.
      - `why` string, required: Why we are asking, in one deterministic sentence naming the door.
      - `evidence_quote` string: Verbatim from the INSTITUTION's own page, via the rule's evidence quote, truncated on a word boundary at 240 characters. Absent when the backing rule carries no quote. This is never the person's own words.
      - `resolves_reason` 'no_rules' | 'geo_inputs_missing' | 'org_inputs_missing' | 'inputs_missing' | 'low_confidence_rules' | 'incomplete_fom' | 'fom_completeness_unaffirmed' | 'unmodeled_condition', required: The `unknown_reason` an answer to this question would resolve.
  - `possibly_eligible` PersonEligibilityMatch[], required: A path may apply but a requirement could not be confirmed.
    - `credit_union_id` string, required: Canonical identifier of the matched credit union.
    - `name` string, required: Name of the matched credit union.
    - `state` string, required: Two-letter US state code the credit union is chartered in.
    - `status` 'eligible' | 'conditionally_eligible' | 'possibly_eligible' | 'unknown', required: The bucket this entry sits in. `ineligible` never appears; evaluated non-matches are returned as `unknown`.
    - `confidence` number, required: Confidence in this match, 0–1, carried from the extracted eligibility rule.
    - `reasons` string[], required: Deterministic sentences explaining the verdict. Never model prose.
    - `paths` EligibilityGraphPath[]: Structured graph paths. Absent on legacy-annotation fallbacks.
      - `rule_id` integer, required: Id of the published rule this path came from.
      - `kind` string, required: Rule family (e.g. community_geo, employer, association, military, school, worship, family).
      - `reason` string, required: Deterministic sentence describing the path.
      - `conditions_matched` string[], required: Canonical condition ids the supplied facts satisfied.
      - `conditions_unknown` string[]: Condition ids left unresolved — what to supply to decide this path.
      - `status` 'eligible' | 'conditionally_eligible' | 'possibly_eligible' | 'ineligible' | 'unknown': Set on non-positive paths so callers can explain the gap. `ineligible` is reserved for compatibility; current customer-safe projections normalize unattested path negatives to `unknown`.
      - `conditional_on` object: The one further step that would make this path apply.
        - `type` 'join_association' | 'family_link' | 'deposit_or_donation': The kind of action the borrower must take to satisfy this path.
        - `org_id` string: Identifier of the association, employer, or organization the borrower must join or link to.
        - `cost_usd` number: Up-front cost of the step, when published.
        - `join_url` string, uri: Page where the borrower can complete the joining step.
    - `join_cost_usd` number: Up-front cost of the cheapest conditional step, when published.
    - `join_url` string, uri: Where to take the conditional step (e.g. the association's join page).
    - `engine` 'graph' | 'legacy', required: `graph` when the verdict came from published eligibility-graph rules, `legacy` when it came from the annotation fallback for a credit union with no rules yet.
    - `publication` EligibilityPublicationRef: Exact immutable active eligibility generation evaluated for this response.
      - `publication_id` integer, required: Database identifier of the active publication.
      - `ruleset_sha256` string, required: SHA-256 digest of the complete ordered rule set.
      - `activated_at` string, date-time, required: When this generation became serving authority.
      - `proposal_coverage` EligibilityProposalCoverage: How many model-proposed claims from the source page survived evidence verification. This is not an attestation that every real-world field-of-membership path is known.
        - `scope` 'source_page_model_proposals', required: Metric scope, explicitly limited to model proposals extracted from reviewed source pages.
        - `proposed_claims` integer, required: Claims proposed by extraction from the reviewed source pages.
        - `verified_claims` integer, required: Proposed claims that passed evidence verification.
        - `published_rules` integer, required: Verified graph rules published from the candidate.
        - `withheld_claims` integer, required: Proposed claims withheld because verification did not pass.
        - `proposal_survival` number, nullable, required: Verified claims divided by proposed claims, or null when no claims were proposed.
        - `withheld_reasons` object, required: Withheld claim counts grouped by machine-readable verification reason.
    - `unknown_reason` 'no_rules' | 'geo_inputs_missing' | 'org_inputs_missing' | 'inputs_missing' | 'low_confidence_rules' | 'incomplete_fom' | 'fom_completeness_unaffirmed' | 'unmodeled_condition': WHY this entry is not a positive match. Present only on non-positive statuses. This is what makes a flat `unknown` array actionable: `geo_inputs_missing` / `org_inputs_missing` / `inputs_missing` mean the CALLER can resolve it by supplying one more fact; `no_rules` means we hold nothing for this institution; `incomplete_fom`, `fom_completeness_unaffirmed`, `low_confidence_rules` and `unmodeled_condition` mean we hold rules but will not turn a failure against them into a no. Legacy-annotation entries (`engine: "legacy"`) report their own reason mapped onto this same vocabulary.
    - `coverage` object, required: What we HOLD about this institution, as distinct from what we concluded about the person. `status` answers "what did we decide"; this answers "what were we deciding from".
      - `state` 'published' | 'no_rules_held' | 'under_review', required: `published` — we hold live rules and evaluated them. `no_rules_held` — we hold none, so this answer rests on the legacy annotation. `under_review` — we hold none and the institution is queued for a human look. Deliberately coarse: our internal pipeline states are not part of this contract.
      - `rules_held` integer, required: Live rules actually held and evaluated for this institution in this response. This count is coverage telemetry, not proof that every possible membership path is known.
      - `last_confirmed_at` string, date-time, nullable, required: Most recent confirmation across those rules. Null when nothing was held, and null when the rules carry no confirmation timestamp — an unconfirmed rule does not borrow the credibility of a confirmed one.
      - `fom_affirmed` boolean, nullable, required: Reserved coverage telemetry. False for current graph rules because no typed closed-world attestation exists; null when there were no rules to assess. Geographic source agreement alone never turns this true or authorizes a negative.
    - `missing_facts` EligibilityMissingFact[]: The question(s) to ask this person next about THIS institution, ranked fewest-facts-most-doors and capped at 3. Present only on `unknown` and `possibly_eligible` entries, and only when a question would help.
      - `cu_id` string, required: The institution this question would unlock.
      - `condition_id` integer, required: The membership condition that evaluated unknown. Stable across calls for a given publication.
      - `kind` string, required: The evaluator condition kind, verbatim (`geo_residence`, `employee_of`, `military_affiliation`, …), so a caller can group questions without parsing prose.
      - `question` string, required: The question to ask the person, in the second person, answerable in one breath. Composed in code from a fixed template; never model prose.
      - `fact_field` string[], required: The request field(s) an answer populates. Always at least one, and always a field this endpoint accepts — merge the answers into the same request body and call again.
      - `why` string, required: Why we are asking, in one deterministic sentence naming the door.
      - `evidence_quote` string: Verbatim from the INSTITUTION's own page, via the rule's evidence quote, truncated on a word boundary at 240 characters. Absent when the backing rule carries no quote. This is never the person's own words.
      - `resolves_reason` 'no_rules' | 'geo_inputs_missing' | 'org_inputs_missing' | 'inputs_missing' | 'low_confidence_rules' | 'incomplete_fom' | 'fom_completeness_unaffirmed' | 'unmodeled_condition', required: The `unknown_reason` an answer to this question would resolve.
  - `unknown` PersonEligibilityMatch[], required: Credit unions we could not decide FOR THIS PERSON. Two different facts live here and `unknown_reason` on each entry tells them apart: we hold no rules for the institution (`no_rules`), or we hold rules and the supplied facts could not decide them (`geo_inputs_missing`, `org_inputs_missing`, `inputs_missing` — one more question would resolve it), or we hold rules that deterministically failed but we refuse to publish that as a no (`incomplete_fom`, `fom_completeness_unaffirmed`, `low_confidence_rules`, `unmodeled_condition`). Unknown NEVER means ineligible.
    - `credit_union_id` string, required: Canonical identifier of the matched credit union.
    - `name` string, required: Name of the matched credit union.
    - `state` string, required: Two-letter US state code the credit union is chartered in.
    - `status` 'eligible' | 'conditionally_eligible' | 'possibly_eligible' | 'unknown', required: The bucket this entry sits in. `ineligible` never appears; evaluated non-matches are returned as `unknown`.
    - `confidence` number, required: Confidence in this match, 0–1, carried from the extracted eligibility rule.
    - `reasons` string[], required: Deterministic sentences explaining the verdict. Never model prose.
    - `paths` EligibilityGraphPath[]: Structured graph paths. Absent on legacy-annotation fallbacks.
      - `rule_id` integer, required: Id of the published rule this path came from.
      - `kind` string, required: Rule family (e.g. community_geo, employer, association, military, school, worship, family).
      - `reason` string, required: Deterministic sentence describing the path.
      - `conditions_matched` string[], required: Canonical condition ids the supplied facts satisfied.
      - `conditions_unknown` string[]: Condition ids left unresolved — what to supply to decide this path.
      - `status` 'eligible' | 'conditionally_eligible' | 'possibly_eligible' | 'ineligible' | 'unknown': Set on non-positive paths so callers can explain the gap. `ineligible` is reserved for compatibility; current customer-safe projections normalize unattested path negatives to `unknown`.
      - `conditional_on` object: The one further step that would make this path apply.
        - `type` 'join_association' | 'family_link' | 'deposit_or_donation': The kind of action the borrower must take to satisfy this path.
        - `org_id` string: Identifier of the association, employer, or organization the borrower must join or link to.
        - `cost_usd` number: Up-front cost of the step, when published.
        - `join_url` string, uri: Page where the borrower can complete the joining step.
    - `join_cost_usd` number: Up-front cost of the cheapest conditional step, when published.
    - `join_url` string, uri: Where to take the conditional step (e.g. the association's join page).
    - `engine` 'graph' | 'legacy', required: `graph` when the verdict came from published eligibility-graph rules, `legacy` when it came from the annotation fallback for a credit union with no rules yet.
    - `publication` EligibilityPublicationRef: Exact immutable active eligibility generation evaluated for this response.
      - `publication_id` integer, required: Database identifier of the active publication.
      - `ruleset_sha256` string, required: SHA-256 digest of the complete ordered rule set.
      - `activated_at` string, date-time, required: When this generation became serving authority.
      - `proposal_coverage` EligibilityProposalCoverage: How many model-proposed claims from the source page survived evidence verification. This is not an attestation that every real-world field-of-membership path is known.
        - `scope` 'source_page_model_proposals', required: Metric scope, explicitly limited to model proposals extracted from reviewed source pages.
        - `proposed_claims` integer, required: Claims proposed by extraction from the reviewed source pages.
        - `verified_claims` integer, required: Proposed claims that passed evidence verification.
        - `published_rules` integer, required: Verified graph rules published from the candidate.
        - `withheld_claims` integer, required: Proposed claims withheld because verification did not pass.
        - `proposal_survival` number, nullable, required: Verified claims divided by proposed claims, or null when no claims were proposed.
        - `withheld_reasons` object, required: Withheld claim counts grouped by machine-readable verification reason.
    - `unknown_reason` 'no_rules' | 'geo_inputs_missing' | 'org_inputs_missing' | 'inputs_missing' | 'low_confidence_rules' | 'incomplete_fom' | 'fom_completeness_unaffirmed' | 'unmodeled_condition': WHY this entry is not a positive match. Present only on non-positive statuses. This is what makes a flat `unknown` array actionable: `geo_inputs_missing` / `org_inputs_missing` / `inputs_missing` mean the CALLER can resolve it by supplying one more fact; `no_rules` means we hold nothing for this institution; `incomplete_fom`, `fom_completeness_unaffirmed`, `low_confidence_rules` and `unmodeled_condition` mean we hold rules but will not turn a failure against them into a no. Legacy-annotation entries (`engine: "legacy"`) report their own reason mapped onto this same vocabulary.
    - `coverage` object, required: What we HOLD about this institution, as distinct from what we concluded about the person. `status` answers "what did we decide"; this answers "what were we deciding from".
      - `state` 'published' | 'no_rules_held' | 'under_review', required: `published` — we hold live rules and evaluated them. `no_rules_held` — we hold none, so this answer rests on the legacy annotation. `under_review` — we hold none and the institution is queued for a human look. Deliberately coarse: our internal pipeline states are not part of this contract.
      - `rules_held` integer, required: Live rules actually held and evaluated for this institution in this response. This count is coverage telemetry, not proof that every possible membership path is known.
      - `last_confirmed_at` string, date-time, nullable, required: Most recent confirmation across those rules. Null when nothing was held, and null when the rules carry no confirmation timestamp — an unconfirmed rule does not borrow the credibility of a confirmed one.
      - `fom_affirmed` boolean, nullable, required: Reserved coverage telemetry. False for current graph rules because no typed closed-world attestation exists; null when there were no rules to assess. Geographic source agreement alone never turns this true or authorizes a negative.
    - `missing_facts` EligibilityMissingFact[]: The question(s) to ask this person next about THIS institution, ranked fewest-facts-most-doors and capped at 3. Present only on `unknown` and `possibly_eligible` entries, and only when a question would help.
      - `cu_id` string, required: The institution this question would unlock.
      - `condition_id` integer, required: The membership condition that evaluated unknown. Stable across calls for a given publication.
      - `kind` string, required: The evaluator condition kind, verbatim (`geo_residence`, `employee_of`, `military_affiliation`, …), so a caller can group questions without parsing prose.
      - `question` string, required: The question to ask the person, in the second person, answerable in one breath. Composed in code from a fixed template; never model prose.
      - `fact_field` string[], required: The request field(s) an answer populates. Always at least one, and always a field this endpoint accepts — merge the answers into the same request body and call again.
      - `why` string, required: Why we are asking, in one deterministic sentence naming the door.
      - `evidence_quote` string: Verbatim from the INSTITUTION's own page, via the rule's evidence quote, truncated on a word boundary at 240 characters. Absent when the backing rule carries no quote. This is never the person's own words.
      - `resolves_reason` 'no_rules' | 'geo_inputs_missing' | 'org_inputs_missing' | 'inputs_missing' | 'low_confidence_rules' | 'incomplete_fom' | 'fom_completeness_unaffirmed' | 'unmodeled_condition', required: The `unknown_reason` an answer to this question would resolve.
  - `resolved` object, required: What the supplied facts resolved to — the audit trail for the narrowing stage.
    - `orgs` object[]: Organization resolutions for employer/school/worship/associations, each with what was matched and how confidently.
    - `geo_keys` string[]: Canonical geography keys derived from the location inputs.
  - `limit` integer, required: Per-bucket cap actually applied.
  - `engine` 'graph+legacy_fallback', required: Which engine produced the verdicts. Present when at least one result carried a graph verdict; the legacy annotation engine always runs and produces every legacy field.
  - `disclosure` string, required: Statement that must be shown to a consumer alongside this result.
  - `next_questions` EligibilityNextQuestion[]: Questions ranked across every institution evaluated, by how many each unlocks. Ask the top one to three, merge the answers into the same request body, and call again. Stop when this is empty, when the top entry's `unlocks_cu_count` drops below 2, or after three turns. Absent when there is nothing to ask.
    - `question` string, required: The generic phrasing of the question. "Do you live in Tulsa County?" and "Do you live in Wagoner County?" are one question to a person: "What's your home ZIP code?"
    - `fact_field` string[], required: The request field(s) an answer populates.
    - `kind` string, required: The evaluator condition kind these questions share.
    - `scope` 'institution_membership', required: Questions clarify membership at named institutions; institutions are not guaranteed to publish the requested product.
    - `effect` 'may_clarify', required: An answer may clarify the cited membership paths; it does not guarantee eligibility or product availability.
    - `unlocks_cu_count` integer, required: How many evaluated institutions this one question would move out of `unknown`. The true total, even when the id list below is capped.
    - `unlocks_cu_ids` string[], required: Institutions this question unlocks, capped at 20. `unlocks_cu_count` remains the true total.
    - `example_evidence_quote` string: One institution's own words behind this question, as an illustration.
  - `unanswerable` EligibilityUnanswerable[]: What no question can fix, and why. Do not ask the user about these. Absent when everything unresolved is askable.
    - `reason` 'no_rules' | 'low_confidence_rules' | 'unresolved_organization' | 'unmodeled_condition' | 'radius_centre_unidentified' | 'radius_centre_ungeocoded' | 'road_miles_unproven' | 'fom_completeness_unaffirmed', required: Why no question is emitted. `no_rules` and `low_confidence_rules` are gaps on our side; `unresolved_organization` means the page names an entity we could not resolve; `unmodeled_condition` is a geography we can state but cannot decide (a census tract, a school district, a sub-area of a county the page never draws); the two `radius_centre_*` values are a circle whose centre the page never identifies or we have not geocoded; `road_miles_unproven` is a driving-distance radius a straight line cannot settle; `fom_completeness_unaffirmed` is our refusal to turn a failure against a partial rule set into a no.
    - `cu_count` integer, required: How many evaluated institutions fall under this reason.
    - `condition_kinds` string[]: The evaluator condition kinds behind this entry, when any are known.
    - `note` string, required: A deterministic sentence a UI can show verbatim.
  - `missing_facts_truncated` boolean: True when a size cap dropped a question that could otherwise have been asked (3 per institution, 5 in the rollup, 20 unlock ids per rollup entry). The same honesty convention as `candidate_set_truncated`.

## Other responses

- `400`: The request was malformed — a missing required field, an unparseable body, or a parameter outside its allowed range. The `error` field names what was wrong; retrying the identical request will fail identically.
- `401`: No API key, or a key that is unknown or disabled. Send it in the `X-API-Key` header. Free keys are issued instantly by POST /keys with an email address. The response carries `WWW-Authenticate` pointing at this host's protected-resource metadata, and the `RateLimit-*` headers describing the Free-tier budget a key issued now would carry (scope `anonymous`) — a rejected request spends none of it, so `Remaining` equals `Limit`.
- `402`: The key is valid, but RateAPI Routes is available only on Enterprise. The response includes `required_plan`, `upgrade_url`, and a `Link` header with `rel="upgrade"`; the rejected request does not consume quota.
- `429`: Quota exhausted for the current window. `Retry-After` gives the seconds to wait; `upgradeInfo` explains how to lift the cap. Retrying before the reset will fail again and still counts as a request.

---

[API](https://skmtc.dev/rateapi/apis/rate-api.md) · [All operations](https://skmtc.dev/rateapi/apis/rate-api/llms.txt) · [OpenAPI document](https://skmtc.dev/rateapi/apis/rate-api/revisions/06a6db76995f?raw)
