---
title: "List and filter raw rate rows"
method: POST
path: "/v1/rates"
tags: ["Data & Analytics"]
---

# List and filter raw rate rows

`POST /v1/rates`

## Request

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

Lists raw, freshness-filtered rate rows (the core data primitive) joined to credit-union metadata. Filter by product, state, lender, term, loan program, and APR band; sort and paginate with limit/offset. Returns the matched rows plus pagination totals and the latest snapshot timestamp. This route is the single source of truth; the MCP `list_rates` tool and the consumer-app /rates page call it.

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

## Request body

- object
  - `product_type` 'mortgage' | 'auto_loan' | 'rv' | 'boat' | 'motorcycle' | 'powersports' | 'heloc' | 'personal_loan' | 'student_loan' | 'equipment_loan' | 'credit_card': Filter to a single product family. Omit for all products.
  - `state` string: 2-letter US state code. Omit for all states.
  - `lender` string: Case-insensitive substring match on the credit-union name.
  - `term_months` integer: Exact term in months (matches auto/personal/savings term columns).
  - `loan_program` string: Exact loan-program match (e.g., conventional, jumbo, FHA, VA, USDA).
  - `min_apr` number: Lower bound on APR (percent).
  - `max_apr` number: Upper bound on APR (percent). Must be >= min_apr.
  - `sort` 'apr_asc' | 'apr_desc' | 'rate_asc' | 'rate_desc' | 'updated_desc': Sort order. Default apr_asc.
  - `limit` integer: Max rows to return (clamped to 1..500). Default 50.
  - `offset` integer: Pagination offset (>= 0). Default 0.
  - `audience` 'business' | 'consumer': Audience scope: 'business' returns business-audience offers only; 'consumer' excludes them. Omit for both.
  - `occupancy` 'investor' | 'second_home' | 'primary': Real-estate occupancy filter (investor/DSCR, second home, primary residence).

## Response `200`

Matched raw rate rows with pagination.

- object
  - `filters` object: The filters the query was actually run with, including any defaults that were applied.
    - `product_type` string, nullable: Loan or deposit product this applies to.
    - `state` string, nullable: Two-letter US state code.
    - `lender` string, nullable: Name of the credit union offering this rate.
    - `term_months` number, nullable: Loan term in months.
    - `loan_program` string, nullable: Program narrowing the product — for example new or used for an auto loan, or the fixed term for a mortgage.
    - `min_apr` number, nullable: Lowest APR accepted, or null when no floor was applied.
    - `max_apr` number, nullable: Highest APR accepted, or null when no ceiling was applied.
    - `sort` string: The ordering the rows were returned in.
  - `pagination` object: Where this page sits in the full result set.
    - `limit` integer: Maximum number of records this response may contain.
    - `offset` integer: How many records were skipped before this page.
    - `returned` integer: Rows in this page.
    - `total` integer: Rows matching the query in full, before paging.
    - `has_more` boolean: True when another page follows. Advance `offset` by `limit` to fetch it.
  - `rates` object[]: The matching rate rows.
    - `lender` string, nullable: Name of the credit union offering this rate.
    - `state` string, nullable: Two-letter US state code.
    - `product_type` string, nullable: Loan or deposit product this applies to.
    - `product_name` string, nullable: The lender's own name for the product, verbatim from its rate page.
    - `display_name` string, nullable: Readable product label; product_name is the raw source name
    - `product_key` string, nullable: Opaque, stable grouping id derived from the product's fact columns (category, term, program, purpose, amount/LTV/credit band, vehicle type) and never from the product name. Rows sharing a non-null product_key have matching KNOWN facts — but a key built from sparse facts is correspondingly coarse, so it may group products we cannot yet tell apart (e.g. two auto loans whose terms were never extracted). Treat it as a strong dedupe hint, not a guarantee of identity. Null until the row is next materialized; null means ungrouped, never a group of its own.
    - `loan_program` string, nullable: Program narrowing the product — for example new or used for an auto loan, or the fixed term for a mortgage.
    - `rate` number, nullable: Note rate as an annual percentage, excluding fees.
    - `apr` number, nullable: The lender's published APR. **null = the lender did not publish an APR** for this product — the API will not report the note rate in its place. Legacy rows scraped before the flag existed still report their stored value; each heals on its credit union's next scrape.
    - `points` number, nullable: Discount points priced into the rate.
    - `term_months` number, nullable: Loan term in months.
    - `vehicle_condition` string, nullable: Whether an auto loan prices a new, used, or certified pre-owned vehicle. Null for any non-auto product.
    - `as_of` string, nullable: When the underlying rates were published.
    - `eligibility` Eligibility: Membership-eligibility guidance for this credit union (design doc §4). Present only when the CU has a cu_eligibility record; absent otherwise. Guidance based on public charter data and institution websites — final determination is made by the institution. Current open-world records never assert ineligibility.
      - `status` 'open_to_anyone' | 'likely_eligible' | 'eligible_via_employer' | 'eligible_via_military' | 'geo_mismatch' | 'unknown', required: Eligibility assessment. `open_to_anyone`: a documented open path matched. `likely_eligible`: positive geo, school, worship, or occupation evidence matched. `eligible_via_employer`: the supplied employer matches a verified SEG list. `eligible_via_military`: the supplied military affiliation matches. `unknown`: cannot be determined from available data, including geographic non-matches. `geo_mismatch` is retained only for wire compatibility and is not emitted from current unattested records.
      - `join_path` string: Reserved legacy field. Current person-specific offer annotations omit unbound flat join paths; use graph-backed eligibility_explanation.conditions instead.
      - `eligibility_type` string, required: Legacy field-of-membership type on flat annotations. Graph-authoritative offer annotations use the neutral value `graph` rather than a stale flat path label.
      - `confidence` number, required: Flat-record confidence for legacy annotations; graph verdict confidence when an active publication is authoritative.
      - `matched_rule` object: The concrete rule that produced a positive `status`. Explanatory only — never present on `geo_mismatch` or `unknown` (those carry no positive rule).
        - `type` 'county' | 'state' | 'employer' | 'occupation' | 'military' | 'school' | 'worship' | 'association_open', required: Exact positive rule category: geographic county/state, employer, occupation, military, school, worship, or an open association path.
        - `detail` string: Human-readable specifics of the match (e.g. the matched county name or employer entry).
  - `as_of` string, nullable: When the underlying rates were published.

## 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`.
- `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)
