Prava Pay — Smart Wallet for AI Agents
Drive the Prava CLI: link this agent to the user's Prava account once, then mint single-use tokenized card credentials to complete purchases at any merchant. There is exactly ONE user hard stop in this flow: the pre-spend confirmation (step 3) — merchant + items + total — before prava sessions create. Never skip it; never add extra pauses after it.
Route on intent (read this first)
| The user wants… | Do |
|---|---|
| To pay for a known item/order/bill at a known merchant ("buy this", "pay $X at Y", "complete the checkout") | Payment steps 1–5 below |
| Prava to FIND a product first (no specific store chosen) | Hand off to the prava-shopping skill (search → product → quote → checkout). If it isn't installed, don't improvise discovery here — give the user: npx --yes skills add https://github.com/Prava-Payments/prava-skills --skill prava-shopping --global --yes --full-depth and ask them to retry |
| To set up Prava / link a card (no purchase yet) | Steps 1 only (setup + link), then stop |
| Pre-authorize a later/repeated buy — "buy this next week", "give you $200 to spend", "let you buy it when it drops to $X", "set a monthly budget at <store>" | Set up a mandate (§ Set up a mandate) |
| Any "buy/pay" intent — a mandate may already cover it | Run Check for a mandate first (§ buy flow step 0) before minting a fresh session |
| A first-party Prava question (product, security, pricing, supported cards/countries, passkeys, mandates, refunds, KYC, URLs) | Read about-prava and answer from it — run NO CLI commands |
| Crypto transfers, x402 APIs, P2P payments | Out of scope — say so |
Answering Prava questions: quote facts directly from the reference; never improvise pricing/billing models (if it says "contact for pricing", that IS the answer); when multiple URLs answer a question, surface ALL of them; not covered → "that's not in our public docs" + support@prava.space / https://docs.prava.space.
Prerequisites: CLI
Before any prava command: which prava. If missing, do NOT assume it's uninstalled — agent shells often lack nvm/Homebrew paths. Search first:
find ~/.nvm/versions/node ~/.npm-global /opt/homebrew /usr/local -path '*/bin/prava' -type f 2>/dev/null
Found → use the absolute path or prepend its bin dir to PATH. Not found → the user installs it, not you (never sudo, never retry a failed install with elevation):
The Prava CLI isn't installed. Please run
npm install -g @prava-sdk/cliin your terminal, then tell me when it's done.
The CLI auto-checks versions on every API call — do not run separate version-check commands. It infers the skill + version itself; no env prefix needed. See Version notices below for how to react to update output.
Payment steps
1. Verify link state
Run prava status. Decision tree (first match wins):
- "Skill update required (minimum: X.Y.Z)" — real block (CLI verified it):
npx skills update prava-pay -g, re-runprava status. If the SAME warning re-prints, the skill on disk is new but this session loaded the old one — tell the user to restart their agent session, naming the actual host you are running in (e.g. if you are Claude Code, say "restart Claude Code" — NOT thepravaCLI, NOT the machine), then STOP. ("Could not verify skill version" instead is harmless — continue, or setPRAVA_SKILL_VERSION=<this skill's version>on a non-standard host.) - "Link expired. Run
prava setupagain." — confirm in one sentence ("The previous link expired. I'll generate a new one — confirm?"), thenprava setup --name "<name>" --platform <platform>and IMMEDIATELYprava setup poll. - "active" — go to step 2.
- "pending" — the CLI re-prints
Link: <URL>: show it to the user and IMMEDIATELYprava setup poll. No URL on theLink:line → the old link is unrecoverable: confirm once, thenprava setupfresh. (Older CLIs: see cli-setup → Troubleshooting.) - "No agent configured" — onboard: detect your platform + default name from platforms (never ask for platform; confirm the name once unless the user already gave one), run
prava setup --name "<name>" --platform <platform>, show the URL, IMMEDIATELYprava setup poll— do NOT wait for the user to say "done". Details: cli-setup.
Stuck on "pending"? Troubleshoot before touching setup: (a) confirm the user opened the exact URL from the most recent prava setup (not an older one); (b) check network connectivity — prava status falls back to local state when the server is unreachable, which can mask a real approval; (c) never re-run prava setup from a troubleshooting path without confirming with the user — it rotates the keypair and kills any link they may still be about to approve (only exceptions: the CLI prints Link expired, or the user confirms they want a fresh link). On older CLIs that never print Link expired, the long-term fix is upgrading: npm update -g @prava-sdk/cli. If a purchase is waiting, remember prava sessions create has built-in auto-link-check and may detect the approval directly. Full legacy notes: cli-setup → Troubleshooting.
If the user's original intent was a purchase, proceed IMMEDIATELY to step 2 after linking.
2. Gather purchase context — no guesses
Before minting, you MUST have all of (gather via your normal discovery flow; never hallucinate):
- Merchant name AND full URL with
https://scheme (https://www.bestbuy.com, notbestbuy.com) - Product(s) finalized with real, discovered prices + a clear description each
- Total amount as a string ("8.50"); currency as ISO 4217 (
USD, not "dollars"); merchant country as ISO 3166-1 alpha-2 (US, not "USA")
Buy flow — step 0: Check for a mandate first
Before continuing to the confirm-and-mint steps below, run prava mandate list --json and look for a mandate that already covers this buy: first a merchant-specific one for this merchant, else a generic (any-scope) one, with enough remaining and an unexpired window.
- User is present → OFFER it: "You have a Nike mandate with $120 left — pay the $40 from it? No new approval needed." On yes →
prava mandate charge --mandate-id <id from --json> --amount 40.00 -y, then check out with the returned token/cryptogram (step 5), thenprava mandate report --mandate-id <id> --txn-id <txn> --status APPROVED. This offer-and-confirm replaces steps 3–4 below — don't also mint a fresh session for the same buy. - User is absent (a standing instruction given up front, like "buy it when it drops to $90") → charge within caps without a fresh prompt — the mandate's setup passkey was the consent — then notify: "Bought the Nikes at $90 from your Nike mandate."
- No matching mandate → continue to step 3 below (the normal confirm-and-mint flow). Optionally offer to set one up (§ Set up a mandate).
Never show the user the raw mdt_/txn ids — refer to mandates by merchant + remaining + expiry (ids only ever travel between commands via --json). Treat remaining as indicative, not authoritative — the server enforces the real cap; a Visa decline on mandate charge (status:"failed", e.g. THRESHOLD_EXCEEDED) is a normal outcome to relay, not an error.
3. Confirm with the user — MANDATORY HARD STOP
Present the merchant, what's being bought, and the total + currency; get an explicit "yes":
"I'll pay $8.50 USD to Blue Bottle Coffee for 1 Latte + 1 Croissant — confirm?"
This gate lives in the conversation, where the user is actually reading — the browser passkey approval is a second check, not a substitute. It catches a wrong merchant or mis-typed amount before it's baked into a session the user may approve on autopilot. "Buy X" is intent, not approval of a specific merchant and total — do not skip this even then.
4. Mint the session and poll
prava sessions create \
--total-amount "8.50" --currency USD \
--merchant-name "Blue Bottle Coffee" \
--merchant-url "https://bluebottlecoffee.com" \
--merchant-country US \
--product '{"description":"Latte","unit_price":"5.00","quantity":1}' \
--product '{"description":"Croissant","unit_price":"3.50","quantity":1}'
Product JSON is {"description","unit_price","quantity"} — use quantity for multi-unit items (never repeat --product for the same item or write "2x Latte"); --total-amount must equal Σ unit_price × quantity. The command prints a payment URL + session id and exits immediately: show the URL to the user, then IMMEDIATELY (don't wait for a reply):
prava sessions poll --session-id <session_id>
Polls up to 10 minutes while the user approves in the browser; returns Token (16-digit Visa network token → card-number field), Cryptogram (single-use dynamic CVV → CVV field), Expiry. Details: cli-sessions.
5. Complete checkout
IMMEDIATELY use the credentials at the merchant's site via browser automation. Credentials are single-use and expire in ~30 minutes — from mint → poll → checkout, move promptly with no pauses (the confirmation already happened at step 3, before the credential existed). Don't re-confirm here.
Multi-merchant requests ("a book from Amazon AND a domain from Namecheap"): one merchant at a time — full create → poll → checkout for A before starting B. Each session is tied to one merchant; never parallelize sessions create, poll multiple sessions before any checkout, or batch checkouts.
Set up a mandate (authorize now, buy later)
A mandate lets the user approve a card once (passkey), after which you can charge it within its caps — without re-approval. Two shapes:
- Merchant-specific ("$120 at Nike") →
--scope listedwith the merchant flags. - Generic budget ("$200 to spend anywhere") →
--scope any(one_timeonly — core forces recurring mandates to be merchant-locked).
- Verify link state — same as Payment step 1 (
prava status). - Gather — merchant (or "any"), the cap amount + currency, the period. State the limits plainly:
one_time/ generic (any-scope) mandates are valid for up to 7 days — the server enforces this cap even if a longer--valid-untilis passed.- For a standing arrangement, use a recurring mandate (
--frequency weekly|monthly|yearly) — core forces this to--scope listed(one merchant, locked) and allows one charge per period.
- MANDATORY HARD STOP — confirm the merchant(s), cap, and the real window; frame it as authorization, not a blank cheque:
"I'll set up a mandate authorizing up to $120 at Nike, good for 7 days — you approve once with a passkey, then I can buy from Nike without asking again each time. Confirm?"
- Create:
Generic budget: drop the threeprava mandate create --merchant-name "Nike" --merchant-url "https://nike.com" --merchant-country US \ --amount 120.00 --currency USD --frequency one_time --scope listed -y--merchant-*flags and pass--scope anyinstead. The command prints an approval URL — show it to the user: "Approve with your passkey." - Confirm active:
Use the exact merchant name passed toprava mandate poll --scope listed --merchant "Nike" --amount 120.00 --currency USDmandate create; URLs are not accepted. Generic:prava mandate poll --scope any --amount 200.00 --currency USD. Polling fails closed unless state, scope, merchant, cap, and currency all match one usable standing mandate. Report by description, never the id: "✓ Your Nike mandate is active — $120, expires Sunday."
Version notices (in command output)
- CLI update —
npm update -g @prava-sdk/cli; "Critical update required" = must update before continuing. - Skill update — printed only after the CLI confirmed you're below minimum:
npx skills update prava-pay -g, retry; re-prints after updating → user must restart the agent session (host app, not CLI/machine). - Neither one re-links the agent — do NOT run
prava setupafter updating unless genuinely setting up.
Automatic behaviors & output contract
- Auto-link-check:
sessions createwhile pending auto-detects a fresh approval — noprava statusneeded between setup and create. - stdout = human-readable; stderr = plain-text errors. Exit 0 success (incl. already-linked setup no-op) · 1 error · 2 agent not configured/approved.
Quick reference
prava setup --name "<name>" --platform <platform> [--description "<desc>"] # prints URL, exits immediately
prava setup poll # waits for link approval
prava status # link state (also detects approval)
prava sessions create --total-amount <amt> --currency <CUR> --merchant-name "<n>" --merchant-url "<url>" --merchant-country <XX> --product '<json>' [--product ...]
prava sessions poll --session-id <id> # waits for tokenized credentials
prava mandate create --amount <amt> --currency <CUR> [--merchant-name "<n>" --merchant-url "<url>" --merchant-country <XX>] [--frequency one_time|weekly|monthly|yearly] [--scope listed|any] -y # prints approval URL
prava mandate poll --scope listed|any [--merchant "<exact name>"] --amount <amt> --currency <CUR> # merchant required for listed, omitted for any
prava mandate list [--merchant <url>] --json # resolve id + remaining/expiry internally — never shown raw to the user
prava mandate charge --mandate-id <id> --amount <amt> [--reference <ref>] -y # no passkey; returns token+cryptogram
prava mandate report --mandate-id <id> --txn-id <id> --status APPROVED|DECLINED
prava mandate cancel --mandate-id <id> -y # revokes; stops future charges
Anti-patterns
- Minting a session before the step-3 confirmation of merchant + total. The #1 thing to never skip.
- Minting a fresh
sessions createwhen a matching mandate already covers the buy — check § buy flow step 0 first. - Showing the user a raw mandate or transaction id — refer to mandates by merchant + remaining + expiry, resolved internally via
mandate list --json. - Promising a
one_time/generic mandate lasts longer than 7 days, or that a recurring mandate covers more than one merchant. - Running
sessions createbefore the agent is linked, or before purchase discovery is complete. - Guessing/hallucinating amount, currency, or purchase context.
- Asking the user for keys, card numbers, or credentials — the CLI handles all auth locally.
- Pausing between receiving credentials and completing checkout.
- Installing the CLI yourself, or using
sudo— the user installs it. - Running
setupwhen already linked (harmless no-op, but unnecessary). - Doing product discovery in this skill — that's prava-shopping.
Built by Prava Payments — the payment stack for AI agents.