> ## Documentation Index
> Fetch the complete documentation index at: https://docs.capcells.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Issue a loan

> Record the agreement: the terms are checked against the configuration, priced once, and frozen with the loan.

Issuance records the agreement and answers `201` with the Loan as every later read answers it: the folded `state`, the recorded `events`, the plan and the `regime` — priced once, frozen forever. [Loans](/lending/loans) carries the checks and the freeze; [Preview](/api-reference/loans/preview) takes this same body and records nothing.

The `reference` is the retry: an issuance retried under it with the same statements answers `200` and the Loan on file, never a second Loan.

## Path parameters

<ParamField path="tenant_id" type="string" required>
  The Tenancy issuing. A key for another Tenancy answers `403`.
</ParamField>

## Body

<ParamField body="reference" type="string" required>
  Your own identifier for the agreement, unique within the Tenancy. 1–64 characters of `a–z A–Z 0–9 - _`.
</ParamField>

<ParamField body="client_id" type="string" required>
  A [registered Client](/api-reference/clients/register) — checked before anything else, preview included.
</ParamField>

<ParamField body="configuration_id" type="string" required>
  The Configuration the Loan is sold under.
</ParamField>

<ParamField body="configuration_version" type="integer" required>
  The version, pinned. The word `latest` is refused by name: a Loan issued against whatever is newest is a Loan issued under terms nobody stated.
</ParamField>

<ParamField body="principal" type="object" required>
  The amount lent: `{"minor_units": 800000, "currency": "MXN"}`. Inside the Configuration's principal range, in its currency.
</ParamField>

<ParamField body="term_periods" type="integer" required>
  How many Periods the Loan repays over. A member of the Configuration's `terms` menu; `1` on a payday product, whose menu is locked.
</ParamField>

<ParamField body="rate" type="string">
  The annual nominal rate, a decimal percentage in a string: `"45.5"`. Inside the Configuration's rate corridor. Optional under a Facility: omitted, the Loan takes the Facility's rate exactly — the `rate.percent` the Facility and its decision read — and a stated rate is compared with it exactly, refused `facility_rate` where it differs. A retry is compared on the rate the Loan resolves to, so omitting the rate and stating the Facility's are one issuance under one `reference`. Required with no Facility, refused `invalid_rate` when omitted.
</ParamField>

