Browse docs
previewThese docs describe OpenIntents as it is being built. Endpoints and payloads may change before launch.

Wallets & payments

Live purchases are paid from the payer's own wallet at Perflo. The payer is whoever the intent buys for: your account buying for itself, or one of your customers. Perflo holds their money, not OpenIntents, and the payer decides how much their agents may spend.

Test keys never pay, so they never need a wallet: a test intent stops at the payment step with a receipt preview.

How a purchase is paid

  1. The agent reaches checkout. The cart is filled and the shop shows its total.
  2. OpenIntents asks the payer's wallet for that total. Perflo checks it against the payer's limits and balance, then loads the payer's virtual Mastercard with exactly that amount.
  3. The card is typed into the shop's form. OpenIntents reveals the card once and types it in itself. It is kept in memory for that run only, never stored, and never shown to the AI model, your code or your agent.
  4. The order is placed and the intent completes with a receipt.

A run that ends without placing the order cancels the request at Perflo, so the money goes back to the wallet. Wallets pay in US dollars. A shop that charges in another currency fails the intent with currency_unsupported, and nothing is charged, unless the service converts for that currency: the wallet is then asked for the dollars the shop's total comes to, with some headroom for the card network's rate, and what the shop does not take goes back to the wallet when the order settles. The intent's max_amount and receipt stay in the shop's currency.

Setting up a wallet

A payer sets up their wallet once, on our page at https://openintents.io/wallet/…, with Perflo's own sign-in framed in it. They:

  • connect their Perflo account, signing in or making one (the email and name you gave the customer are filled in already),
  • set limits: the most one purchase, and one month, may cost,
  • add money, or turn on Perflo's automatic top-ups from a saved card,
  • confirm who they are, only when Perflo asks for it (for larger purchases, say).

The page runs only the steps left and skips the ones done. You don't need to send anyone there ahead of time: the first live purchase that needs the wallet pauses and asks for it (see below). To have a customer ready before their first purchase, send them a wallet link during your own sign-up.

Your account's own wallet is on the Wallets page of the dashboard, with each customer's status beside it. Customers also see theirs in the payer portal.

When a purchase waits

When the payer has a step to take first, the intent pauses with "status": "requires_payment" and a payment object:

json
{
  "id": "int_7f11b0",
  "status": "requires_payment",
  "payment": {
    "reason": "add_money",
    "payment_url": "https://openintents.io/api/pay/pay_7f11b0",
    "merchant": "TAP Air Portugal",
    "total": 38900,
    "fee": 0,
    "currency": "usd",
    "expires_at": "2026-10-01T09:00:00Z"
  }
}

Send payment_url to the payer: show it in your app, post it in a chat, or let the MCP tools hand it to the user. It opens the wallet page with only the step this purchase needs. Once it's done the intent goes back to queued by itself, with the same cart, and on to completed.

reasonWhat the payer does
connect_walletConnects their wallet, or signs in to it again.
add_moneyAdds money: the wallet is short of total.
identity_checkConfirms who they are: the wallet needs it first.
wallet_processingNothing yet: the wallet is still taking the money (from a card they saved, say). The link shows how it is going.
over_limitRaises the limit they set: the purchase is over it.
requestedApproves the purchase: you asked for a link first (require_payment_link).

Other values may appear, so treat an unknown reason like any other: send the link. Links expire after 30 minutes; an expired or cancelled intent is free. If Perflo declines the payment, the intent fails with payment_declined and Perflo's own message, and nothing is charged.

Onboard a customer ahead of their first purchase:

POST/v1/customers/{id}/wallet_links
ParameterType
amountintegerA purchase, in cents, to have the wallet ready for: the page then also asks for any money or limits it would need.
return_urlstringA public https URL the page offers a way back to.
ttl_secondsintegerHow long the link works, 60 to 604800. Default 86400 (a day).
json
{
  "id": "wl_7c2e91d04b8a3f56",
  "object": "wallet_link",
  "customer": "cus_8f2a",
  "amount": 5000,
  "url": "https://openintents.io/wallet/wl_7c2e91d04b8a3f56",
  "status": "open",
  "expires_at": "2026-10-02T08:12:00Z"
}

url is for the customer alone. status is open until the wallet needs nothing more (completed) or the link runs out (expired). Once they connect, endpoints that name wallet.connected get a webhook.

GET/v1/customers/{id}/wallet?amount=5000
json
{
  "object": "wallet",
  "customer": "cus_8f2a",
  "status": "funded",
  "next_step": { "kind": "identity_check", "amount": null }
}

Where the customer stands, and the one step left before a purchase of amount could be paid (without amount: before they are set up at all). status goes not_connected, pending, connected, limits_set, funded, identity_checked, or is not_needed for test keys. next_step.kind is connect, set_limits, add_money or identity_check, and next_step is null when nothing is left. It never shows their balance or limits: those are the customer's own.

Keeping spend in check

ControlWhereEffect
Wallet limitsset by the payer, in their walletA purchase over them waits with reason: over_limit until the payer raises them.
max_amountper intentThe intent fails with over_max_amount rather than spend more.
require_payment_linkper intentThe purchase always waits for the payer's approval link.
Customer limitsset by you, per customerper_intent caps one purchase and monthly what the payer's wallet purchases commit in a UTC calendar month (each counts its cart's total). They are checked before anything is paid: a purchase over one fails with limit_exceeded, and nothing the payer does in their wallet lifts them.

The fee

For now, purchases paid from a wallet carry no fee: fee is 0 on the payment request and on the receipt. See Pricing.

What Perflo gets

To pay, Perflo receives the payer's ID with us, their email and name when we have them, and for each purchase the shop's name and domain, the amount and a short description. We never store the card's details, and Perflo never sees what is in the cart beyond that description.

Card details never reach your code, your agent or the model. The card is revealed once, typed into the shop's payment form by OpenIntents, and kept in memory for that run only.