---
title: "Batch/bulk financing decisions"
method: POST
path: "/v1/decisions/batch"
tags: ["Data & Analytics"]
---

# Batch/bulk financing decisions

`POST /v1/decisions/batch`

## Request

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

Stateless batch scoring: accepts an array of inline scenarios and returns ranked decisions per item in one call, so a backend can score a whole portfolio without persisted cohorts or per-borrower HTTP loops. Each scenario is evaluated by the same decision engine that powers `/v1/simulate-decision` (single source of truth); the MCP `batch_decisions` tool and the consumer-app Playground batch mode call this route. Per-item engine misses are partial successes (`status: no_qualifying_offer`) and never fail the whole batch.

**Billing**: each scenario counts as one API call (a 25-scenario batch bills 25 calls); a rejected batch (4xx) bills a single call.

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

## Request body

- object
  - `state` string: Optional default 2-letter US state applied to scenarios lacking one.
  - `credit_score` integer: Optional default credit score applied to scenarios lacking one.
  - `max_providers` integer: Top-N offers returned per scenario. Defaults to 3.
  - `scenarios` object[], required: Inline scenarios to score in one stateless call (no persisted cohorts).
    - `id` string: Caller correlation id, echoed back.
    - `product_type` 'mortgage' | 'mortgage_arm' | 'auto_loan' | 'personal_loan' | 'heloc' | '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`.
    - `intent` 'purchase' | 'refinance' | 'cash_out' | 'balance_transfer' | 'new_credit', required: What the borrower is trying to do — for example purchase or refinance.
    - `amount` number, required: Loan amount in US dollars.
    - `state` string: Overrides top-level state.
    - `term_months` integer: Loan term in months.
    - `credit_score` integer: Overrides top-level credit_score.
    - `down_payment_percent` number: Down payment as a percentage of purchase price.

## Response `200`

Ranked decisions per scenario. Rows are ranked 1..n across the batch by best-offer APR ascending; rows with no qualifying offer rank last.

- object
  - `count` integer: Equals scenarios.length.
  - `results` object[]: One result per scenario submitted, in the order they were sent.
    - `id` string, nullable: The scenario id the caller supplied, echoed back so results can be matched up, or null when none was given.
    - `index` integer: Input position.
    - `rank` integer: 1..n across the whole batch.
    - `status` 'ok' | 'no_qualifying_offer': Whether this scenario produced an offer.
    - `best_offer` object, nullable: The winning offer for this scenario, or null when nothing qualified.
      - `lender` string: Name of the credit union offering this rate.
      - `apr` number: Annual percentage rate, which includes fees and is the figure offers are ranked on.
      - `rate` number: Note rate as an annual percentage, excluding fees.
      - `monthly_payment` number: Monthly principal-and-interest payment in US dollars.
    - `offers` object[]: The qualifying offers for this scenario, best first.
      - `rank` integer: Position in the ranking, 1 being best.
      - `lender` string: Name of the credit union offering this rate.
      - `apr` number: Annual percentage rate, which includes fees and is the figure offers are ranked on.
      - `rate` number: Note rate as an annual percentage, excluding fees.
      - `monthly_payment` number: Monthly principal-and-interest payment in US dollars.
    - `recommended_action` string, nullable: What to do about this scenario, or null when nothing qualified.
  - `totals` object: How the batch came out overall.
    - `scenarios_with_offer` integer: Scenarios that produced at least one qualifying offer.
    - `scenarios_no_offer` integer: Scenarios that produced none.

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