ZanoraBetaDocs

Endpoints

Create and list wallets, read balances and ledgers, fund them, freeze or unfreeze them, and replace an agent's signing key.

POST/v1/wallets

Scope wallets:write. Creates a wallet in your workspace (workspaceId comes from your key).

FieldTypeRequiredNotes
ownerType"agent" | "workspace"yesagent for anything that buys
ownerIdstringyesyour label for the agent, e.g. invoice-bot
publicKeyPemstringagents: yesthe ed25519 public key that payment proofs are checked against
GET/v1/wallets

Scope wallets:read. Your wallets: { wallets }. A provider key returns its settlement wallet.

GET/v1/wallets/:id

Scope wallets:read. Wallet details: owner, status, and whether it's frozen.

GET/v1/wallets/:id/balance

Scope wallets:read. { balanceMicros, availableMicros, withdrawableMicros, clearingMicros, heldForDisputesMicros, clearingPeriodMs, currency }. Available is the balance minus holds. Withdrawable is what a payout can take now: the balance minus earnings still in the clearing period and anything held for a card dispute. Everything is calculated from the ledger every time. The balance can be negative after a card dispute.

GET/v1/wallets/:id/ledger

Scope wallets:read. Every ledger entry for the wallet (amounts as strings). Refunds and corrections appear as new entries. Nothing is removed.

POST/v1/wallets/:id/topup

Scope wallets:write. Starts a card payment into the wallet (bank debit is in preview). Body { amountMicros, confirmationToken?, asset?, treasuryProviderId? }. Returns { intent: { externalRef, status, amountMicros, hostedUrl?, clientSecret?, declineReason? } }. The wallet is credited when the payment processor confirms.

With confirmationToken (a Stripe ConfirmationToken made in the buyer's browser), the gateway checks the card and confirms the payment itself: status is succeeded, processing, declined, or requires_action (finish 3-D Secure with clientSecret). Without it, you get an unconfirmed intent to finish with clientSecret. A top-up under the international minimum ($50) must send confirmationToken, because a card issued outside the US is refused below it, before anything is charged. See your own card form.

Returns 400 VALIDATION_FAILED below a minimum (details.limit says which), 501 NOT_SUPPORTED if no connected provider takes top-ups, and 429 LIMIT_EXCEEDED over a top-up limit.

POST/v1/wallets/:id/funding-links

Scope funding:request (agent keys have it). Creates a one-time page where a person pays a card top-up into this wallet. Body { amountMicros }. Returns 201 { link: { id, walletId, amountMicros, currency, expiresAt, status: "open", url, token } }. url is the console's pay page with the token after #, so the token never reaches a server log. The link pays once and expires after 30 minutes.

Returns 501 NOT_SUPPORTED if no connected provider takes card top-ups, 400 VALIDATION_FAILED under the card minimum, and 429 LIMIT_EXCEEDED when the wallet already has 5 unpaid links. See a payment link, from your agent.

POST/v1/funding-links/lookup

No auth. What the pay page shows. Body { token }, so the token stays out of URLs. Returns { link: { id, amountMicros, currency, status, expiresAt, credited }, payee: { workspaceName?, agentName?, accountEmail? }, card?: { vendor, publishableKey, testMode }, minimums? }. status is open, paying, charged or expired; credited turns true once the card processor confirms. accountEmail is partly hidden. An unknown or altered token is 404.

POST/v1/funding-links/pay

No auth. Pays the link with a card the browser tokenised. Body { token, confirmationToken }. The amount and wallet come from the link, never from the body. Returns { link: { id, status }, intent }, with intent as for /topup. A declined card leaves the link open to try another one; a paid link refuses a second payment with 400. Card minimums, caps and the hourly top-up limit apply as they do to /topup. Both public routes are limited to 30 requests a minute per IP.

POST/v1/wallets/:id/deposit-address

Scope wallets:write. Gets or creates the wallet's USDC deposit address. Body is optional: { asset?: "USDC", chain?: "base", treasuryProviderId? }. It's idempotent: asking twice returns the same address. Returns { address: { address, chain, asset, walletId, treasuryProviderId, createdAt } }.

GET/v1/wallets/:id/deposit-addresses

Scope wallets:read. The wallet's registered deposit addresses.

POST/v1/wallets/:id/faucet

Scope wallets:write. Testnet deployments only, so not available on the hosted platform. Sends test USDC to the wallet's deposit address. Body is optional: { address? }.

POST/v1/wallets/:id/fund

Scope wallets:write. Not available on the hosted platform, where it returns 501 NOT_SUPPORTED: real funding comes through a card or a USDC deposit. It exists for Zanora's internal development gateways, where it credits a wallet against a simulated deposit.

POST/v1/wallets/:id/key

Scope wallets:write. Your own agent wallets only. Replaces the wallet's signing key, which is how you recover from a lost or leaked private key. Body { publicKeyPem }: an ed25519 public key PEM. A private key, a non-ed25519 key, or anything else is refused with VALIDATION_FAILED. Returns the wallet, with keyReplacedAt and keyUsableAt.

  • The old key stops verifying immediately.
  • The new key can't pay until keyUsableAt (24 hours by default). Purchases before then get WALLET_KEY_COOLING_DOWN (409), and nothing is charged.
  • The replacement pages the platform's operators (wallet_key_replaced).
  • Sending the key the wallet already has is a no-op: no cool-down, no alert.
  • SDK: client.replaceWalletKey(walletId, publicKeyPem).
POST/v1/wallets/:id/freeze

Scope wallets:write. Blocks all outgoing payments. No body.

POST/v1/wallets/:id/unfreeze

Scope wallets:write. Reverses a freeze. No body.

POST/v1/wallets/:id/resume-provider

Scope wallets:write. Lifts the pause on a seller that failed three calls in a row for this wallet (5xx, thrown handler, MCP isError or timeout; 4xx responses don't count). Body { providerId }. Returns { ok, paused }.

GET/v1/wallets/:id/breaker/:providerId

Scope wallets:read. Whether a seller is paused for this wallet: { walletId, providerId, paused }.