ZanoraBetaDocs

Set up

Top up by card, or send USDC on Base to a deposit address. Money is credited when the payment rail confirms it. Ask the gateway which options it offers before you choose one.

An agent can't fund its own wallet. Moving money in is always a person's action, in the console or through your own code on their behalf.

Ask what's available first

Funding options depend on which payment providers the gateway has connected. Ask instead of guessing:

Terminal
curl -s https://api.zanora.dev/v1/rail/providers -H "x-api-key: $WKEY"
JSON
{
  "sandbox": false,
  "providers": [
    { "id": "tprov_stripe", "name": "stripe", "roles": ["funding"], "currencies": ["USD"],
      "rails": ["card"], "topUps": true, "depositAddresses": false, "faucet": false, "status": "active",
      "client": { "vendor": "stripe", "publishableKey": "pk_live_…", "testMode": false } },
    { "id": "tprov_circle", "name": "circle", "roles": ["funding", "payout"], "currencies": ["USDC"],
      "rails": ["usdc_base"], "topUps": false, "depositAddresses": true, "faucet": false, "status": "active" }
  ]
}
FlagMeansUse
topUps: trueyou can charge a card into the walletTop-up
depositAddresses: trueyou can get a USDC address for the walletDeposit address
faucetfree test USDC, on testnet deployments only. Always false on the hosted platform—
sandboxa development gateway with mock rails. Always false on the hosted platform—
clientthe public key a browser card form is set up withYour own card form

Top-up (card)

Bank funding is in preview

ACH (bank debit) funding exists in the API as a preview rail and isn't offered in the console. Use a card or USDC.

The console's Fund button is the easiest route. It opens Stripe's card form, handles 3-D Secure if your bank asks for it, and waits until the payment is confirmed. It's on each wallet in Buyer → Wallets, and Create agent offers it on its confirmation screen, preset to the funding method you picked. From the API:

Terminal
curl -s -X POST https://api.zanora.dev/v1/wallets/$WAL/topup -H "x-api-key: $WKEY" \
  -H 'content-type: application/json' -d '{"amountMicros":"50000000"}'
# → {"intent":{"externalRef":"pi_…","asset":"USD","amountMicros":"50000000","status":"requires_payment_method","clientSecret":"pi_…_secret_…"}}

This call doesn't charge anything by itself: it returns an intent that a card form completes with its clientSecret. Without a confirmationToken it must be at least $50 (see Your own card form and Limits). The wallet is credited when the card processor confirms, not when this call returns. If the card is declined, status is declined with a declineReason. That's an answer, not an error.

An agent that runs out of money can't add any, but it can ask you to. It creates a one-time link for a card top-up, and you pay on that page. No sign-in is needed:

Terminal
curl -s -X POST https://api.zanora.dev/v1/wallets/$WAL/funding-links -H "x-api-key: $AGENT_KEY" \
  -H 'content-type: application/json' -d '{"amountMicros":"25000000"}'
# → 201 {"link":{"id":"flk_…","amountMicros":"25000000","expiresAt":"…","status":"open","url":"https://console.zanora.dev/pay#flk_…","token":"flk_…"}}

From an MCP client the tool is zanora_request_funding. The page shows the amount, the agent, the workspace and your account's email (partly hidden), then Stripe's card form. Check those before paying: anyone can send you a link to their wallet.

  • Scope funding:request, which agent keys have. It can create a link and nothing else: the money still comes from a person entering a card.
  • One payment, 30 minutes. The amount is fixed when the link is made. A declined card can be retried on the same link; once it's paid, it's spent.
  • Same rules as the Fund button. The card minimums, the daily caps and the hourly limit on top-ups all apply. A link under the card minimum, or on a deployment with no card payments, is refused when it's created.
  • At most 5 unpaid links per wallet at a time.

Your own card form

Mount Stripe's Payment Element↗ with the amount and the client.publishableKey from GET /v1/rail/providers. Turn the card into a ConfirmationToken in the browser and send that with the top-up. The gateway reads where the card was issued, then confirms the payment. The card number goes from the buyer's browser to Stripe and never reaches Zanora.

