# Lease wallet funds

> The leases.create operation: POST /api/v1/wallets/{id}/leases in the REST API and the leases_create tool in the MCP server.

Reserves a block of a wallet's funds (`amount`, e.g. 5.00) for `ttlSeconds` (at most 900), so your service can
meter high-frequency usage locally against it and settle in batches instead of calling Ownrecord for every
charge. Never consume more than the lease's `remaining`.

The funds are taken from the wallet's credits in spend order (promo credits first, then subscription credits,
then purchased credits, each soonest expiry first) and held until you settle the lease
(`leases.settle`). If the lease reaches its TTL without a closing settle, its `expiryPolicy` applies:

- `release` (default): the rest goes back to the wallet; a later settle gets `lease_expired`;
- `capture`: all of the rest is charged as consumed;
- `hold_for_late_settle`: the funds stay reserved for `lateSettleWindowSeconds` (default one day), and one
  settle in that window charges what was consumed and releases the rest. If none comes,
  `lateSettleFallback` applies (`release` by default, or `capture`). For services that keep a durable local
  consumption log and can recover after a crash.

The response includes `suggestedNextAmount`, sizing guidance from recent consumption. Retrying with the same
`Idempotency-Key` returns the same lease.

## At a glance

- **REST:** `POST /api/v1/wallets/{id}/leases`
- **MCP tool:** `leases_create`
- **Operation ID:** `leases.create`
- **Scope:** Brand
- **Permission:** `wallets:write`
- **Risk tier:** `write`
- **Dry run:** Supported (send `Ownrecord-Dry-Run: true`)
- **Idempotency:** `Idempotency-Key` required
- **Modes:** Test and live
- **Plan feature:** `usage_billing.enabled`
- **Authentication:** API key with the `wallets:write` scope
- **Since:** M6

## Request

### Path parameters

| Field | Type       | Description                         |
| ----- | ---------- | ----------------------------------- |
| `id`  | `wal_…` ID | The wallet to lease from (`wal_…`). |

### Body

A JSON object (`Content-Type: application/json`):

| Field                     | Type                                                | Required | Description                                                                                                                                                                                                      |
| ------------------------- | --------------------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `amount`                  | object                                              | Yes      | How much to lease, in the wallet's currency.                                                                                                                                                                     |
| `amount.amount`           | string                                              | Yes      | Integer micros as a string (5000000 = 5.00); finer than a cent is fine.                                                                                                                                          |
| `amount.currency`         | string                                              | Yes      | ISO 4217 currency code: the wallet's currency.                                                                                                                                                                   |
| `ttlSeconds`              | integer (1 to 900)                                  | Yes      | How long the lease lives, 1 to 900 seconds.                                                                                                                                                                      |
| `expiryPolicy`            | one of `release`, `capture`, `hold_for_late_settle` | No       | What happens if the lease reaches its TTL without a closing settle: `release` (default) returns the rest to the wallet, `capture` charges all of it, `hold_for_late_settle` keeps it reserved for a late settle. |
| `lateSettleWindowSeconds` | integer (1 to 604800)                               | No       | `hold_for_late_settle` only: how long after the TTL a settle is still accepted (default 86400, at most 7 days).                                                                                                  |
| `lateSettleFallback`      | one of `release`, `capture`                         | No       | `hold_for_late_settle` only: what happens when the window ends without a settle, `release` (default) or `capture`.                                                                                               |
| `metadata`                | object                                              | No       | Your own key-value pairs (at most 50 keys). On updates the whole set is replaced.                                                                                                                                |

## Response

`201 Created` with a JSON object:

