The flow of funds is chosen once, when you create the organization (user funded or company wallet) and cannot be changed later. Company-wallet orgs get the full sandbox loop immediately and free; live usage (the real pool, real transfers) requires an active paid subscription, the same one that unlocks production clients. Want to switch models? Create a new organization and pick the other flow.
The flow of funds

- A connected user’s agent calls
create_cardwith the company flow. Agentcard checks your pool covers the amount (plus fees) and reserves it. - You receive a
card_flow.startedwebhook. This is your moment to collect: place a hold on your customer’s payment method in your own billing system. - You report the outcome with the
confirm_collectiontool — outcomesucceeded, orfailedto release the reservation. - Agentcard transfers the amount from your wallet into the user’s spending power (about 30 seconds on-chain) and emits
transfer.initiated, thentransfer.completed. - Capture your customer’s payment when you receive
transfer.completed. From this point the funding is final: even if the card mint failed afterward, the money stays as that user’s spending power and automatically offsets their next card (you would seecard_flow.failedwithfunds_status: released_to_headroom). - The card mints and
card.createdfires with thetransfer_id, then the agent pays the merchant as usual. - If the card closes with unused balance,
transfer.releasedfires so you can release or refund your own hold. The unused amount stays as that user’s spending power and reduces what your pool sends on their next card.
ack_mode: auto_approve (dashboard or CLI): step 2 still fires as a webhook, but the transfer proceeds without waiting.
Fund the wallet
Provision the wallet once (dashboard: Company wallet → Create company wallet, oragent-cards-admin wallet provision --org <org-id>), then send USDC on Base to its deposit address. Deposits are detected automatically and confirmed with a wallet.funded webhook. Set a low-balance threshold to get wallet.balance.low before a card creation ever fails for insufficient funds.
Creating the live pool and running live transfers both require the active subscription; without one these calls return 403 subscription_required (sandbox credentials and the mock pool keep working). Subscribe from the dashboard’s Go live page.
The create_card protocol
The end user’s agent calls the samecreate_card it always has. Two additions:
- An optional
funds_source(onramp_floworcompany_flow). Most orgs set their default funds source tocompany_flowso agents never pass it. - While funding is in flight,
create_cardreturns202 { "status": "funding_in_progress", "transferId": "owt_…", "retry_after_seconds": 10 }. The agent retries with the same arguments and attaches to the in-flight funding, returning the card when it is ready. A retry that lands after the mint still returns that card (for about 2 minutes, while it is unused) instead of opening a second funding. Declined or timed-out collections surface as422 funding_not_approved.
create_card itself with funds_source: "company_flow". Your own call counts as the collection confirmation, and passing an idempotency_key makes retries attach instead of double-funding. Reusing a key with a different amount or cardholder is rejected with 409 idempotency_key_reuse; use a fresh key for each new request.
Wallet tools
Available with your org credentials:| Action | Tool |
|---|---|
| Balances, deposit address, settings | get_company_wallet |
| List transfers (filter by status) | list_transfers |
| Confirm or fail your collection | confirm_collection |
Flow settings (ack_mode, thresholds) | Dashboard → Company wallet, or the admin CLI |
| Add sandbox test funds | Dashboard → Company wallet (sandbox mode) |
pending (substates: awaiting_collection, collection_confirmed, transferring), funded, consumed, reclaimed, and failed (with a failure_reason).
Test it in sandbox
The whole loop runs in test mode with no real money: fund the mock pool from the dashboard’s Company wallet page (sandbox mode), then runcreate_card with a sandbox credential. The same webhooks fire with livemode: false, the transfer simulates in about 2 seconds, and the card is a mock. Integrate the webhook and collection confirmation exactly as you would in production.
Events
| Event | Fires when | What you do |
|---|---|---|
card_flow.started | A user’s card creation reserved your pool | Hold your customer’s payment method, then confirm collection |
transfer.approved | Collection confirmed (by you or auto-approve) | Nothing, informational |
transfer.initiated | The on-chain transfer was submitted | Nothing, informational |
transfer.completed | Funds are the user’s spending power | Capture your customer’s payment |
card.created | The card minted (includes transfer_id) | Final confirmation |
transfer.failed | Funding aborted before the card (insufficient_org_funds, ack_timeout, ack_denied, transfer_failed) | Release your hold |
card_flow.failed | Mint failed after funds moved (funds_status: released_to_headroom) | Keep your capture, the user keeps the spending power |
transfer.released | Card closed or expired with unused balance | Release or refund your own hold |
wallet.funded | A deposit landed in your pool | Reconcile your treasury |
wallet.balance.low | The pool dropped under your threshold | Top up |