JavaScript
const stripe = Stripe(publishableKey);                    // <script src="https://js.stripe.com/v3/">
const elements = stripe.elements({ mode: "payment", amount: 3000, currency: "usd", paymentMethodTypes: ["card"] });
elements.create("payment").mount("#card");
// on submit:
await elements.submit();
const { confirmationToken } = await stripe.createConfirmationToken({ elements });
const { intent } = await post(`/v1/wallets/${walletId}/topup`, { amountMicros: "30000000", confirmationToken: confirmationToken.id });
if (intent.status === "requires_action") await stripe.handleNextAction({ clientSecret: intent.clientSecret }); // 3-D Secure

A top-up of $50 or more can still use the older flow: create the intent without a token, then mount the form with its clientSecret and call stripe.confirmPayment. Below $50 that flow is refused, because the gateway can't see the card before the browser charges it.

Top-ups are card only. Then poll GET /v1/rail/transactions for the row whose externalRef is the intent's, until it reads confirmed.

Limits

Funding has limits, for cards and USDC alike. The defaults:

LimitDefault
the smallest card top-up$25
the smallest top-up on a card issued outside the US$50
a single top-up or USDC deposit$100
total funding across your workspace's wallets in 24 hours$100
top-ups started per workspace per hour10

A card is charged in whole cents, so a top-up amount must be a whole number of cents ("25000000", not "25000001"); anything finer is refused, never rounded. USDC is credited to the last on-chain unit. USDC deposits have no minimum. A card top-up below $25 is refused with VALIDATION_FAILED before anything is charged. So is one below $50 on a card issued outside the US, because the processor charges more for those cards. The gateway reads the card's issuing country from the confirmation token, and if it can't tell where the card was issued, it applies the $50 minimum. GET /v1/rail/providers returns the minimums this gateway enforces (minimums.cardTopUpMicros, minimums.internationalCardTopUpMicros).

The 24-hour total counts card top-ups and USDC deposits together, and on the hosted platform it's $100. It's also the most your workspace can spend in a day, and GET /v1/workspaces/:id/budget reports it as maxDailyBudgetMicros ("100000000").

  • A card top-up over a limit is refused with LIMIT_EXCEEDED (429) before anything is charged.
  • A USDC deposit can't be refused, because it has already arrived. One that would pass a limit is held: it shows in GET /v1/rail/transactions with status: "held", and the wallet isn't credited yet. It's credited automatically once your 24-hour total has room for it. A single deposit over $100 never fits, so it stays held until Zanora support credits it or sends it back.

Email support@zanora.dev if you need higher limits.

Disputes

If a card top-up is disputed with the card issuer (a chargeback), the amount is taken back out of the wallet when the processor withdraws it. This is recorded as a new ledger entry, and the balance can go below zero if the money was already spent. A wallet with a negative balance can't buy anything until it's topped up.

When the dispute is decided:

  • In Zanora's favour: the processor reinstates the funds and the amount is credited back to the wallet.
  • In the buyer's favour: the card issuer has already returned the money to the card, so the wallet stays debited. Zanora reviews what the disputed money paid for, and where a seller's earnings from it are returned, the buyer is refunded the amount paid to the seller: the purchase price less Zanora's platform fee, and at most what that seller still holds. Zanora's platform fee is non-refundable, so it stays part of any negative balance.

USDC deposit address

Terminal
curl -s -X POST https://api.zanora.dev/v1/wallets/$WAL/deposit-address -H "x-api-key: $WKEY" \
  -H 'content-type: application/json' -d '{"asset":"USDC","chain":"base"}'
# → {"address":{"id":"…","walletId":"wal_…","treasuryProviderId":"tprov_circle","chain":"base","asset":"USDC","address":"0x5f3a…","createdAt":"…"}}
  • Calling it again returns the same address, never a second one.
  • Send only USDC on Base to it. Other tokens and chains aren't credited.
  • The wallet is credited in USD at 1:1 once the transfer is confirmed.
  • A deposit that would pass your funding limits is held, and credited once it fits.
  • If USDC is trading more than 0.5% away from $1 when the deposit arrives, it's held, not credited at a guessed rate. Zanora checks held deposits again automatically, and credits them once the price is back within the band.

GET /v1/wallets/$WAL/deposit-addresses lists a wallet's addresses. The zanora_deposit_addresses MCP tool reports them to an agent, so it can tell its user where to send money.

Checking it landed

Terminal
curl -s https://api.zanora.dev/v1/wallets/$WAL/balance -H "x-api-key: $WKEY"
curl -s https://api.zanora.dev/v1/rail/transactions -H "x-api-key: $WKEY"   # each deposit and its state

A balance of zero right after you fund is normal. Card payments usually confirm within seconds and USDC within minutes.