Tell your agent
What the account holder sets up
Three steps belong to the human, once per account. An agent can report where each one stands and hand the human the right link, but it cannot complete them.- Laso identity verification. This is the one card product that requires it.
GET /get-kyc-statusreports whether the holder has completed it, andGET /get-kyc-linkreturns the link to hand them. The two non-reloadable cards read no verification status at order time. - Linking the card issuer. The issuer sends the holder a one-time code, which they enter in your interface. See Linking the card issuer below.
- Accepting the issuer’s terms and sharing their verification with it. The issuer gates card creation on the holder’s Laso verification being shared with it, and on the holder accepting its terms. This happens in the dashboard at
https://laso.finance/agent/dashboard/verified/card, and may ask the holder to type details their verification did not capture. There is no API route for it, by design: identity forms are completed by the human. If card creation keeps failing after the issuer is linked, send the holder to that page.
Lifecycle
multi_use card (reusable: true) stays open across charges until its spend limit is consumed, and the holder can top it up rather than issuing a new card each time. If a holder has a reloadable card, prefer it over ordering a fresh single-load card per checkout.
Linking the card issuer
The link is a one-time code the card issuer sends to the account holder. The account holder, never the agent, receives and enters it. Send your human to their own inbox and have them type the code into your interface. Never try to read their email or messages yourself.GET /get-card-issuer-statusreportsnot_started,code_sent,linked, orkyc_required(with akyc_urlfor the human; linking requires Laso identity verification).POST /start-card-issuer-linksends the code. An account with an email on file gets it there and sends an empty body; an account with no email sends exactly one ofemailorphone. The response names the destination masked, assent_to.POST /verify-card-issuer-linkwith{"code": "123456", "consent_accepted": true}completes it. Show the account holder the card issuer’s connection disclosure next to the code input before sendingconsent_accepted.
400 invalid_code with attempts_left, then 429 too_many_attempts). At most 5 codes can be sent per account per hour (429 rate_limited with Retry-After). Both routes take an Idempotency-Key, and both answer status: "linked" when the account is already linked, so calling twice is safe.
Unlinking
POST /disconnect-card-issuer removes the link, the same as the disconnect button in the dashboard. The card balance stays with the card issuer, but every reloadable card route and saved card purchases stop working until the holder links again with a fresh code. Confirm with the account holder before calling it.
Topping up
You can top the balance up yourself. The simplest way isGET /fund-card-balance?amount=X, a paid route that loads the balance directly: pay from Base or Solana and Laso bridges the payment to the holder’s deposit address at the card issuer, so a managed agent wallet holding USDC on Solana can fund a card without any chain juggling. The price is exactly the amount loaded. If the account already holds USDC in its Laso account balance (for example, a paid request that could not be fulfilled), POST /fund-card-from-account-balance with {"amount": 25} loads it from there for free, debiting exactly the amount loaded. To move the USDC yourself instead, GET /get-card-deposit-address returns the holder’s own deposit address at the card issuer, and USDC on Base sent there credits their balance. The holder can also pay in with Apple or Google Pay in the dashboard, which settles against their own payment method. Either way, top up rather than ordering a replacement non-reloadable card.
Base only, if you send it yourself. The deposit address takes USDC on Base, and USDC sent on another chain is unrecoverable. A Laso managed agent wallet holds USDC on Solana, so do not transfer straight from one to that address. Use GET /fund-card-balance instead, which bridges for you. Deposits usually confirm within a couple of minutes; poll GET /get-card-deposit-address until balance reflects the deposit, then create the card.
Creating a card
Once the holder has linked their card issuer account, you create cards yourself against a funded balance (see Topping up to fund it):"reusable": false for one that closes after its first approved charge.
A 402 means the balance is short: fund it as in Topping up, then retry. A 400 means no card issuer account is linked yet, which the holder fixes in the dashboard.
Finding a card to spend
card_id, last4, expiry, balance, spend_limit, reusable, and status (open, paused, or closed). Spend against a card whose status is open and whose balance covers the purchase.
If the wallet has no card issuer account linked, the list comes back empty with a note. That is not an error. It means the account holder has not set up a reloadable card yet.
Getting the card number requires the holder’s approval
This is the one place a reloadable card differs sharply from the non-reloadable flow. Card numbers are not returned on demand:details_approval with status: "pending", the card issuer has emailed the account holder an approve/deny link. There is no way to bypass this, and retrying without an approval only mints another request. Tell your human that an approval is waiting in their email, then retry with the approval_id once they have approved:
card_details (card number, CVV, expiry) is populated in that second response.
Spending it
Pay the merchant exactly as you would with any card: enter the number, expiry, and CVV at checkout, and enter the returnedbilling_address exactly as given. That address comes from the account holder’s identity verification and is AVS-checked at checkout. A mismatch as small as St versus Street is the most common decline on a card that has enough money on it.
A reloadable card with reusable: true stays open after an approved charge, so the same card can be spent again up to its remaining balance. Re-read balance before each purchase instead of assuming it still covers the total: there are no partial approvals, so a charge larger than the balance is declined in full rather than partly filled.
If a charge is declined, check in order: status is still open; balance covers the full amount including tax and shipping; and the billing address was entered exactly as returned. If all three hold, the decline is merchant-side, some merchants reject prepaid or debit cards, and some non-U.S. merchants decline U.S.-issued cards.
Reading the history
GET /list-card-transactions returns transactions on the holder’s reloadable cards, newest first:
card_id for every reloadable card on the account, and narrow with limit and status. Each row carries merchant, amount in US dollars, status, and created_at in milliseconds. This covers reloadable cards only; non-reloadable history stays in /get-card-data.
The same response carries card_events, which is the rest of the story. The card issuer reports spends and nothing else, so a list of purchases alone shows money leaving a balance that grows for no visible reason. Each event has an event_type of deposit, limitChange, cardCreated, or withdrawal, plus amount and balance for a deposit, previous_spend_limit/new_spend_limit for a limit change, and amount, withdrawal_id and destination_address for a withdrawal requested through POST /withdraw-card-balance. Merge the two lists by created_at and you have the account holder’s own view of the card. Skip a deposit that carries a top_up_id: that money is already the final leg of a top_ups row below, and counting both doubles it. A third list, top_ups, follows each top-up from GET /fund-card-balance or POST /fund-card-from-account-balance across its legs (paid, bridging, delivered, credited, or failed) with the transaction hash behind each one, which is the same tracker the holder sees in the dashboard after adding money.
Withdrawing the balance
Unspent money comes back off the card withPOST /withdraw-card-balance. The balance behind a reloadable card is held by the card issuer, and this asks the issuer to pay part of it out as USDC on Base to an address you supply:
amount is $2 to $10,000 and no more than the balance, and destination_address is a Base wallet you control. The response carries the issuer’s withdrawal_id and a status of requested.
Base only, and not instant. The issuer pays on Base and nowhere else, so a Laso managed agent wallet, which holds USDC on Solana, cannot receive it, and the card deposit address is not a valid destination. The issuer processes payouts manually, usually within 1-3 business days, and emails the account holder when the payout is sent. There is no status route: the request shows up as a withdrawal event in the history below, and balance from GET /get-card-deposit-address drops once it is paid. Withdrawals the issuer has not paid yet still count against the balance, so a second request can only take what remains. A refusal is a 400 whose code says why (insufficient_card_balance names how much can be withdrawn right now). Confirm the amount and destination with the account holder before calling this.