Skip to content

Capture a reservation

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

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

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.

At a glance

  • REST: POST /api/v1/reservations/{id}/capture
  • MCP tool: reservations_capture
  • Operation ID: reservations.capture
  • Scope: Brand
  • Permission: wallets:write
  • Risk tier: write
  • Dry run: Supported (send Ownrecord-Dry-Run: true)
  • Idempotency: Idempotency-Key required
  • Modes: Test and live
  • Authentication: API key with the wallets:write scope
  • Since: M6

Request

Path parameters

FieldTypeDescription
idres_… IDThe reservation (res_…).

Body

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

FieldTypeRequiredDescription
amountobjectYesWhat was used: at most what is still reserved.
amount.amountstringYesInteger micros as a string (5000000 = 5.00).
amount.currencystringYesISO 4217 currency code: the wallet's currency.
usagearray of objectNoWhat the amount was spent on, one line per meter. Without it the whole amount is usage of the meter default.
usage[].meterstringYesWhat was used, e.g. api.calls: the revenue goes to this meter.
usage[].quantitystringYesHow much of the meter was used, as an integer string.
usage[].amountobjectYesWhat this line costs, in the wallet's currency. The lines add up to the capture's amount.
usage[].amount.amountstringYes
usage[].amount.currencystringYes
usage[].attributesobjectNoOptional dimensions of the event, e.g. { "model": "large" }; usage is aggregated per day and attribute set.
finalbooleanNoRelease the rest and close the reservation (default true). false keeps it open for more captures until it expires.
idempotencyKeystringNoUnique per reservation: the same key and request returns the same capture (default: the request's Idempotency-Key).

Response

200 OK 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

Capture €1.25 of €3 for 250 API calls and release the rest

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

{
  "amount": {
    "amount": "1250000",
    "currency": "EUR"
  },
  "usage": [
    {
      "meter": "api.calls",
      "quantity": "250",
      "amount": {
        "amount": "1250000",
        "currency": "EUR"
      }
    }
  ]
}

Response (200):

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

Errors

CodeHTTP statusWhenWhat to do
validation_failed400The amount isn't positive, or the usage lines don't add up to it or are malformed.Fix the fields listed in details.issues and send the request again.
resource_not_found404This brand has no such reservation in this mode.Check the ID and that your credentials are scoped to the right organization, entity, brand and mode.
capture_exceeds_reservation400The amount is more than is still reserved (details.outstanding).Capture at most the reserved amount that is still open (details.outstanding, integer micros), or reserve more first.
reservation_expired409The reservation's expiresAt has passed: reserve again.Reserve the amount again and capture the new reservation. Expired reservations are released automatically.
reservation_not_open409The reservation was already captured, released or expired.Create a new reservation. A reservation is closed once it is captured, released or expired (details.status).
wallet_currency_mismatch400The amount or a usage line isn't in the wallet's currency.Use the wallet for this currency. A customer has one wallet per currency (details.expected, details.actual).
usage_meter_billed_in_arrears400A usage line names a meter a metered price bills in arrears (prepaid usage needs another meter).Report prepaid (wallet) usage on a meter that no metered price names, e.g. api.calls.prepaid (details.meter).
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.

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.