Documentation

Wallet

The wallet is the payment source for every order. It has separate USD, EUR and GBP balances. The API never auto-converts a wallet balance to cover an order in another currency.

Code
GET /v1/wallet

Required scope: wallet:read.

Balances

JSONCode
{ "currencyMode": "native", "wallet": [ { "currency": "USD", "postedMinor": 10000, "heldMinor": 0, "availableMinor": 10000 }, { "currency": "EUR", "postedMinor": 0, "heldMinor": 0, "availableMinor": 0 }, { "currency": "GBP", "postedMinor": 0, "heldMinor": 0, "availableMinor": 0 } ] }
FieldMeaning
postedMinorCredited funds, including completed funding and refunds, minus completed debits.
heldMinorFunds reserved by an operation. Current wallet orders debit immediately, so this is normally 0.
availableMinorpostedMinor - heldMinor. The order check uses this value.

Amounts are integer minor units. currencyMode: native means balances are held in their own currency; no conversion is applied at read time.

Paying for an order

An order is paid from the wallet in the quote currency. There are three possible outcomes:

  1. Enough available credit: the purchase is created, the wallet is locked, the debit and ledger entry are written in the same transaction, and the order returns completed or processing.
  2. Not enough credit: the API returns 402 insufficient_funds. No purchase, child order or debit is created.
  3. Price or stock moved: the API returns 409 price_changed or 409 out_of_stock. No purchase or debit is created.

The target contract changes case 2 to 402 payment_required with a funding_url. That shape is Planned and not returned by every deployment yet. Handle both insufficient_funds and payment_required.

Fund the wallet

Code
POST /v1/wallet/funding Idempotency-Key: fund-2026-001

Required scope: wallet:fund.

JSONCode
{ "amountMinor": 10000, "currency": "USD" }

amountMinor must be an integer from 1 to 1,000,000. currency must be USD, EUR or GBP. The response is a hosted Stripe Checkout URL and a payment operation id.

JSONCode
{ "paymentOperationId": "88888888-8888-4888-8888-888888888888", "checkoutUrl": "https://checkout.stripe.com/c/pay/REDACTED", "amountMinor": 10000, "currency": "USD" }

The wallet is not credited when this response is returned. Credit is issued only after the signed Stripe webhook confirms a paid session, exactly once per operation.

Gating

Stripe funding is behind a deployment flag and a configured Stripe account. When it is disabled:

StatusCodeMeaning
501capability_unavailableFunding is deliberately disabled.
503payment_pendingStripe is not configured.
409idempotency_conflictThat funding key is already paid or already used.

Sandbox credit is added by RoamWorks staff. A live partner receives written wallet terms before funding is enabled. Direct card purchase and saved payment methods are not available through the partner API.

Funding rules

  • Use a new Idempotency-Key per funding intent. Reuse it only for a retry of that intent.
  • Do not create a second funding session for the same intent with a new key.
  • Do not treat checkoutUrl as proof of payment. Poll the wallet or wait for the webhook.
  • The API and the dashboard share the wallet. A dashboard top-up and an API top-up both appear in the same balances and ledger.
  • The funding hook does not accept card data and never returns card data. The card form is hosted by Stripe.
  • Treat checkoutUrl as a sensitive, short-lived URL. Do not post it in a public channel or send it to an unrelated service.

Sandbox

Sandbox wallets are isolated from live. Use a sandbox key and the test-mode funding path when it is enabled, or ask RoamWorks support for test credit. Sandbox balances and upstream fulfilment are not real money or real inventory.

  • Orders for the debit path.
  • Webhooks for funding and order events.
  • Errors for 402 and 409 handling.