---
title: "Rate-lock / float-down timing decision"
method: POST
path: "/v1/rate-lock"
tags: ["Data & Analytics"]
---

# Rate-lock / float-down timing decision

`POST /v1/rate-lock`

## Request

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

Lock-window-aware lock-vs-float-down recommendation for a deal already in the pipeline. Given a known lock-expiration date, the currently locked rate, and the float-down cost, returns a defensible hold-lock vs exercise-float-down call a loan officer can document — combining recent market movement (1w/2w/4w window) with payment + break-even math on this loan. Builds on the same recent-movement signal as `/v1/buy-timing` but framed around the lock window/expiration. A documentable sensitivity scenario, NOT a rate forecast. This route is the single source of truth; the MCP `assess_rate_lock` tool and the consumer app call it.

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

## Request body

- object
  - `product_type` 'mortgage' | 'mortgage_arm' | 'auto_loan' | 'heloc' | 'personal_loan' | 'student_loan' | 'credit_card', required: Use `mortgage_arm` for an adjustable-rate mortgage (5/1, 5/5, 7/1, 10/1, ...). ARMs are a separate market from fixed-rate mortgages: an ARM's introductory rate sits structurally below a comparable fixed rate, so a blended list would rank every ARM on top and present it as cheaper — true only until it resets. All initial fixed periods share one `mortgage_arm` bucket. Within `mortgage`, `term_months` selects 15- vs 30-year; it has no meaning for `mortgage_arm`.
  - `state` string, required: 2-letter US state code
  - `loan_amount` number, required: Loan amount in dollars — drives payment + break-even math
  - `term_months` integer: Loan term in months. Defaults to 360.
  - `locked_rate` number, required: The rate already locked, in percent.
  - `lock_expiration_date` string, required: ISO date (YYYY-MM-DD) the lock expires.
  - `float_down_cost` number: Dollar cost to exercise the float-down. Defaults to 0.
  - `float_down_rate` number: Optional explicit floated rate (percent). If absent, the best live market rate is used.
  - `float_down_bps` number: Optional improvement in bps if float_down_rate is omitted and no live offer is available.
  - `closing_costs` number: Optional closing costs in dollars (cost framing). Defaults to 0.
  - `since` '1w' | '2w' | '4w': Movement window. Defaults to 4w.

## Response `200`

Lock-vs-float assessment. Degrades gracefully: when the lock is expired the recommendation is `extend_or_relock`; when rate history is thin, `movement` fields are null, `recommendation` is `insufficient_data`, `confidence` is `low`, and a `note` is included.

- object
  - `product_type` string: Loan or deposit product this applies to.
  - `state` string: Two-letter US state code.
  - `window` string: How far back the rate movement was measured, as the `since` value used.
  - `lock` object: The lock the borrower currently holds.
    - `locked_rate` number: The rate the borrower locked, as an annual percentage.
    - `lock_expiration_date` string: When the lock expires.
    - `days_remaining` integer: Days left before the lock expires.
    - `expired` boolean: True when the lock has already expired.
  - `movement` object: How the market moved across the window.
    - `now` object, nullable: Market summary as of today, or null when there is too little data.
    - `then` object, nullable: Market summary at the start of the window, or null when no comparable snapshot survives in retention.
    - `median_delta_pct` number, nullable: Change in median APR across the window, in percentage points. Negative means rates eased.
    - `direction` 'down' | 'up' | 'flat': Which way the value moved over the window.
  - `market_now` object: The best offer available today.
    - `best_rate` number, nullable: Lowest rate available today, or null when no offer was found.
    - `best_lender` string, nullable: Credit union offering that rate, or null when no offer was found.
    - `monthly_payment` number, nullable: Monthly principal-and-interest payment in US dollars.
  - `float_down` object: What exercising a float-down to today’s market would be worth.
    - `projected_rate` number, nullable: The rate a float-down would move the borrower to, or null when no offer was found.
    - `available_improvement_bps` number: How much better that rate is than the locked rate, in basis points.
    - `monthly_savings` number: Monthly payment reduction in US dollars.
    - `lifetime_savings` number: Total payment reduction over the full term, in US dollars.
    - `float_down_cost` number: The float-down fee supplied by the caller, in US dollars. Defaults to 0 when none was given.
    - `net_benefit` number: Lifetime savings minus the float-down cost, in US dollars.
    - `break_even_months` integer, nullable: Months until accumulated savings cover the upfront cost.
    - `worth_it` boolean: True when `net_benefit` is positive.
  - `recommendation` 'exercise_float_down' | 'hold_lock' | 'let_lock_ride' | 'extend_or_relock' | 'insufficient_data': What to do with the lock.
  - `confidence` 'high' | 'medium' | 'low': How much rate history backed the recommendation.
  - `rationale` string: The reasoning behind the recommendation, in plain language.
  - `assumed_defaults` object: Values filled in where the caller supplied none, so the result is reproducible.
  - `next_actions` object[]: Other endpoints or tools that are the natural next step from this result.
  - `disclosures` string[]: Statements that must be shown to a consumer alongside this result.
  - `note` string: Plain-language explanation of what this result means.

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