laso

← All agent guides

Issue a virtual card with one x402 call

Last reviewed: August 2026

What you will build

One paid HTTP request that returns a spendable virtual prepaid card. The flow: GET /get-card?amount=X answers with an x402 challenge quoting the exact USDC price, your client pays it and replays, the response carries a card_id plus API credentials, and polling the free GET /get-card-data endpoint returns the card number, expiry, CVV, and billing address about 7 to 10 seconds later. Works with any x402 client library, or with no wallet at all via the Laso-managed wallet.

Prerequisites

Step 1 — Request a card and read the challenge

Call the endpoint with no payment attached. You get HTTP 402 with a machine-readable challenge. For a $50 card the price is exactly $50: U.S. cards carry a 0% Laso fee.

curl -i "https://laso.finance/get-card?amount=50"
# HTTP/1.1 402 Payment Required
# {
#   "x402Version": 2,
#   "accepts": [
#     { "scheme": "exact", "network": "eip155:8453",
#       "amount": "50000000",
#       "payTo": "0x3291e96b3bff7ed56e3ca8364273c5b4654b2b37", ... },
#     { "scheme": "exact", "network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
#       "payTo": "3MZVk97x9SeRxbYpc3jhzRfU2fyA3emYutnqfn9kNfYX", ... }
#   ]
# }

Two things worth noting. The amount is in atomic units: USDC has six decimals, so 50000000 is $50.00, and this figure is authoritative, fee-inclusive pricing you can check against your balance before paying. And the challenge offers both networks; pay whichever one your wallet lives on.

Step 2 — Pay the challenge and replay

Client libraries do the challenge handling for you. With x402-fetch on Base:

import { wrapFetchWithPayment } from "x402-fetch";
import { privateKeyToAccount } from "viem/accounts";

const account = privateKeyToAccount(process.env.WALLET_PRIVATE_KEY);
const fetchWithPay = wrapFetchWithPayment(fetch, account);

const res = await fetchWithPay("https://laso.finance/get-card?amount=50");
const data = await res.json();

The paid response contains the order and, importantly, your API credentials for the free follow-up calls:

{
  "auth": {
    "id_token": "eyJ...",
    "refresh_token": "AMf...",
    "expires_in": "3600"
  },
  "user_id": "0xabc...",
  "card": {
    "card_id": "card_abc123",
    "usd_amount": 50,
    "country": "US",
    "status": "pending"
  }
}

The card number is deliberately not in this response. status is always pending at first; the details come from the next step.

Step 3 — Poll for the card details

Poll the free /get-card-data endpoint with the Bearer token every 2 to 3 seconds. status flips to ready in roughly 7 to 10 seconds:

curl "https://laso.finance/get-card-data?card_id=card_abc123" \
  -H "Authorization: Bearer eyJ..."
# {
#   "card_id": "card_abc123",
#   "status": "ready",
#   "usd_amount": 50,
#   "card_details": {
#     "card_number": "4111111111111111",
#     "exp_month": "12",
#     "exp_year": "2027",
#     "cvv": "123",
#     "available_balance": 50,
#     "billing_address": {
#       "name": "Laso Finance",
#       "line_1": "440 N Barranca Avenue",
#       "line_2": "#4496",
#       "city": "Covina", "state": "CA", "zip": "91723", "country": "US",
#       "required": false
#     }
#   },
#   "transactions": []
# }

Everything a checkout form asks for is here. When a merchant wants a billing address or ZIP, use the returned billing_address; the billing name is always Laso Finance, and for U.S. cards any valid U.S. address also works (required: false). The same endpoint later returns the card’s transactions and remaining balance, so spend tracking needs no other tooling.

Step 4 — Keep your credentials alive

The id_token expires after an hour. Renew it for free, no wallet signature needed:

curl -X POST "https://laso.finance/auth" \
  -H "Content-Type: application/json" \
  -d '{"grant_type": "refresh_token", "refresh_token": "AMf..."}'
# { "id_token": "eyJ...", "refresh_token": "AMf...", "expires_in": "3600", "user_id": "0xabc..." }

If you need credentials before ever buying anything, GET /auth signs you in free with a wallet signature: send a SIGN-IN-WITH-X header (a CAIP-122 signed message; wrapFetchWithSIWx from @x402/extensions/sign-in-with-x builds it). A missing or rejected signature returns 402 with a fresh challenge to sign, never 401.

When the payment fails

The most common failure is an underfunded wallet. You get a second 402, distinguishable from the challenge by its body: a challenge carries accepts, a failed settlement carries success: false:

{
  "success": false,
  "errorReason": "insufficient_funds",
  "errorMessage": "the transfer could not be settled on-chain",
  "payer": "9sZE...9WXR",
  "network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp"
}

Nothing is charged for a failed settlement, so retrying with a smaller amount is safe. Branch on errorReason, not the HTTP status.

No wallet? Use the managed wallet

An agent with no wallet at all can route the same purchase through a Laso-managed wallet: a human approves the wallet once from the agent dashboard and hands the agent a lasoak_ key. The agent signs in via /auth, then calls the agentX402Pay callable naming the route; Laso constructs and settles the x402 payment server-side, so the agent never builds a payment header or holds a private key:

curl "https://us-central1-kyc-ts.cloudfunctions.net/agentX402Pay" \
  -X POST -H "Authorization: Bearer <id_token>" \
  -H "Content-Type: application/json" \
  -d '{"data": {"route": "get-card", "params": {"amount": "50"}}}'
# { "result": { "status": 200, "body": { "card": { "card_id": "...", "status": "pending" }, ... } } }

From there the flow is identical: poll /get-card-data, spend the card. The full agent-readable version of everything on this page, including gift cards, payouts, bank rails, and webhooks, lives at laso.finance/SKILL.md.

Limits and constraints, stated plainly

Frequently asked questions

Is it really one call?

One paid call. GET /get-card is the only request that costs anything; the polling call and token refresh are free. Counting HTTP round trips it is typically three requests over about ten seconds from nothing to a spendable card number.

How much USDC do I need for a $50 card?

Exactly $50 plus network transaction costs: U.S. cards carry a 0% Laso fee. The 402 challenge quotes the authoritative total in atomic units (50000000 for $50.00) before you pay, so verify against your balance rather than assuming.

Which chains and tokens can I pay with?

USDC on Base (eip155:8453) or Solana. Every challenge offers both networks and your client pays whichever one the wallet lives on. Deposits into a Laso account balance additionally accept USDC, USDT, and DAI on Ethereum, Solana, Stellar, Arbitrum, Base, and Polygon.

Can I test without spending real money?

There is no sandbox: payments settle on mainnet. The cheapest real test is the free path: GET /auth costs nothing with a wallet signature, and /get-card-data, /get-account-balance, and /search-gift-cards are free with the resulting Bearer token. The smallest card order is $5.

What happens to leftover balance on a card?

It stays on the card and remains spendable until depleted; /get-card-data shows the available balance and transactions. The intended pattern avoids leftovers: determine the exact checkout total first and size the card to it.

More guides

Try it: connect a wallet at laso.finance and you'll have a card, gift card, or payout in minutes.

Building an agent? Point it at laso.finance/SKILL.md — it can set itself up.