Reservations tools
MCP tools for Reservations: reservations_capture, reservations_create, reservations_get, reservations_list, reservations_release.
reservations_capture: Capture a reservationreservations_create: Reserve funds on a walletreservations_get: Get a reservationreservations_list: List a wallet's reservationsreservations_release: Release a reservation
reservations_capture
Capture a reservation. Operation reservations.capture · Brand level · permission wallets:write · risk tier write · since M6
Charges what was used from a reservation: at most what is still reserved (capture_exceeds_reservation), and
before expiresAt (reservation_expired). By default the rest is released and the reservation closes as
captured; with final: false it stays open for further captures until it expires.
usage says what the amount was spent on, one line per meter (meter, quantity, amount, attributes); the
line amounts add up to amount. The revenue is booked per meter, and each line is stored as a usage event
(with its wallet, the reservation and its amount) and counted in the daily usage aggregates. A meter that a
metered price bills in arrears can't be used (usage_meter_billed_in_arrears). Without usage the whole
amount is usage of the meter default, and no usage event is stored.
The captured credits leave the wallet: it may emit wallet.low_balance or wallet.depleted, and start an
auto-reload. The same idempotencyKey (or Idempotency-Key) and request returns the same capture.
Annotations: readOnlyHint: false · destructiveHint: false · idempotentHint: true · openWorldHint: false
Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
id | ID (res_…) | yes | The reservation (res_…). |
amount | object | yes | What was used: at most what is still reserved. |
usage | array of object | no | What the amount was spent on, one line per meter. Without it the whole amount is usage of the meter default. (1–100 items) |
final | boolean | no | Release the rest and close the reservation (default true). false keeps it open for more captures until it expires. |
idempotencyKey | string | no | Unique per reservation: the same key and request returns the same capture (default: the request's Idempotency-Key). |
entity_id | ID (ent_…) | no | Optional: the brand's legal entity (ent_…); brand_id alone is enough. |
brand_id | ID (brd_…) | no | The brand to act on (brd_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |
idempotency_key | string | no | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's idempotency_key. (1–255 characters) |
dry_run | boolean | no | true: check permissions and rules and compute the result, then roll everything back. Nothing is saved. (default false) |
- Brand: pass
brand_idwith an organization or entity key; a brand key acts on its own brand. - Idempotency: the result returns the
idempotency_keyused, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours). - Dry run: supported.
dry_run: truechecks permissions and rules and computes the result, then rolls everything back.
Errors
validation_failed: The amount isn't positive, or the usage lines don't add up to it or are malformed.resource_not_found: This brand has no such reservation in this mode.capture_exceeds_reservation: The amount is more than is still reserved (details.outstanding).reservation_expired: The reservation'sexpiresAthas passed: reserve again.reservation_not_open: The reservation was already captured, released or expired.wallet_currency_mismatch: The amount or a usage line isn't in the wallet's currency.usage_meter_billed_in_arrears: A usage line names a meter a metered price bills in arrears (prepaid usage needs another meter).idempotency_key_reused: The idempotency key was used before for another request.version_conflict: The wallet changed at the same moment (another reservation, capture or grant); retry.scope_required: The request names no brand (an org or entity key withoutOwnrecord-Brand).permission_denied: You can't change this brand's wallets (wallets:write: owners, admins and support).livemode_not_allowed: You have no live-mode access, so you can't use live wallets.
Example
Capture €1.25 of €3 for 250 API calls and release the rest
Arguments:
{
"id": "res_01m4ty7w2a5qx7mhd9k8w6s0b4",
"amount": {
"amount": "1250000",
"currency": "EUR"
},
"usage": [
{
"meter": "api.calls",
"quantity": "250",
"amount": {
"amount": "1250000",
"currency": "EUR"
}
}
],
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
"idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
Result output:
{
"id": "res_01m4ty7w2a5qx7mhd9k8w6s0b4",
"object": "reservation",
"brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
"livemode": false,
"walletId": "wal_01m4ty7w2a5qx7mhd9k8w6s0b1",
"kind": "single",
"status": "captured",
"amount": {
"amount": "3000000",
"currency": "EUR"
},
"captured": {
"amount": "1250000",
"currency": "EUR"
},
"outstanding": {
"amount": "0",
"currency": "EUR"
},
"expiresAt": "2026-10-16T10:20:00.000Z",
"closedAt": "2026-10-16T10:16:00.000Z",
"idempotencyKey": "job-8841",
"metadata": {
"job": "8841"
},
"createdAt": "2026-10-16T10:15:00.000Z",
"updatedAt": "2026-10-16T10:16:00.000Z"
}
reservations_create
Reserve funds on a wallet. Operation reservations.create · Brand level · permission wallets:write · risk tier write · since M6
Holds an amount of a wallet's credits for usage that is about to happen (spec §17.2): taken in spend order
(promo credits first, soonest expiry first) and moved to reserved, so nothing else can spend them. Capture
what was used (reservations.capture) or release the hold (reservations.release) before expiresAt
(ttlSeconds, at most an hour); after that the funds go back to the wallet by themselves
(reservation.expired).
Answers 402 insufficient_funds when the wallet's spendable credits are less than the amount. A frozen or
closed wallet takes no reservations. The same idempotencyKey (or Idempotency-Key) and request returns the
same reservation. For thousands of small charges a minute, use leases instead (§17.3).
Annotations: readOnlyHint: false · destructiveHint: false · idempotentHint: true · openWorldHint: false
Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
id | ID (wal_…) | yes | The wallet (wal_…). |
amount | object | yes | The amount to hold, in the wallet's currency. |
ttlSeconds | integer | yes | How long the hold lasts, 1 to 3600 seconds. (1–3600) |
metadata | object | no | Your own key-value pairs (at most 50 keys, values of at most 500 characters). |
idempotencyKey | string | no | Unique per wallet: the same key and request returns the same reservation (default: the request's Idempotency-Key). |
entity_id | ID (ent_…) | no | Optional: the brand's legal entity (ent_…); brand_id alone is enough. |
brand_id | ID (brd_…) | no | The brand to act on (brd_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |
idempotency_key | string | no | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's idempotency_key. (1–255 characters) |
dry_run | boolean | no | true: check permissions and rules and compute the result, then roll everything back. Nothing is saved. (default false) |
- Brand: pass
brand_idwith an organization or entity key; a brand key acts on its own brand. - Idempotency: the result returns the
idempotency_keyused, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours). - Dry run: supported.
dry_run: truechecks permissions and rules and computes the result, then rolls everything back. - Plan feature: needs
usage_billing.enabled.
Errors
validation_failed: The amount isn't positive,ttlSecondsisn't 1 to 3600, or the metadata or idempotency key is malformed.resource_not_found: This brand has no such wallet in this mode (wallets belong to one brand and one mode).insufficient_funds: The wallet's spendable credits (unexpired, not already reserved) are less than the amount (details.requested,details.available). HTTP 402.wallet_fresh_balance_capped: The wallet has the credits, but the brand caps how much of recently bought credits can be used (details.freeAt; §20, WP-M9-risk). HTTP 402.wallet_currency_mismatch: The amount isn't in the wallet's currency.wallet_frozen: The wallet is frozen: it takes no new reservations.wallet_closed: The wallet is closed.idempotency_key_reused: The idempotency key was used before for another request.version_conflict: The wallet changed at the same moment (another reservation, capture or grant); retry.scope_required: The request names no brand (an org or entity key withoutOwnrecord-Brand).permission_denied: You can't change this brand's wallets (wallets:write: owners, admins and support).livemode_not_allowed: You have no live-mode access, so you can't use live wallets.entitlement_required: The organization's plan doesn't include usage billing (usage_billing.enabled).
Example
Hold €3 for five minutes
Arguments:
{
"id": "wal_01m4ty7w2a5qx7mhd9k8w6s0b1",
"amount": {
"amount": "3000000",
"currency": "EUR"
},
"ttlSeconds": 300,
"idempotencyKey": "job-8841",
"metadata": {
"job": "8841"
},
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
"idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
Result output:
{
"id": "res_01m4ty7w2a5qx7mhd9k8w6s0b4",
"object": "reservation",
"brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
"livemode": false,
"walletId": "wal_01m4ty7w2a5qx7mhd9k8w6s0b1",
"kind": "single",
"status": "active",
"amount": {
"amount": "3000000",
"currency": "EUR"
},
"captured": {
"amount": "0",
"currency": "EUR"
},
"outstanding": {
"amount": "3000000",
"currency": "EUR"
},
"expiresAt": "2026-10-16T10:20:00.000Z",
"closedAt": null,
"idempotencyKey": "job-8841",
"metadata": {
"job": "8841"
},
"createdAt": "2026-10-16T10:15:00.000Z",
"updatedAt": "2026-10-16T10:15:00.000Z"
}
reservations_get
Get a reservation. Operation reservations.get · Brand level · permission wallets:read · risk tier read · since M6
Returns a single reservation (res_…): what it holds (outstanding), what was captured, its status and when it
expires. Leases have their own operation (leases.get).
Annotations: readOnlyHint: true · destructiveHint: false · idempotentHint: true · openWorldHint: false
Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
id | ID (res_…) | yes | The reservation (res_…). |
entity_id | ID (ent_…) | no | Optional: the brand's legal entity (ent_…); brand_id alone is enough. |
brand_id | ID (brd_…) | no | The brand to act on (brd_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |
- Brand: pass
brand_idwith an organization or entity key; a brand key acts on its own brand. - Read-only: changes nothing;
idempotency_keyis ignored.
Errors
validation_failed:idisn't a reservation ID (res_…).resource_not_found: This brand has no such reservation in this mode.scope_required: The request names no brand (an org or entity key withoutOwnrecord-Brand).permission_denied: You can't read this brand's wallets (wallets:read).livemode_not_allowed: You have no live-mode access, so you can't use live wallets.
Example
An open reservation
Arguments:
{
"id": "res_01m4ty7w2a5qx7mhd9k8w6s0b4",
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}
Result output:
{
"id": "res_01m4ty7w2a5qx7mhd9k8w6s0b4",
"object": "reservation",
"brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
"livemode": false,
"walletId": "wal_01m4ty7w2a5qx7mhd9k8w6s0b1",
"kind": "single",
"status": "active",
"amount": {
"amount": "3000000",
"currency": "EUR"
},
"captured": {
"amount": "0",
"currency": "EUR"
},
"outstanding": {
"amount": "3000000",
"currency": "EUR"
},
"expiresAt": "2026-10-16T10:20:00.000Z",
"closedAt": null,
"idempotencyKey": "job-8841",
"metadata": {
"job": "8841"
},
"createdAt": "2026-10-16T10:15:00.000Z",
"updatedAt": "2026-10-16T10:15:00.000Z"
}
reservations_list
List a wallet's reservations. Operation reservations.list · Brand level · permission wallets:read · risk tier read · since M6
Lists a wallet's reservations, newest first: single reservations and leases (kind), open and closed.
status=active: what holds funds now (withexpired_pendingfor leases waiting for a late settle);kind:singleorlease.
Paginate with limit (1–100, default 10) and starting_after (a res_… or lease_… ID).
Annotations: readOnlyHint: true · destructiveHint: false · idempotentHint: true · openWorldHint: false
Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
id | ID (wal_…) | yes | The wallet (wal_…). |
limit | integer | no | (1–100) |
starting_after | string | no | – |
status | one of active, expired_pending, captured, released, expired | no | Only reservations with this status. |
kind | one of single, lease | no | single reservations or leases (spec §17.3); both by default. |
entity_id | ID (ent_…) | no | Optional: the brand's legal entity (ent_…); brand_id alone is enough. |
brand_id | ID (brd_…) | no | The brand to act on (brd_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |
- Brand: pass
brand_idwith an organization or entity key; a brand key acts on its own brand. - Read-only: changes nothing;
idempotency_keyis ignored.
Errors
validation_failed:idisn't a wallet ID, or a filter or pagination value is invalid.resource_not_found: This brand has no such wallet in this mode (wallets belong to one brand and one mode).scope_required: The request names no brand (an org or entity key withoutOwnrecord-Brand).permission_denied: You can't read this brand's wallets (wallets:read).livemode_not_allowed: You have no live-mode access, so you can't use live wallets.
Example
Open reservations
Arguments:
{
"id": "wal_01m4ty7w2a5qx7mhd9k8w6s0b1",
"status": "active",
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}
Result output:
{
"object": "list",
"data": [
{
"id": "res_01m4ty7w2a5qx7mhd9k8w6s0b4",
"object": "reservation",
"brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
"livemode": false,
"walletId": "wal_01m4ty7w2a5qx7mhd9k8w6s0b1",
"kind": "single",
"status": "active",
"amount": {
"amount": "3000000",
"currency": "EUR"
},
"captured": {
"amount": "0",
"currency": "EUR"
},
"outstanding": {
"amount": "3000000",
"currency": "EUR"
},
"expiresAt": "2026-10-16T10:20:00.000Z",
"closedAt": null,
"idempotencyKey": "job-8841",
"metadata": {
"job": "8841"
},
"createdAt": "2026-10-16T10:15:00.000Z",
"updatedAt": "2026-10-16T10:15:00.000Z"
}
],
"has_more": false,
"next_cursor": null
}
reservations_release
Release a reservation. Operation reservations.release · Brand level · permission wallets:write · risk tier write · since M6
Gives what a reservation still holds back to the wallet (each part to the credits it came from) and closes it
as released. Releasing a released reservation returns it unchanged; a captured or expired one can't be
released (reservation_not_open). Releasing changes no balance: the credits only stop being held.
Annotations: readOnlyHint: false · destructiveHint: false · idempotentHint: true · openWorldHint: false
Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
id | ID (res_…) | yes | The reservation (res_…). |
entity_id | ID (ent_…) | no | Optional: the brand's legal entity (ent_…); brand_id alone is enough. |
brand_id | ID (brd_…) | no | The brand to act on (brd_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |
idempotency_key | string | no | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's idempotency_key. (1–255 characters) |
dry_run | boolean | no | true: check permissions and rules and compute the result, then roll everything back. Nothing is saved. (default false) |
- Brand: pass
brand_idwith an organization or entity key; a brand key acts on its own brand. - Idempotency: the result returns the
idempotency_keyused, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours). - Dry run: supported.
dry_run: truechecks permissions and rules and computes the result, then rolls everything back.
Errors
validation_failed:idisn't a reservation ID (res_…).resource_not_found: This brand has no such reservation in this mode.reservation_not_open: The reservation was already captured or expired.version_conflict: The wallet changed at the same moment (another reservation, capture or grant); retry.scope_required: The request names no brand (an org or entity key withoutOwnrecord-Brand).permission_denied: You can't change this brand's wallets (wallets:write: owners, admins and support).livemode_not_allowed: You have no live-mode access, so you can't use live wallets.
Example
The job was cancelled
Arguments:
{
"id": "res_01m4ty7w2a5qx7mhd9k8w6s0b4",
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
"idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
Result output:
{
"id": "res_01m4ty7w2a5qx7mhd9k8w6s0b4",
"object": "reservation",
"brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
"livemode": false,
"walletId": "wal_01m4ty7w2a5qx7mhd9k8w6s0b1",
"kind": "single",
"status": "released",
"amount": {
"amount": "3000000",
"currency": "EUR"
},
"captured": {
"amount": "0",
"currency": "EUR"
},
"outstanding": {
"amount": "0",
"currency": "EUR"
},
"expiresAt": "2026-10-16T10:20:00.000Z",
"closedAt": "2026-10-16T10:16:00.000Z",
"idempotencyKey": "job-8841",
"metadata": {
"job": "8841"
},
"createdAt": "2026-10-16T10:15:00.000Z",
"updatedAt": "2026-10-16T10:16:00.000Z"
}