---
title: "Turn a person's own words into person-fact fields, for confirmation"
method: POST
path: "/v1/eligibility/facts"
tags: ["Reference Data"]
---

# Turn a person's own words into person-fact fields, for confirmation

`POST /v1/eligibility/facts`

## Request

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

**This is an extractor, not a decision.** Prose in, the structured facts `/v1/eligibility/search` accepts out, each with the exact words we read it from and a flag saying whether a human should confirm it. The response contains no status, no bucket, no credit union and no URL, and it cannot be made to contain one: there is no property in the response schema in which a verdict could be returned.

The intended flow is two calls. Send the person's sentence here; show them what we read; then send `person_search_body` - plus whatever they confirmed - to `POST /v1/eligibility/search`, where the deterministic eligibility graph decides, exactly as it does today.

**How the model is contained.** Its only output channel is a closed enum of field names generated from the search endpoint's own schema, a raw string this API coerces itself, and an evidence quote that must appear verbatim in your `text`. A quote we cannot find in your text means the fact is dropped. The worst case for hostile prose in `text` is therefore a WRONG FACT, flagged for confirmation - never an assertion about a credit union.

**Privacy.** `text` is never stored: not in usage logs, not in object storage, not in an error payload. Logs carry a truncated hash of it, its length, and the NAMES of the fields extracted - never values. Responses are `Cache-Control: no-store`.

**Cost.** Billed as 3 API units per call, because one model call is roughly three times the marginal cost of a deterministic lookup. This is an Enterprise Routes operation. It is separately capped at 20 calls per key per rolling hour to bound inference spend against a looping agent; that cap is independent of your plan quota and returns `429` with `scope: "endpoint_burst"`.

**Failure is always silent about eligibility.** If extraction is unavailable you get `503` with `facts: []`. Ask the person for the fields directly; never read a failure as a negative answer.

## Request body

- object
  - `text` string, required: The person's own words, 1-2000 characters after trimming. Send it VERBATIM - do not paraphrase or pre-structure it, because evidence spans are anchored in the original sentence. Over the limit returns 400 rather than truncating: a cut final clause silently deletes a membership door.
  - `known_facts` object: Optional. Facts you already hold, in the same shape as the /v1/eligibility/search request body. Echoed with source="supplied" and never overwritten; an extracted value that contradicts one is returned alongside it with `conflict_with` set.
  - `locale` 'en-US': Optional. Only "en-US" is supported at launch.

## Response `200`

Facts read from the text, for confirmation.

- object: An extraction, for confirmation. Contains no eligibility verdict, no bucket, no credit union and no URL - by construction: there is no property here in which one could be returned.
  - `facts` object[]: Every fact we could read, plus every `known_facts` value echoed back. Facts whose evidence quote does not occur verbatim in your `text` are dropped before this array is built.
    - `field` string: A field name accepted by POST /v1/eligibility/search. The extractor is constrained to a closed enum generated from that endpoint's schema, so it can never name a field this API does not read.
    - `value` unknown
    - `confidence` number: The MODEL'S OWN self-reported confidence, 0-1. It is uncalibrated and it gates only whether the fact autofills `person_search_body`. Do not present it as a probability; prefer showing `evidence.quote`, which is the real evidence.
    - `needs_confirmation` boolean: True when a human must confirm this before it is relied on: low confidence, a value we could not coerce, a category rather than a name, a conflict with `known_facts`, or one of the door fields (family_relation, relative_*, applicant_kind, asserted_org_qualifiers) where a wrong reading changes the answer.
    - `evidence` object, nullable: Where in YOUR `text` we read this fact. `start`/`end` are character offsets into the text exactly as you sent it, computed by this API — the model returns only the string. `null` only when source is "supplied".
      - `quote` string: The person's own words, copied verbatim out of `text`.
      - `start` integer: Character offset of the first character of the quote.
      - `end` integer: Character offset one past the last character of the quote.
    - `source` 'extracted' | 'supplied': "supplied" is a fact you sent in `known_facts`, echoed back untouched. "extracted" was read from `text` and always carries an evidence span.
    - `note` string: A plain-language question or explanation for the person, present when something needs resolving.
    - `conflict_with` string: Set when this extracted value contradicts a `known_facts` value for the same field. Both are returned; no winner is picked.
  - `person_search_body` object: A ready-to-POST request body for /v1/eligibility/search, containing ONLY facts with needs_confirmation=false and confidence at or above 0.8, plus everything you supplied. Facts awaiting confirmation are deliberately excluded; add them once the person says yes.
  - `unparsed_spans` object[]: Phrases we recognized as meaningful but did not map to a field, including anything stated in the past tense, hypothetically, or as a denial. Ask about these rather than guessing.
    - `quote` string: The phrase, verbatim from `text`.
    - `start` integer: Character offset of the first character.
    - `end` integer: Character offset one past the last character.
    - `note` string: Why it was not turned into a fact, and what to ask.
  - `confirmation_required` boolean: True when any fact needs confirmation or any unparsed span exists. When true, show the facts to the person before calling /v1/eligibility/search.
  - `confidence_is_model_self_reported` boolean: Always true. A standing warning that `confidence` is the extraction model's own uncalibrated number rather than a measured accuracy, so a consumer UI can hide the figure and show the evidence quote instead.
  - `model` string: Extraction model identifier.
  - `extractor_version` string: Version of this extractor's prompt and post-processing.
  - `note` string: Standing disclosure: these are our reading of what the person said, not a membership decision.
  - `billed_units` integer: API units billed for this call: 3 for a live extraction, 0 for a sandbox fixture.
  - `sandbox` boolean: Present and true only for a sandbox (`rk_test_…`) key, which receives a frozen fixture extraction: no model call, no inference spend, and `billed_units: 0`. The shape is otherwise identical to a live extraction.

## Other responses

- `400`: `text_required`, `text_too_long`, `unsupported_locale`, or `invalid_known_facts`.
- `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`: Either the plan rate limit or this endpoint's per-key hourly burst cap (`scope: "endpoint_burst"`). Carries `Retry-After`.
- `503`: Extraction unavailable - model timeout, upstream failure, spend cap, or the extractor key not being configured. Never an eligibility signal.

---

[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)