<ParamField body="penalty_rate" type="string">
  The lateness rate, quoted the same way. Inside the penalty corridor, and at or under the jurisdiction's cap — in `MX`, twice the Loan's own ordinary rate; [the market's rules](/api-reference/markets/read) state the cap's form. Optional where the version's penalty corridor is a single point (`penalty_rate_min` equal to `penalty_rate_max`): the Loan takes the point, held to the cap exactly as a stated rate is. Required under a corridor whose ends differ, refused `missing_term` when omitted.
</ParamField>

<ParamField body="anchor" type="string" required>
  The civil date the agreement runs from, `YYYY-MM-DD`. Never compared to a clock: a backdated anchor is legitimate.
</ParamField>

<ParamField body="jurisdiction" type="string" required>
  ISO 3166-1 alpha-2, with up to two subdivisions: `MX`, `DE-BY`. [It selects the market's rules](/lending/markets), and a jurisdiction the rule tables do not cover refuses issuance.
</ParamField>

<ParamField body="tax" type="object" required>
  The tax rule: `{"rate": "16", "on": ["interest", "fee", "penalty", "late_fee"]}`. It must be one of the rules the `jurisdiction` admits, rate and components compared whole, in any order: [Markets](/lending/markets#taxes) lists them, and the country registry serves them as `tax_rules`. A component the version can never charge may be left out. No default and no omission — an exempt borrower is a rule of zero, stated. Principal is never taxed.
</ParamField>

<ParamField body="down_payment" type="object">
  The collection at the purchase. Required on `bnpl`, meaningless elsewhere.
</ParamField>

<ParamField body="schedule" type="object">
  Which of the version's admitted schedules the Loan collects on: `{"kind": "semi_monthly_grid"}`, or `{"kind": "monthly_on_day", "day": 20}` — the `day` is required under `monthly_on_day` and refused under every other kind. Required where the version admits several schedules, optional where it admits one. The interval and the grid days are the version's and are not restated. The `anchor` keeps its meaning: the credit runs from it and interest accrues from it; the choice moves only where the payments fall, so a loan anchored on the 9th charging on the 20th collects first on the 20th of the same month.
</ParamField>

<ParamField body="facility_id" type="string">
  The Facility to draw from — required where the Configuration version requires a grant, and held to it: the Client, the product, the currency, the rate, the window and the headroom are each checked against the grant.
</ParamField>

<ParamField body="decision_id" type="string">
  The Credit Decision this plan is priced under, recorded with the Loan. A Decision taken on another Client, another product or another principal is refused.
</ParamField>

<ParamField body="merchant_ref" type="string">
  Your own key for the store, counter or biller the Loan financed a purchase at — letters, digits, hyphen and underscore, 1 to 64. Optional: a Loan that names none is on no merchant's book. Recorded on the Loan, echoed on every read of it and on every [feed](/api-reference/loans/events) entry about it, a filter on [the listing](/api-reference/loans/list), and the key [the merchant's book](/api-reference/merchants/book) is read by. The platform reads nothing from it and holds nothing about the merchant. A retry restates it.
</ParamField>

<ParamField body="hold_until" type="string">
  The last day the Loan may be [confirmed](/api-reference/loans/confirmation) on, for an issuance that waits for a signature; on or after `anchor`. Until confirmed the Loan is `held`: every term and period is fixed and it draws its grant, but it owes nothing, takes no money and counts toward no figure about the Client. Past this day it reads `expired` and admits only [a void](/api-reference/loans/void), which gives the grant and the `reference` back. Refused on a product collecting a payment at the purchase. A retry restates it.
</ParamField>

<ParamField body="context" type="object">
  Your key-value object: at most 16 keys, 64 bytes a key, 4096 bytes rendered, string values. Stored, echoed on every read, read by nothing — and part of what a reused `reference` restates.
</ParamField>

## Response

`201` with the Loan; [Read a loan](/api-reference/loans/read) documents every field of it. A retry with the same `reference` and the same statements answers `200` and the same Loan.

## Refusals

| Condition | Status and code |
| - | - |
| `client_id` is not registered | <Badge color="orange">422</Badge> `unknown_client` |
| `configuration_version` is `"latest"` | <Badge color="orange">422</Badge> `unversioned` |
| The term is outside the menu, the principal outside its range, a rate outside its corridor, the `schedule.kind` not one the version admits, a `day` under a kind that takes none | <Badge color="orange">422</Badge> `term_refused` |
| A term the version requires is absent: a `down_payment` on `bnpl`, a `schedule` under a version admitting several, a `day` under `monthly_on_day` | <Badge color="orange">422</Badge> `missing_term` |
| The penalty rate exceeds the jurisdiction's cap — `details.bound` is the cap at the Loan's own rate, `details.observed` the penalty rate | <Badge color="orange">422</Badge> `penalty_rate_above_cap` |
| A late fee and a penalty rate above zero, in a market that charges a delay once | <Badge color="orange">422</Badge> `late_fee_beside_penalty` |
| The `tax` is not one of the rules the jurisdiction admits | <Badge color="orange">422</Badge> `tax_rule_not_admitted` — the jurisdiction and every rule it admits are named, or the component left out |
| The Platform asserts no tax for the jurisdiction | <Badge color="orange">422</Badge> `no_tax_rules` |
| The Schedule the term produces reaches past [the jurisdiction's calendar](/lending/markets) | <Badge color="orange">422</Badge> `beyond_horizon` — the first date past it, the last covered day and the jurisdiction are named |
| A grant named in `facility_id` does not fit — see [What is checked](/lending/facilities#what-is-checked) — or is a line its successor superseded | <Badge color="orange">422</Badge> `facility_*` — `facility_superseded` for a renewed line |
| The version states a [client guard](/lending/facilities#client-guards) the Client's whole book on the anchor fails: the open-loan count, the exposure cap, or a Loan standing overdue — the last also on any draw from a line | <Badge color="orange">422</Badge> `client_open_loans`, `client_exposure` or `client_overdue` — `details.bound` and `details.observed` on the first two |
| The `reference` names a different record | <Badge color="red">409</Badge> `reference_reused` — the differing field is named |
| An unknown field, a repeated key, a malformed value | <Badge color="orange">400</Badge> `malformed_request` — refused by name, never dropped |

<RequestExample>
  ```bash Issue theme={null}
  BODY='{
    "reference": "loan-0001",
    "client_id": "c-0001",
    "configuration_id": "inst-000001",
    "configuration_version": 1,
    "principal": { "minor_units": 800000, "currency": "MXN" },
    "term_periods": 12,
    "rate": "45.5",
    "penalty_rate": "60",
    "anchor": "2026-03-02",
    "jurisdiction": "MX",
    "tax": { "rate": "16", "on": ["interest", "fee", "penalty", "late_fee"] }
  }'

  curl -X POST "https://api.capcells.com/v1/tenants/$TENANT_ID/loans" \
    -H "Authorization: $(capcells_sign "$CAPCELLS_API_KEY" POST "/v1/tenants/$TENANT_ID/loans" "$BODY")" \
    -H "Content-Type: application/json" \
    -d "$BODY"
  ```
</RequestExample>

<ResponseExample>
  ```json 201 theme={null}
  {
    "id": "7771aef0-9e65-4a0a-a902-aab4e970d33f",
    "tenant_id": "t-0001",
    "state": "active",
    "created_at": "2026-08-18T22:26:41.420114Z",
    "rolled_over": 0,
    "events": [
      { "id": "…0001", "kind": "issued", "occurred_on": "2026-03-02", "recorded_at": "2026-08-18T22:26:41.420114Z" }
    ],
    "anomalies": [],
    "configuration": { "id": "inst-000001", "version": 1 },
    "terms": { … },
    "periods": [ … ],
    "disclosure": { "jurisdiction": "MX", "figure": "64.9", "convention": "mx-cat", "convention_version": 1, "provisional": true },
    "regime": { … }
  }
  ```

  ```json 422 A term outside the menu theme={null}
  {
    "error": "loan: selected term is outside the configuration: a term of 7 periods is not one of the [6 12 18] the product is sold in",
    "code": "term_refused"
  }
  ```

  ```json 422 An unpinned version theme={null}
  {
    "error": "reading the request: field configuration_version: loan: issuance must name the configuration version: latest is not a version number",
    "code": "unversioned"
  }
  ```
</ResponseExample>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.