---
title: "Lender competitive position (where do MY rates rank)"
method: POST
path: "/v1/competitive-position"
tags: ["Data & Analytics"]
---

# Lender competitive position (where do MY rates rank)

`POST /v1/competitive-position`

## Request

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

Lender-facing 'is my offer competitive' check. Input your published note rate (and optional APR) for a state + mortgage product and get a market percentile (share of live offers strictly below your APR), bps vs the state median, and a leading / competitive / overpriced verdict. Mortgage-only (30-year or 15-year fixed). Distinct from `/v1/rate-sanity-check`, which validates an inbound borrower quote. This route is the single source of truth; the MCP `check_competitive_position` tool and the consumer app call it.

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

## Request body

- object
  - `state` string, required: 2-letter US state code.
  - `product` '30yr_fixed' | '15yr_fixed', required: Mortgage product you publish.
  - `rate` number, required: Your published note rate as a percentage.
  - `apr` number: Your published APR. Optional — falls back to rate.

## Response `200`

Market position for the supplied offer.

- object
  - `state` string: Two-letter US state code.
  - `product` '30yr_fixed' | '15yr_fixed': Mortgage product the offer was positioned against.
  - `your_offer` object: The offer that was checked, echoed back.
    - `rate` number: Note rate as an annual percentage, excluding fees.
    - `apr` number: Annual percentage rate, which includes fees and is the figure offers are ranked on.
  - `percentile` integer: Share of live offers strictly below your APR (lower = more competitive).
  - `bps_vs_median` integer, nullable: (your_apr - median) in bps; negative = below median = cheaper.
  - `verdict` 'leading' | 'competitive' | 'overpriced' | 'below_floor' | 'unknown': Where the offer sits: `leading` beats most of the market, `competitive` sits in the normal range, `overpriced` sits well above the median, `below_floor` falls under what this product plausibly prices at, `unknown` when there was no market to compare against.
  - `market` object: Summary of the live offers the position was measured against.
    - `min` number, nullable: Lowest APR among the offers compared against.
    - `median` number, nullable: Median APR among the offers compared against.
    - `max` number, nullable: Highest APR among the offers compared against.
    - `count` integer: How many offers the comparison was made against.
    - `as_of` string, nullable: When the underlying rates were published.
  - `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)
