Skip to main content
The company balance flips the default user funded model: your company prefunds one pooled balance, and when a connected user’s agent creates a card, Agentcard moves exactly that card’s amount from your pool into the user’s spending power. Your users never see a funding step, and you stay in the transaction loop to collect from your own customer first.
The flow of funds is chosen when you create the company (user funded or company balance). A company owner can switch models from the dashboard’s Company balance page, but only until the company first moves money (a card is funded, a transfer runs, or a deposit lands); after that the choice locks. Companies on the company balance model get the full test-mode loop immediately and free; live usage (the real pool, real transfers) requires an active paid subscription, the same one that unlocks production clients.

The flow of funds

Company balance flow of funds diagram

Company balance: from create_card to the agent paying the merchant

Step by step, for one card:
  1. A connected user’s agent calls create_card with the company flow. Agentcard checks your pool covers the amount (plus fees) and reserves it.
  2. You receive a card_flow.started webhook. This is your moment to collect: place a hold on your customer’s payment method in your own billing system.
  3. You report the outcome with the confirm_collection tool — outcome succeeded, or failed to release the reservation.
  4. Agentcard transfers the amount from your balance into the user’s spending power (about 30 seconds on-chain) and emits transfer.initiated, then transfer.completed.
  5. 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 see card_flow.failed with funds_status: released_to_headroom).
  6. The card mints and card.created fires with the transfer_id, then the agent pays the merchant as usual.
  7. If the card closes with unused balance, transfer.released fires 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. For a user you won’t fund again, pull it back into your pool with a recovery.
Companies that prefer not to confirm each funding can set ack_mode: auto_approve (dashboard or CLI): step 2 still fires as a webhook, but the transfer proceeds without waiting.

Fund the balance

Set up the balance once (dashboard: Company balance → Set up balance, or agent-cards companies balance provision --org <org-id>). Then fund it two ways — both credit the same pool and fire the same wallet.funded webhook:
  • Apple Pay / Google Pay (dashboard → Company balance → Add funds, admins only). A hosted card checkout — no crypto wallet needed. Any debit or credit card inside Apple/Google Pay works (a Mercury card, for example). The payment provider may run a quick identity check on the payer the first time. 22–10,000 per transaction; no lifetime purchase caps. Agentcard covers the provider fee (up to a cap), so the full amount lands.
  • USDC on Base — send to the pool’s deposit address. No caps; this is the rail for recurring, treasury-scale funding.
Deposits are detected automatically. Set a low-balance threshold to get wallet.balance.low before a card creation ever fails for insufficient funds. Creating the live pool and funding it both require the active subscription; without one these calls return 403 subscription_required (test 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 same create_card it always has. Two additions:
  • An optional funds_source (onramp_flow or company_flow). Most companies set their default funds source to company_flow so agents never pass it.
  • While funding is in flight, create_card returns 202 { "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 as 422 funding_not_approved.
Server-initiated issuance works too: your backend can call 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, cardholder, or card type is rejected with 409 idempotency_key_reuse; use a fresh key for each new request.

Reusable (multi-use) cards

Company funding works for both card types. Pass type: "multi_use" (optionally with expires_at) to mint a reusable card from the pool: it survives repeated charges and closes when its balance is spent (card.closed with reason balance_exhausted) or its expiry passes. Everything above applies unchanged — one funding, one collection, one card.created carrying the type.

Topping up a reusable card

Raise an existing company-funded multi-use card from the pool with POST /api/v1/cards/{id}/topups:
Each top-up is its own transfer through the same machinery — your call is the collection confirmation, the pool moves the amount into the user’s spending power, and the card’s limit and balance rise by exactly amount_cents. The response is 200 with the updated card and the transfer_id, or 202 funding_in_progress (retry with the same Idempotency-Key to attach and collect the result).
  • Idempotency-Key is required (400 without one). A replayed key returns the applied result instead of topping up twice; reusing a key with different parameters — including across a card creation and a top-up — is a 409 idempotency_key_reuse.
  • The webhook sequence is the one you already handle: card_flow.started (with kind: "top_up" and the card_id) → transfer.completed (your billing anchor, funding is final here) → card.updated carrying the transfer_id as the applied confirmation.
  • If the card closes while a top-up is in flight, the flow ends with card_flow.failed (reason: "card_closed", funds_status: "released_to_headroom"): your collection stands and the value offsets that user’s next company-funded allocation.

Releases with multiple fundings

A reusable card can carry several allocations (its mint plus top-ups). When it closes or expires partially spent, the unused remainder is released across those transfers oldest-first — you receive one transfer.released per funded transfer, and their released_cents sum to exactly the unused remainder. Fully-consumed allocations release nothing. As always, released value stays as that user’s spending power, offsets their next company-funded allocation, and is recoverable.

Recover unused balance

Released value normally finds its way back on its own: the next company-funded card (or top-up) for that user draws on their existing spending power first, and your pool only sends the difference. Nothing to build — it’s automatic, and GET /api/v1/cardholders/{id} reports the current headroomCents so you can reconcile it. For a user you won’t fund again (offboarding, a one-off purchase that fell through), pull the residual back into your pool with a recovery:
Omit amount_cents to recover the full current headroom (open cards and in-flight claims subtract from it; asking for more is a 422 insufficient_headroom with the available amount). The same primitive is the recover_funds tool on your company MCP connection, and list_recoveries / GET /api/v1/wallet/recoveries track them.
  • Live recoveries are fulfilled by the Agentcard team, usually within a business day: the row starts requested, and recovery.completed fires when the funds land back in your pool (the deposit also shows up in your balance like any funding). If a recovery can’t be fulfilled it ends rejected with a failure_reason, and the value keeps netting as before — money is never lost in between.
  • Test mode completes instantly against the mock pool, so you can integrate the webhook loop end to end.
  • A recovery and a new card can never jointly spend the same headroom: in-flight recoveries are subtracted from netting, and open cards are subtracted from the recovery ceiling.

Balance tools

Available with your company credentials: Transfers move through public states pending (substates: awaiting_collection, collection_confirmed, transferring), funded, consumed, reclaimed, and failed (with a failure_reason).

Try it in test mode

The whole loop runs in test mode with no real money: fund the mock pool from the dashboard’s Company balance page (test mode), then run create_card with a test credential. The same webhooks fire with livemode: false, the transfer simulates in about 2 seconds, and the card is a mock. Headroom behaves like live too: close a test card unused and the next test card for that user draws on the released value first (a fully-covered funding completes immediately with transferred_cents: 0), and recoveries complete instantly. Integrate the webhook and collection confirmation exactly as you would in production.

Events