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.
/api/v1/wallets/{id}/leasesReserves 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 getslease_expired;capture: all of the rest is charged as consumed;hold_for_late_settle: the funds stay reserved forlateSettleWindowSeconds(default one day), and one settle in that window charges what was consumed and releases the rest. If none comes,lateSettleFallbackapplies (releaseby default, orcapture). 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-Keyrequired - Modes: Test and live
- Plan feature:
usage_billing.enabled - Authentication: API key with the
wallets:writescope - 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
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):
{
"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.
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):
{
"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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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: api_version_unsupported, invalid_json, unsupported_media_type, authentication_required, api_key_invalid, scope_required, idempotency_key_required, idempotency_in_progress, approval_required, approval_rejected, approval_expired, stale_approval, approval_policy_denied, rate_limited, internal_error.