Skip to content

Reserve funds on a wallet

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

POST/api/v1/wallets/{id}/reservations

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).

At a glance

  • REST: POST /api/v1/wallets/{id}/reservations
  • MCP tool: reservations_create
  • Operation ID: reservations.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

FieldTypeDescription
idwal_… IDThe wallet (wal_…).

Body

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

FieldTypeRequiredDescription
amountobjectYesThe amount to hold, in the wallet's currency.
amount.amountstringYesInteger micros as a string (5000000 = 5.00).
amount.currencystringYesISO 4217 currency code: the wallet's currency.
ttlSecondsinteger (1 to 3600)YesHow long the hold lasts, 1 to 3600 seconds.
metadataobjectNoYour own key-value pairs (at most 50 keys, values of at most 500 characters).
idempotencyKeystringNoUnique per wallet: the same key and request returns the same reservation (default: the request's Idempotency-Key).

Response

201 Created with a JSON object:

FieldTypeDescription
idstring
object"reservation"
brandIdbrd_… ID
livemodeboolean
walletIdwal_… ID
kindone of single, leasesingle (§17.2) or lease (§17.3).
statusone of active, expired_pending, captured, released, expiredactive, captured, released, expired (or expired_pending for leases).
amountmoneyWhat was reserved.
capturedmoneyWhat was captured so far.
outstandingmoneyWhat is still held: amount − captured while open, zero once closed.
expiresAtdate-time
closedAtdate-time or null
idempotencyKeystring
metadataobject
createdAtdate-time
updatedAtdate-time

Examples

Hold €3 for five minutes

HTTP
POST /api/v1/wallets/wal_01m4ty7w2a5qx7mhd9k8w6s0b1/reservations
Authorization: Bearer or_test_…
Idempotency-Key: 3f1c9a52-7d0e-4b8f-9a61-0c2d5e8b7f14
Content-Type: application/json

{
  "amount": {
    "amount": "3000000",
    "currency": "EUR"
  },
  "ttlSeconds": 300,
  "idempotencyKey": "job-8841",
  "metadata": {
    "job": "8841"
  }
}

Response (201):

JSON
{
  "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"
}

Errors

CodeHTTP statusWhenWhat to do
validation_failed400The amount isn't positive, ttlSeconds isn't 1 to 3600, or the metadata or idempotency key is malformed.Fix the fields listed in details.issues and send the request again.
resource_not_found404This brand has no such wallet in this mode (wallets belong to one brand and one mode).Check the ID and that your credentials are scoped to the right organization, entity, brand and mode.
insufficient_funds402The wallet's spendable credits (unexpired, not already reserved) are less than the amount (details.requested, details.available). HTTP 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_capped402The 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.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_mismatch400The 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_frozen409The wallet is frozen: it takes no new reservations.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_closed409The wallet is closed.Reopen the wallet with wallets.update (status: active). A purchase of credits in its currency reopens it, too.
idempotency_key_reused409The idempotency key was used before for another request.Use a new Idempotency-Key for a different request, or resend the original request unchanged.
version_conflict409The wallet changed at the same moment (another reservation, capture or grant); retry.Fetch the object again and retry the change.
scope_required400The request names no brand (an org or entity key without Ownrecord-Brand).Call the operation with credentials or a target at its level: an organization, a legal entity, a brand or the platform (details.required_scope).
permission_denied403You can't change this brand's wallets (wallets:write: owners, admins and support).Ask an organization owner or admin for a role or scope that allows this action.
livemode_not_allowed403You have no live-mode access, so you can't use live wallets.Use test mode, or credentials for the target's mode. Developers need live access granted by an organization owner or admin.
entitlement_required403The 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.

It can also return these common errors: api_version_unsupported, invalid_json, unsupported_media_type, authentication_required, api_key_invalid, idempotency_key_required, idempotency_in_progress, approval_required, approval_rejected, approval_expired, stale_approval, approval_policy_denied, rate_limited, internal_error.