Settle a lease
The leases.settle operation: POST /api/v1/leases/{id}/settle in the REST API and the leases_settle tool in the MCP server.
/api/v1/leases/{id}/settleCharges what your service consumed from a lease since the previous settle (consumed), with a
usageSummary: one line per meter (and attribute set) with the quantity used and its amount; the amounts
add up to consumed. Each line is stored as a usage event and counted in the daily usage aggregates. The
settle is charged at once (the lease's captured grows); the ledger books what a lease's settles charged as
usage revenue per meter in one posting, when the lease closes, is renewed or expires. Wallet usage can't use a
meter that a metered price bills in arrears (prepaid and arrears usage coexist on different meters).
Then the lease:
- stays open (neither
finalnorrenew): keep consuming itsremaining; - closes (
final: true): what it still holds goes back to the wallet; - is renewed (
renew): it closes, and a new lease with a new ID, a fresh TTL and the same expiry terms is reserved, forrenew.amount(default: the same amount) or as much of it as the wallet has.renewedLeaseis null when the wallet can't fund it.
A settle after expiresAt gets lease_expired, except within the late-settle window of a
hold_for_late_settle lease: that settle charges consumed and closes the lease (late: true).
Ordering and retries: a resent settle (same Idempotency-Key and content) is answered with duplicate: true
and changes nothing. Optionally number your settles with settleSeq (the lease's settleSeq plus one): a
numbered settle is applied only after the one before it (lease_settle_out_of_order, retryable, otherwise),
so a final settle can't overtake earlier usage, and a settle numbered at or below the lease's settleSeq
is a duplicate. Every response has the lease's suggestedNextAmount.
At a glance
- REST:
POST /api/v1/leases/{id}/settle - MCP tool:
leases_settle - Operation ID:
leases.settle - Scope: Brand
- Permission:
wallets:write - Risk tier:
write - Dry run: Supported (send
Ownrecord-Dry-Run: true) - Idempotency:
Idempotency-Keyrequired - Modes: Test and live
- Authentication: API key with the
wallets:writescope - Since: M6
Request
Path parameters
| Field | Type | Description |
|---|---|---|
id | lease_… ID | The lease (lease_…). |
Body
A JSON object (Content-Type: application/json):
| Field | Type | Required | Description |
|---|---|---|---|
consumed | object | Yes | What was used since the previous settle of this lease, in its currency; at most what it still holds. |
consumed.amount | string | Yes | Integer micros as a string (5000000 = 5.00); finer than a cent is fine. |
consumed.currency | string | Yes | ISO 4217 currency code: the wallet's currency. |
usageSummary | array of object | No | Up to 100 lines, one per meter (and attribute set), whose amounts add up to consumed. Each is stored as a usage event. |
usageSummary[].meter | string | Yes | The meter this usage counts toward, e.g. api.calls. Not a meter a metered price bills in arrears. |
usageSummary[].quantity | string or integer (at least 0) | Yes | How much of the meter was used, in its unit, as an integer string (e.g. "1200" calls). |
usageSummary[].amount | object | Yes | What this usage cost, in the wallet's currency. |
usageSummary[].amount.amount | string | Yes | Integer micros as a string (5000000 = 5.00); finer than a cent is fine. |
usageSummary[].amount.currency | string | Yes | ISO 4217 currency code: the wallet's currency. |
usageSummary[].attributes | object | No | Optional dimensions of the event, e.g. { "model": "large" }; usage is aggregated per day and attribute set. |
final | boolean | No | true: close the lease; what it still holds goes back to the wallet. |
renew | object | No | Close this lease and continue in a new one (new ID, fresh TTL, same expiry terms). Send {} to keep the amount and TTL. |
renew.amount | object | No | The new lease's amount (default: this lease's amount). |
renew.amount.amount | string | Yes | Integer micros as a string (5000000 = 5.00); finer than a cent is fine. |
renew.amount.currency | string | Yes | ISO 4217 currency code: the wallet's currency. |
renew.ttlSeconds | integer (1 to 900) | No | The new lease's TTL (default: this lease's TTL). |
settleSeq | integer (at least 1) | No | Optional: this settle's number, the lease's settleSeq plus one. Numbered settles are applied in order, and a resent one is answered as a duplicate. |
Response
200 OK with a JSON object:
| Field | Type | Description |
|---|---|---|
object | "lease_settlement" | |
settleSeq | integer (at least 1) | This settle's number on the lease. |
duplicate | boolean | true: this settle had been applied before (resent, or numbered at or below the lease's settleSeq); nothing changed now, and lease is the lease as it is now. |
late | boolean | Settled after expiresAt, inside the late-settle window; it closed the lease. |
consumed | money | What this settle captured. |
lease | object | The settled lease. |
lease.id | lease_… ID | |
lease.object | "lease" | |
lease.brandId | brd_… ID | |
lease.livemode | boolean | |
lease.walletId | wal_… ID | |
lease.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. |
lease.amount | money | How much was leased. |
lease.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. |
lease.remaining | money | What can still be consumed: amount − captured while the lease is open, 0 once it is closed. |
lease.ttlSeconds | integer (1 to 900) | |
lease.expiresAt | date-time | Settle before this instant; then the expiry policy applies. |
lease.expiryPolicy | one of release, capture, hold_for_late_settle | |
lease.lateSettleWindowSeconds | integer or null | hold_for_late_settle only. |
lease.lateSettleFallback | one of release, capture or null | hold_for_late_settle only. |
lease.lateSettleUntil | date-time or null | hold_for_late_settle only: a settle is accepted until this instant (expiresAt + window). |
lease.settleSeq | integer (at least 0) | How many settles the lease has had. |
lease.renewedFrom | lease_… ID or null | The lease this one renewed. |
lease.renewedTo | lease_… ID or null | The lease a renewing settle replaced this one with. |
lease.suggestedNextAmount | money | Sizing guidance for the next lease (or renewal) of this TTL, from the recent consumption rate with 25% headroom. |
lease.metadata | object | |
lease.createdAt | date-time | |
lease.closedAt | date-time or null | |
renewedLease | object or null | The new lease of a renewal. null without renew, after a late settle, or when the wallet couldn't fund it (frozen, or no spendable credits); its amount may be less than asked when the wallet had less. |
usageEventIds | array of uev_… ID | The usage events, one per usage summary line. |
Examples
Settle usage of two meters and keep the lease
POST /api/v1/leases/lease_01m4v0d9k2j7r5t3w8x6y4z2b0/settle
Authorization: Bearer or_test_…
Idempotency-Key: 3f1c9a52-7d0e-4b8f-9a61-0c2d5e8b7f14
Content-Type: application/json
{
"consumed": {
"amount": "1250000",
"currency": "EUR"
},
"usageSummary": [
{
"meter": "api.calls",
"quantity": "1000",
"amount": {
"amount": "1000000",
"currency": "EUR"
},
"attributes": {
"model": "large"
}
},
{
"meter": "tokens.out",
"quantity": "25000",
"amount": {
"amount": "250000",
"currency": "EUR"
}
}
],
"settleSeq": 1
}
Response (200):
{
"object": "lease_settlement",
"settleSeq": 1,
"duplicate": false,
"late": false,
"consumed": {
"amount": "1250000",
"currency": "EUR"
},
"lease": {
"id": "lease_01m4v0d9k2j7r5t3w8x6y4z2b0",
"object": "lease",
"brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
"livemode": false,
"walletId": "wal_01m4ty3c2b5qx7mhd9k8w6s0b1",
"status": "active",
"amount": {
"amount": "5000000",
"currency": "EUR"
},
"captured": {
"amount": "1250000",
"currency": "EUR"
},
"remaining": {
"amount": "3750000",
"currency": "EUR"
},
"ttlSeconds": 300,
"expiresAt": "2026-10-13T12:05:00.000Z",
"expiryPolicy": "release",
"lateSettleWindowSeconds": null,
"lateSettleFallback": null,
"lateSettleUntil": null,
"settleSeq": 1,
"renewedFrom": null,
"renewedTo": null,
"suggestedNextAmount": {
"amount": "7812500",
"currency": "EUR"
},
"metadata": {
"worker": "api-eu-1"
},
"createdAt": "2026-10-13T12:00:00.000Z",
"closedAt": null
},
"renewedLease": null,
"usageEventIds": ["uev_01m4v0d9k2j7r5t3w8x6y4z2b2", "uev_01m4v0d9k2j7r5t3w8x6y4z2b3"]
}
Settle and renew before the TTL
The lease closes and a new 5.00 lease continues with a fresh TTL.
POST /api/v1/leases/lease_01m4v0d9k2j7r5t3w8x6y4z2b0/settle
Authorization: Bearer or_test_…
Idempotency-Key: 3f1c9a52-7d0e-4b8f-9a61-0c2d5e8b7f14
Content-Type: application/json
{
"consumed": {
"amount": "3500000",
"currency": "EUR"
},
"usageSummary": [
{
"meter": "api.calls",
"quantity": "3500",
"amount": {
"amount": "3500000",
"currency": "EUR"
}
}
],
"renew": {},
"settleSeq": 2
}
Response (200):
{
"object": "lease_settlement",
"settleSeq": 2,
"duplicate": false,
"late": false,
"consumed": {
"amount": "3500000",
"currency": "EUR"
},
"lease": {
"id": "lease_01m4v0d9k2j7r5t3w8x6y4z2b0",
"object": "lease",
"brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
"livemode": false,
"walletId": "wal_01m4ty3c2b5qx7mhd9k8w6s0b1",
"status": "captured",
"amount": {
"amount": "5000000",
"currency": "EUR"
},
"captured": {
"amount": "4750000",
"currency": "EUR"
},
"remaining": {
"amount": "0",
"currency": "EUR"
},
"ttlSeconds": 300,
"expiresAt": "2026-10-13T12:05:00.000Z",
"expiryPolicy": "release",
"lateSettleWindowSeconds": null,
"lateSettleFallback": null,
"lateSettleUntil": null,
"settleSeq": 2,
"renewedFrom": null,
"renewedTo": "lease_01m4v0d9k2j7r5t3w8x6y4z2b1",
"suggestedNextAmount": {
"amount": "7421875",
"currency": "EUR"
},
"metadata": {
"worker": "api-eu-1"
},
"createdAt": "2026-10-13T12:00:00.000Z",
"closedAt": "2026-10-13T12:04:00.000Z"
},
"renewedLease": {
"id": "lease_01m4v0d9k2j7r5t3w8x6y4z2b1",
"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:09:00.000Z",
"expiryPolicy": "release",
"lateSettleWindowSeconds": null,
"lateSettleFallback": null,
"lateSettleUntil": null,
"settleSeq": 0,
"renewedFrom": "lease_01m4v0d9k2j7r5t3w8x6y4z2b0",
"renewedTo": null,
"suggestedNextAmount": {
"amount": "7421875",
"currency": "EUR"
},
"metadata": {
"worker": "api-eu-1"
},
"createdAt": "2026-10-13T12:04:00.000Z",
"closedAt": null
},
"usageEventIds": ["uev_01m4v0d9k2j7r5t3w8x6y4z2b2"]
}
Close the lease
POST /api/v1/leases/lease_01m4v0d9k2j7r5t3w8x6y4z2b0/settle
Authorization: Bearer or_test_…
Idempotency-Key: 3f1c9a52-7d0e-4b8f-9a61-0c2d5e8b7f14
Content-Type: application/json
{
"consumed": {
"amount": "0",
"currency": "EUR"
},
"usageSummary": [],
"final": true
}
Response (200):
{
"object": "lease_settlement",
"settleSeq": 1,
"duplicate": false,
"late": false,
"consumed": {
"amount": "0",
"currency": "EUR"
},
"lease": {
"id": "lease_01m4v0d9k2j7r5t3w8x6y4z2b0",
"object": "lease",
"brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
"livemode": false,
"walletId": "wal_01m4ty3c2b5qx7mhd9k8w6s0b1",
"status": "captured",
"amount": {
"amount": "5000000",
"currency": "EUR"
},
"captured": {
"amount": "0",
"currency": "EUR"
},
"remaining": {
"amount": "0",
"currency": "EUR"
},
"ttlSeconds": 300,
"expiresAt": "2026-10-13T12:05:00.000Z",
"expiryPolicy": "release",
"lateSettleWindowSeconds": null,
"lateSettleFallback": null,
"lateSettleUntil": null,
"settleSeq": 1,
"renewedFrom": null,
"renewedTo": null,
"suggestedNextAmount": {
"amount": "500000",
"currency": "EUR"
},
"metadata": {
"worker": "api-eu-1"
},
"createdAt": "2026-10-13T12:00:00.000Z",
"closedAt": "2026-10-13T12:01:00.000Z"
},
"renewedLease": null,
"usageEventIds": []
}
Errors
| Code | HTTP status | When | What to do |
|---|---|---|---|
validation_failed | 400 | A field is malformed, or a settle asks for both final and renew. | Fix the fields listed in details.issues and send the request again. |
lease_usage_mismatch | 400 | The amounts of the usageSummary lines don't add up to consumed. | Make the amounts of the usageSummary lines add up to consumed exactly (details.consumed, details.usageTotal, integer micros), in the lease's currency. |
capture_exceeds_reservation | 400 | consumed is more than the lease still holds (remaining). | Capture at most the reserved amount that is still open (details.outstanding, integer micros), or reserve more first. |
wallet_currency_mismatch | 400 | consumed isn't in the lease's currency. | Use the wallet for this currency. A customer has one wallet per currency (details.expected, details.actual). |
usage_meter_billed_in_arrears | 400 | A line names a meter a metered price bills in arrears. | Report prepaid (wallet) usage on a meter that no metered price names, e.g. api.calls.prepaid (details.meter). |
resource_not_found | 404 | No lease 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. |
lease_expired | 409 | The lease is past expiresAt (and past lateSettleUntil for hold_for_late_settle). | Create a new lease for further usage. To settle usage after a crash, make leases with the expiry policy hold_for_late_settle, which accepts a settle until lateSettleUntil. |
lease_closed | 409 | A final settle or a renewal already closed the lease (details.renewedTo names its successor). | Settle the lease it was renewed into (details.renewedTo), or create a new lease. A lease closes with a final settle or a renewal. |
lease_settle_out_of_order | 409 | settleSeq skips a settle that hasn't arrived yet (retryable). | Send the settles of a lease in order, one at a time: settleSeq must be the lease's settleSeq plus one (details.expected). Retry once the earlier settle has been applied. |
lease_settle_seq_conflict | 409 | The lease's last settle has this settleSeq but other content. | Give each settle of a lease the next number (details.settleSeq is the lease's current one) and resend a settle only unchanged. |
idempotency_key_reused | 409 | The Idempotency-Key was already used for a different settle of this lease. | 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.