---
title: "Batch membership-eligibility verdicts for a list of credit unions"
method: POST
path: "/v1/eligibility/check"
tags: ["Reference Data"]
---

# Batch membership-eligibility verdicts for a list of credit unions

`POST /v1/eligibility/check`

## Request

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

Answers "can THIS user join THESE credit unions?" for up to 50 credit unions against one user profile, in a single call. Every field of `user` is optional — the more you supply, the sharper the verdicts; a ZIP resolves to candidate counties server-side. Home geography accepts either the original `state`/`zip`/`county` names or the `home_state`/`home_zip`/`home_county` aliases returned in `missing_facts`; equivalent duplicate forms are accepted, but conflicting values are rejected.

Each verdict uses the shared eligibility `status` vocabulary also found on offers and rate surfaces. Exact graph-authority parity is guaranteed only between surfaces that evaluate the same active publication for the same person; `/v1/league-table` and raw `/v1/rates` annotations remain flat-record compatibility surfaces today. Accuracy-first: a credit union with no membership record, or whose held rules do not match, verdicts as `unknown` with an actionable `unknown_reason` — never as ineligible. Reserved negative enum literals remain for v1 wire compatibility but are not emitted from current open-world web-derived records.

**Versioned graph verdict**: when the credit union has eligibility-graph rules, the verdict also carries a `graph` object (five-value status, confidence, structured paths, and a deterministic `explain` sentence). For a CU with an atomically activated publication, the graph object names the exact publication and the v1 top-level status is projected from that same verdict; `engine` is `eligibility_publication_v1`. CUs not yet migrated retain additive compatibility output under `legacy+graph_shadow`.

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

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

## Request body

- object
  - `credit_union_ids` string[], required: Composite credit-union ids (`name|city|STATE`) to verdict. Capped at 50 per call; duplicates are de-duplicated.
  - `user` object: The user's context. Every field is optional; supplying more sharpens the verdicts. Absent fields never produce a mismatch. For home geography, `home_state`/`home_zip`/`home_county` are merge-and-retry aliases for `state`/`zip`/`county`; conflicting duplicate forms are rejected.
    - `state` string: 2-letter US state code.
    - `zip` string: US 5-digit ZIP or ZIP+4. Resolved to candidate counties in-memory; when state is also supplied, the ZIP crosswalk must agree with it.
    - `county` string: Explicit county name. Wins over ZIP-derived counties.
    - `home_state` string: Alias for `state`, accepted so a returned `missing_facts.fact_field` can be merged into this same request. Conflicting duplicate forms are rejected.
    - `home_zip` string: Alias for `zip`, accepted so a returned `missing_facts.fact_field` can be merged into this same request. Conflicting duplicate forms are rejected.
    - `home_county` string: Alias for `county`, accepted so a returned `missing_facts.fact_field` can be merged into this same request. Conflicting duplicate forms are rejected.
    - `home_city` string: Census-recognized home city. Requires `state`/`home_state` or a recognized `zip`/`home_zip` that uniquely identifies one state; unresolved labels are rejected rather than guessed.
    - `work_state` string: 2-letter state where the person works. Evaluated independently from home geography for live-or-work fields of membership.
    - `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_county` string: Explicit work county. Wins over work-ZIP-derived counties.
    - `work_city` string: Census-recognized work city. Requires `work_state` or a recognized `work_zip` that uniquely identifies one state.
    - `payroll_state` string: 2-letter state from which the employer issues the person's payroll. This is distinct from the worksite 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_county` string: County from which the employer issues payroll. This is distinct from the worksite county.
    - `payroll_city` string: Census-recognized payroll city. Requires `payroll_state` or a recognized `payroll_zip` that uniquely identifies one state.
    - `employer` string: Employer name, matched against the credit union's employer-group (SEG) list. Positive-signal-only: a non-match never asserts ineligibility.
    - `employer_org_ids` string[]: Caller-confirmed canonical employer ids returned by eligibility entity discovery. IDs are revalidated against current active publications before evaluation.
    - `occupation` string: Occupation, matched against occupation fields of membership.
    - `military_status` 'active_duty' | 'veteran' | 'reservist' | 'dod_civilian' | 'military_family': Military affiliation, matched against military fields of membership (yields `eligible_via_military`).
    - `school` string: School, college, or university. Supply `school_relationship` as well before a school-affinity path can produce a positive verdict.
    - `school_org_ids` string[]: Caller-confirmed canonical school ids; current active-publication validation is mandatory.
    - `school_relationship` 'student' | 'alumni' | 'employee': How the person relates to the named school; required to affirm typed student, alumni, or school-employee graph paths.
    - `worship` string: Place of worship, matched against worship-org affinity lists (yields `likely_eligible`).
    - `worship_org_ids` string[]: Caller-confirmed canonical place-of-worship ids; current active-publication validation is mandatory.
    - `association_org_ids` string[]: Canonical ids for associations the person already belongs to. Discovery alone never confirms membership.
    - `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.
    - `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.

## Response `200`