| Field                     | Type                                                                  | Description                                                                                                                                                                                                  |
| ------------------------- | --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `id`                      | `lease_…` ID                                                          |                                                                                                                                                                                                              |
| `object`                  | `"lease"`                                                             |                                                                                                                                                                                                              |
| `brandId`                 | `brd_…` ID                                                            |                                                                                                                                                                                                              |
| `livemode`                | boolean                                                               |                                                                                                                                                                                                              |
| `walletId`                | `wal_…` ID                                                            |                                                                                                                                                                                                              |
| `status`                  | one of `active`, `expired_pending`, `captured`, `released`, `expired` | `active`: settle against it. `expired_pending`: past its TTL, held for a late settle until `lateSettleUntil`. `captured`: closed by a settle (`final` or a renewal). `expired`: closed by its expiry policy. |
| `amount`                  | money                                                                 | How much was leased.                                                                                                                                                                                         |
| `captured`                | money                                                                 | How much the settles (and an expiry capture) have charged so far. The ledger books it as usage revenue when the lease closes, is renewed or expires.                                                         |
| `remaining`               | money                                                                 | What can still be consumed: `amount` − `captured` while the lease is open, 0 once it is closed.                                                                                                              |
| `ttlSeconds`              | integer (1 to 900)                                                    |                                                                                                                                                                                                              |
| `expiresAt`               | date-time                                                             | Settle before this instant; then the expiry policy applies.                                                                                                                                                  |
| `expiryPolicy`            | one of `release`, `capture`, `hold_for_late_settle`                   |                                                                                                                                                                                                              |
| `lateSettleWindowSeconds` | integer or null                                                       | `hold_for_late_settle` only.                                                                                                                                                                                 |
| `lateSettleFallback`      | one of `release`, `capture` or null                                   | `hold_for_late_settle` only.                                                                                                                                                                                 |
| `lateSettleUntil`         | date-time or null                                                     | `hold_for_late_settle` only: a settle is accepted until this instant (`expiresAt` + window).                                                                                                                 |
| `settleSeq`               | integer (at least 0)                                                  | How many settles the lease has had.                                                                                                                                                                          |
| `renewedFrom`             | `lease_…` ID or null                                                  | The lease this one renewed.                                                                                                                                                                                  |
| `renewedTo`               | `lease_…` ID or null                                                  | The lease a renewing settle replaced this one with.                                                                                                                                                          |
| `suggestedNextAmount`     | money                                                                 | Sizing guidance for the next lease (or renewal) of this TTL, from the recent consumption rate with 25% headroom.                                                                                             |
| `metadata`                | object                                                                |                                                                                                                                                                                                              |
| `createdAt`               | date-time                                                             |                                                                                                                                                                                                              |
| `closedAt`                | date-time or null                                                     |                                                                                                                                                                                                              |

## Examples

### Lease 5.00 for five minutes

```http
POST /api/v1/wallets/wal_01m4ty3c2b5qx7mhd9k8w6s0b1/leases
Authorization: Bearer or_test_…
Idempotency-Key: 3f1c9a52-7d0e-4b8f-9a61-0c2d5e8b7f14
Content-Type: application/json

{
  "amount": {
    "amount": "5000000",
    "currency": "EUR"
  },
  "ttlSeconds": 300,
  "metadata": {
    "worker": "api-eu-1"
  }
}
```

Response (`201`):

```json
{
  "id": "lease_01m4v0d9k2j7r5t3w8x6y4z2b0",
  "object": "lease",
  "brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
  "livemode": false,
  "walletId": "wal_01m4ty3c2b5qx7mhd9k8w6s0b1",
  "status": "active",
  "amount": {
    "amount": "5000000",
    "currency": "EUR"
  },
  "captured": {
    "amount": "0",
    "currency": "EUR"
  },
  "remaining": {
    "amount": "5000000",
    "currency": "EUR"
  },
  "ttlSeconds": 300,
  "expiresAt": "2026-10-13T12:05:00.000Z",
  "expiryPolicy": "release",
  "lateSettleWindowSeconds": null,
  "lateSettleFallback": null,
  "lateSettleUntil": null,
  "settleSeq": 0,
  "renewedFrom": null,
  "renewedTo": null,
  "suggestedNextAmount": {
    "amount": "5000000",
    "currency": "EUR"
  },
  "metadata": {
    "worker": "api-eu-1"
  },
  "createdAt": "2026-10-13T12:00:00.000Z",
  "closedAt": null
}
```

### A lease that survives a crash

The funds stay reserved for an hour after the TTL, for one late settle; then they're released.

```http
POST /api/v1/wallets/wal_01m4ty3c2b5qx7mhd9k8w6s0b1/leases
Authorization: Bearer or_test_…
Idempotency-Key: 3f1c9a52-7d0e-4b8f-9a61-0c2d5e8b7f14
Content-Type: application/json

{
  "amount": {
    "amount": "5000000",
    "currency": "EUR"
  },
  "ttlSeconds": 300,
  "expiryPolicy": "hold_for_late_settle",
  "lateSettleWindowSeconds": 3600,
  "metadata": {
    "worker": "api-eu-1"
  }
}
```

Response (`201`):

```json
{
  "id": "lease_01m4v0d9k2j7r5t3w8x6y4z2b0",
  "object": "lease",
  "brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
  "livemode": false,
  "walletId": "wal_01m4ty3c2b5qx7mhd9k8w6s0b1",
  "status": "active",
  "amount": {
    "amount": "5000000",
    "currency": "EUR"
  },
  "captured": {
    "amount": "0",
    "currency": "EUR"
  },
  "remaining": {
    "amount": "5000000",
    "currency": "EUR"
  },
  "ttlSeconds": 300,
  "expiresAt": "2026-10-13T12:05:00.000Z",
  "expiryPolicy": "hold_for_late_settle",
  "lateSettleWindowSeconds": 3600,
  "lateSettleFallback": "release",
  "lateSettleUntil": "2026-10-13T13:05:00.000Z",
  "settleSeq": 0,
  "renewedFrom": null,
  "renewedTo": null,
  "suggestedNextAmount": {
    "amount": "5000000",
    "currency": "EUR"
  },
  "metadata": {
    "worker": "api-eu-1"
  },
  "createdAt": "2026-10-13T12:00:00.000Z",
  "closedAt": null
}
```

## Errors

| Code                                                                                                       | HTTP status | When                                                                                                                                       | What to do                                                                                                                                                                                |
| ---------------------------------------------------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`entitlement_required`](https://ownrecord.io/docs/reference/errors.md#entitlement_required)               | 403         | The organization's plan doesn't include usage billing (`usage_billing.enabled`).                                                           | Upgrade the organization's plan, or remove something to get back under the limit.                                                                                                         |
| [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed)                     | 400         | A field is malformed: an amount that isn't positive micros, a TTL outside 1–900 seconds, late-settle terms without `hold_for_late_settle`. | Fix the fields listed in `details.issues` and send the request again.                                                                                                                     |
| [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found)                   | 404         | No wallet with this ID in the brand and mode.                                                                                              | Check the ID and that your credentials are scoped to the right organization, entity, brand and mode.                                                                                      |
| [`insufficient_funds`](https://ownrecord.io/docs/reference/errors.md#insufficient_funds)                   | 402         | The wallet's spendable credits are less than `amount` (402).                                                                               | Reserve or capture a smaller amount, or top up the wallet first. `details.requested` and `details.available` are in integer micros.                                                       |
| [`wallet_fresh_balance_capped`](https://ownrecord.io/docs/reference/errors.md#wallet_fresh_balance_capped) | 402         | The brand caps how much of recently bought credits can be used (402; §20 usage caps for fresh balances).                                   | Ask for less now, or wait until `details.freeAt`: the brand caps how much of a fresh balance can be used (`details.cap`, micros) until the credits are `details.untilAgeHours` hours old. |
| [`wallet_currency_mismatch`](https://ownrecord.io/docs/reference/errors.md#wallet_currency_mismatch)       | 400         | `amount` isn't in the wallet's currency.                                                                                                   | Use the wallet for this currency. A customer has one wallet per currency (`details.expected`, `details.actual`).                                                                          |
| [`wallet_frozen`](https://ownrecord.io/docs/reference/errors.md#wallet_frozen)                             | 409         | The wallet is frozen.                                                                                                                      | Unfreeze the wallet (`wallets.update` with `status: active`) once the review is done. Reservations made before the freeze can still be captured or released.                              |
| [`wallet_closed`](https://ownrecord.io/docs/reference/errors.md#wallet_closed)                             | 409         | The wallet is closed.                                                                                                                      | Reopen the wallet with `wallets.update` (`status: active`). A purchase of credits in its currency reopens it, too.                                                                        |
| [`idempotency_key_reused`](https://ownrecord.io/docs/reference/errors.md#idempotency_key_reused)           | 409         | The `Idempotency-Key` was already used for another lease or reservation of this wallet.                                                    | Use a new Idempotency-Key for a different request, or resend the original request unchanged.                                                                                              |
| [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied)                     | 403         | The credentials can't use this brand's wallets (`wallets:write`).                                                                          | Ask an organization owner or admin for a role or scope that allows this action.                                                                                                           |

It can also return these [common errors](https://ownrecord.io/docs/reference.md#common-errors): [`api_version_unsupported`](https://ownrecord.io/docs/reference/errors.md#api_version_unsupported), [`invalid_json`](https://ownrecord.io/docs/reference/errors.md#invalid_json), [`unsupported_media_type`](https://ownrecord.io/docs/reference/errors.md#unsupported_media_type), [`authentication_required`](https://ownrecord.io/docs/reference/errors.md#authentication_required), [`api_key_invalid`](https://ownrecord.io/docs/reference/errors.md#api_key_invalid), [`scope_required`](https://ownrecord.io/docs/reference/errors.md#scope_required), [`idempotency_key_required`](https://ownrecord.io/docs/reference/errors.md#idempotency_key_required), [`idempotency_in_progress`](https://ownrecord.io/docs/reference/errors.md#idempotency_in_progress), [`approval_required`](https://ownrecord.io/docs/reference/errors.md#approval_required), [`approval_rejected`](https://ownrecord.io/docs/reference/errors.md#approval_rejected), [`approval_expired`](https://ownrecord.io/docs/reference/errors.md#approval_expired), [`stale_approval`](https://ownrecord.io/docs/reference/errors.md#stale_approval), [`approval_policy_denied`](https://ownrecord.io/docs/reference/errors.md#approval_policy_denied), [`rate_limited`](https://ownrecord.io/docs/reference/errors.md#rate_limited), [`internal_error`](https://ownrecord.io/docs/reference/errors.md#internal_error).