One verdict per requested credit union, in request order.

- object
  - `count` integer, required: Number of verdicts returned (de-duplicated id count).
  - `user` object: Echo of the interpreted user context. Present only when a state or counties were resolved.
    - `state` string: Two-letter US state code.
    - `resolved_counties` string[]: Counties resolved from `zip` or `county`.
    - `work_state` string: Two-letter work state supplied by the caller.
    - `resolved_work_counties` string[]: Counties resolved from `work_zip` or `work_county`.
  - `results` EligibilityCheckVerdict[], required: One verdict per credit union checked.
    - `credit_union_id` string, required: Canonical identifier of the credit union this verdict is about.
    - `status` 'open_to_anyone' | 'likely_eligible' | 'eligible_via_employer' | 'eligible_via_military' | 'geo_mismatch' | 'unknown', required: The same status vocabulary used to annotate ranked offers on /v1/decisions, /v1/league-table, and /v1/rates. `geo_mismatch` is retained for compatibility but current responses normalize unattested geographic non-matches to `unknown`.
    - `matched_rule` object: Which concrete rule produced the status. Purely explanatory; absent on `geo_mismatch` and `unknown`.
      - `type` 'county' | 'state' | 'employer' | 'occupation' | 'association_open' | 'military' | 'school' | 'worship': Which category of membership rule the borrower satisfied.
      - `detail` string: The specific value that matched — the county, employer, association, or school named by the rule.
    - `unknown_reason` 'charter_type_not_geo_matchable' | 'no_membership_record' | 'low_confidence_record' | 'geo_inputs_missing' | 'fom_completeness_unaffirmed' | 'publication_authority_unavailable': Present only when `status` is `unknown`. Names the missing input, incomplete evidence, or unavailable authoritative publication that prevented a positive verdict.
    - `join_path` string: Reserved legacy field; current person-specific verdicts omit unbound flat join steps. Read the full eligibility profile or a graph `conditional_on` path instead.
    - `requirements_summary` string: Reserved legacy field; current person-specific verdicts omit whole-record summaries because they are not bound to the matched path.
    - `membership_fee_usd` number: Reserved legacy field; current person-specific verdicts omit unbound global fees.
    - `min_deposit_usd` number: Reserved legacy field; current person-specific verdicts omit unbound global deposits.
    - `join_cost_usd` number: Reserved legacy field; current person-specific verdicts expose actionable cost only on a graph conditionally-eligible path.
    - `eligibility_type` string, required: Legacy charter shape for flat annotations. When an active graph publication is authoritative, this is the neutral value `graph` rather than a possibly stale flat path label.
    - `confidence` number, required: Deprecated alias of `record_confidence` (same value, kept for compatibility). On graph-authoritative results both mirror graph verdict confidence; otherwise they reflect flat-record confidence.
    - `record_confidence` number, required: Flat-record confidence on legacy results. On graph-authoritative results this instead mirrors the exact graph verdict confidence so stale flat confidence cannot contradict the publication.
    - `evidence_url` string, uri: The credit union's own membership page — the source of every claim.
    - `details` string: Relative path to the full record (GET /v1/credit-unions/{id}/eligibility). Absent when no record exists.
    - `graph` EligibilityGraphVerdict: Eligibility-graph verdict. When `publication` is present, this exact immutable generation is authoritative and the v1 top-level status is its compatibility projection. Without `publication`, the object is additive compatibility output. `ineligible` remains in the enum for wire compatibility but is not emitted from current web-derived publications without a future typed closed-world attestation.
      - `status` 'eligible' | 'conditionally_eligible' | 'possibly_eligible' | 'ineligible' | 'unknown', required: Five-value graph vocabulary retained for compatibility. Current open-world web-derived rules emit `unknown`, never `ineligible`, when no held path matches. `unknown` NEVER means no.
      - `confidence` number, required: Confidence in this verdict, 0–1. Lower values mean the underlying rule was extracted with less certainty; an unknown is never reported as ineligible.
      - `paths` EligibilityGraphPath[], required: Only paths at the verdict's winning status, with what matched or remained unresolved. Lower-ranked alternative OR paths are omitted.
        - `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.
      - `unknown_reason` 'no_rules' | 'geo_inputs_missing' | 'org_inputs_missing' | 'inputs_missing' | 'low_confidence_rules' | 'incomplete_fom' | 'fom_completeness_unaffirmed' | 'unmodeled_condition': Present when the verdict is not positive. Machine-readable so a dead end becomes a next step.
      - `explain` string, required: Deterministic one-sentence explanation of the verdict. Never model prose.
      - `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.
    - `missing_facts` EligibilityMissingFact[]: The question(s) to ask about this named institution. A check that returns unknown for every id AND a question for each is a successful call, not a failure.
      - `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.
  - `engine` 'legacy+graph_shadow' | 'eligibility_publication_v1': Present when at least one verdict carries a graph result. `eligibility_publication_v1` means an exact active publication was authoritative; `legacy+graph_shadow` is compatibility output for CUs without a publication pointer.
  - `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)
