` | Yes | ISO 4217 code, e.g. `USD`, `EUR`, `GBP`, `INR`. |
| `--merchant-name ` | Yes | Merchant display name (shown to the owner). |
| `--merchant-url ` | Yes | Full merchant URL **including `https://`**. |
| `--merchant-country ` | Yes | ISO 3166-1 alpha-2 country, e.g. `US`. |
| `--product ` | Yes | A product line item as JSON. **Repeat** the flag for each distinct item. |
Each `--product` is a JSON object:
```json theme={null}
{ "description": "Latte", "unit_price": "5.00", "quantity": 1 }
```
For multiple units of the same item, set `"quantity"`; don't repeat `--product` or bake counts
into the description. The total must reconcile with the line items.
On success:
```
Session created.
Session ID: ses_abc123
Payment URL: https://collect.prava.space?session=…
Share this URL to complete card entry.
Run `prava sessions poll --session-id ses_abc123` to wait for card entry.
```
Give the **Payment URL** to the owner. They open it and enter their card in Prava's secure page;
the card details never pass through the agent.
## `prava sessions poll`
Wait for the card to be entered and tokenized (exchanged for one-time credentials that stand in
for the real card):
```bash theme={null}
prava sessions poll --session-id ses_abc123
```
| Option | Required | Description |
| ------------------- | -------- | ---------------------------------------- |
| `--session-id ` | Yes | The `Session ID` from `sessions create`. |
While waiting it prints dots; once the owner completes card entry:
```
Waiting for card entry on session ses_abc123...
....
Card tokenized.
Token: 4323126882557932
Cryptogram: 957
Expiry: 12/2028
```
`poll` waits up to **10 minutes** for the owner to enter the card; that's the CLI's card-entry
window. The underlying session expires **15 minutes** after creation; once it does, create a new
session.
### What you receive
| Field | Format | Use it as |
| -------------- | --------------- | ------------------------------------ |
| **Token** | 16-digit number | The card number at checkout. |
| **Cryptogram** | 3-digit number | The CVV / security code at checkout. |
| **Expiry** | `MM/YYYY` | The card expiry at checkout. |
These credentials are **single-use and expire quickly**. Complete the checkout immediately after
polling; don't cache, log, or reuse them. If they expire, create a new session.
## Exit codes
Session commands use exit codes so an agent can branch reliably:
| Code | Meaning |
| ---- | -------------------------------------------------------------------------- |
| `0` | Success. |
| `1` | Error or timeout (e.g. session expired, tokenization failed). |
| `2` | Not linked / no agent configured; run [`prava setup`](/prava-pay/linking). |
## Common outcomes
| Output | What it means | What to do |
| -------------------------------------------------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------- |
| `Session expired. Run \`prava sessions create\` again.\` | The owner didn't finish card entry within 10 minutes. | Re-create the session; have the card ready. |
| `Tokenization failed.` | The card couldn't be processed. | Try again, or a different card. See [Troubleshooting](/prava-pay/troubleshooting). |
| `Invalid product JSON: …` | A `--product` value isn't valid JSON. | Fix the JSON: `'{"description":"…","unit_price":"…","quantity":1}'`. |
| `Failed to create session: …` | The order was rejected. | Check the amount reconciles with the line items and the URL includes `https://`. |
Next: use the token to [complete a checkout](/prava-pay/shopping#checkout), or drive the
merchant's checkout directly with the returned credentials.
# Agentic Shopping
Source: https://docs.prava.space/prava-pay/shopping
Discover a product, compare offers, get a live quote, and pay — all from the CLI.
When the agent needs to **find** the product too, Prava Pay adds a discovery flow on top of payments:
**search → product → quote → checkout**. Each step hands you exactly what you need to run the next.
For the richest guided experience (pacing, confirmations, masked previews), install the
`prava-shopping` skill alongside the CLI. The `shop` commands work without it, but the skill adds
the guardrails that keep the agent honest with the user.
```bash theme={null}
npx --yes skills add https://github.com/Prava-Payments/prava-skills \
--skill prava-shopping --global --yes --full-depth
```
The agent must be [linked](/prava-pay/linking) before any `shop` command works.
## Delivery addresses
Physical goods need a delivery address and a contact phone. These are stored on the **owner's
account** and hydrated server-side at purchase time: the agent only ever sees **masked summaries**,
never full street addresses or phone numbers.
The agent never handles the buyer's full address or phone. It selects *which* saved address by id;
Prava fills in the real details privately. See [Guardrails](/concepts/guardrails).
### List addresses
```bash theme={null}
prava shop address list
```
```
2 saved addresses (masked):
1. Home [default]
San Francisco, CA 94103, US
address-id: addr_home1
2. Work
Oakland, CA 94612, US
address-id: addr_work2
```
### Add an address
The primary place to add addresses is the Prava dashboard; this is the CLI fallback.
```bash theme={null}
prava shop address add \
--first-name John --last-name Doe \
--line1 "123 Main St" --city "San Francisco" \
--region CA --postal 94103 --country US \
--phone "+1 415 555 0100" --label "Home" --default
```
| Option | Required | Description |
| --------------------------------------------- | -------- | ---------------------------------------------------------------------------- |
| `--first-name`, `--last-name` | Yes | Recipient name. |
| `--line1` | Yes | Street address. `--line2` optional. |
| `--city`, `--region`, `--postal`, `--country` | Yes | City, state/province, ZIP, ISO country. |
| `--phone` | No | Contact phone **with country code**. Stored on the account, not per-address. |
| `--label` | No | e.g. `"Home"`. |
| `--default` | No | Make this the default delivery address. |
### Set the default
```bash theme={null}
prava shop address set-default --address-id addr_work2
```
## Search
```bash theme={null}
prava shop search --query "dark roast coffee" --intent "gift for a coffee lover" --limit 10 --ships-to US
```
| Option | Required | Description |
| ---------------------- | -------- | ------------------------------------------------------------------------------ |
| `--query ` | Yes | Tight keyword query. |
| `--intent ` | No | The user's full natural-language request (occasion, budget); improves ranking. |
| `--limit ` | No | Max results (default 10). |
| `--cursor ` | No | Next-page cursor from a previous search. |
| `--merchant ` | No | Restrict to one merchant domain. |
| `--ships-to ` | No | ISO destination country, e.g. `US`. |
| `--json` | No | Raw JSON output for chaining. |
```
3 results for "dark roast coffee":
1. Hair Bender Whole Bean
$17.00 USD · stumptowncoffee.com
product-id: prod_abc
Next: prava shop product --product-id --merchant
```
## Product
Show a product's variants and offers across sellers:
```bash theme={null}
prava shop product --product-id prod_abc --merchant stumptowncoffee.com
```
| Option | Required | Description |
| --------------------- | -------- | --------------------------------------- |
| `--product-id ` | Yes | `product-id` from search results. |
| `--merchant ` | No | Merchant domain from the search result. |
| `--json` | No | Raw JSON output. |
Offers are listed **orderable first, then cheapest**. Prices shown are item-only: **shipping and tax
are added at the quote step**.
```
2 offers from 2 sellers, 2 orderable (orderable first, then cheapest) — prices are item-only; shipping is added at quote:
1. 12 oz — $17.00 USD [whole bean] · stumptowncoffee.com
variant-id: var_123
Next: prava shop quote --variant-id --merchant
```
## Quote
Pricing a variant opens a **checkout session** with the *final*, binding total: subtotal + shipping
* tax. This is a spend-adjacent step, so it asks for confirmation.
```bash theme={null}
prava shop quote --variant-id var_123 --merchant stumptowncoffee.com --quantity 1 --address-id addr_home1 --yes
```
| Option | Required | Description |
| --------------------- | -------- | ---------------------------------------------------------------------------------- |
| `--variant-id ` | Yes | `variant-id` from product details. |
| `--merchant ` | Yes | Merchant domain. |
| `--quantity ` | No | Quantity (default 1). |
| `--email ` | No | Buyer email (optional if on file). |
| `--address-id ` | No | Which saved address to ship to (default: the owner's default address). |
| `--retries ` | No | Retry on timeout / server error (default 1; `0` to disable). |
| `-y, --yes` | No | Confirm. Pass **only** after the user has approved the seller, variant, and price. |
| `--json` | No | Raw JSON output. |
```
Quote for stumptowncoffee.com
Total: $22.45 USD
(subtotal $17.00 + shipping $4.50 + tax $0.95)
Shipping: Standard (5–7 days)
Expires: in ~15m
checkout-session-id: ches_xxx
Next: mint a card session for $22.45 USD (prava sessions create … → approve → prava sessions poll), then:
prava shop checkout --checkout-session-id ches_xxx --token --cryptogram --expiry-month --expiry-year
```
A quote **expires** (about 15 minutes) and locks the price. Get the card credentials and check out
promptly. If it lapses, quote again.
Between the quote and the checkout, mint the card credentials with a [payment
session](/prava-pay/sessions): `prava sessions create` for the quoted total → owner enters the card →
`prava sessions poll` to get the token and cryptogram.
## Checkout
Pay the quoted total with the card credentials. This is the actual charge, so it also confirms.
```bash theme={null}
prava shop checkout \
--checkout-session-id ches_xxx \
--token 4323126882557932 --cryptogram 957 \
--expiry-month 12 --expiry-year 2028 \
--cardholder-name "John Doe" --yes
```
| Option | Required | Description |
| --------------------------------------------- | -------- | -------------------------------------------------------------------------- |
| `--checkout-session-id ` | Yes | From `shop quote`. |
| `--token ` | Yes | Network token from `prava sessions poll`. |
| `--cryptogram ` | Yes | Dynamic CVV from `prava sessions poll`. |
| `--expiry-month `, `--expiry-year ` | No | Card expiry (defaults from the session if omitted). |
| `--cardholder-name ` | No | Cardholder name. |
| `-y, --yes` | No | Confirm the charge. Pass **only** after the user approves the final total. |
| `--json` | No | Raw JSON output. |
On success:
```
✓ Paid.
Amount: $22.45 USD
Order: ord_abc123
```
The total charged is **bound to the quote**: it can't drift between quote and payment. Prava pays
exactly the amount you saw, at exactly the merchant you quoted.
## Confirmation & safety
Both `quote` and `checkout` are gated by a confirmation:
* At an interactive terminal, they prompt `[y/N]`.
* For an agent (non-interactive), they **refuse** unless `--yes` is passed, making a skipped
confirmation a deliberate, visible act.
Pass `--yes` **only after the user has actually approved** the seller, item, and total. It exists to
record the user's approval, not to bypass it.
## Exit codes
| Code | Meaning |
| ---- | -------------------------------------------------- |
| `0` | Success. |
| `1` | Error, declined, or not confirmed. |
| `2` | Not linked / confirmation refused without `--yes`. |
For the full list of failure messages and how to recover, see
[Troubleshooting](/prava-pay/troubleshooting).
# Skills
Source: https://docs.prava.space/prava-pay/skills
How Prava skills guide an AI agent to call the right CLI command at the right time.
The `prava` CLI does the work; **skills tell the agent *when* to call *what*.** A skill is a small
instruction file installed into the agent's runtime that encodes the correct flow, the pacing, and the
safety stops, so the agent invokes `setup`, `sessions`, and `shop` commands in the right order and
pauses for the user's approval before spending.
The CLI works **without** any skill: every `prava` command runs on its own. The skill adds the
*guidance*: which command comes next, when to stop and confirm, and how to keep the user in control.
If the matching skill is missing, the CLI nudges you (once) to install it.
## Why skills matter
Left to itself, an agent might chain commands or spend without checking. Skills prevent that by encoding:
* **One decision per turn**: after `search`, wait for the user to pick; after `product`, wait for them
to confirm the seller/variant; after `quote`, wait for them to approve the total.
* **Confirmation hard-stops**: the agent must present the merchant, item, and final amount and get an
explicit "yes" before `sessions create` / `checkout`. *"Buy me X"* is intent, not price approval.
* **PII masking**: the agent works with masked address summaries only; full details stay server-side.
* **Move promptly**: card credentials are single-use and short-lived, so mint → poll → checkout
without pausing.
## The skills
| Skill | Version | What it guides |
| --------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| **`prava-pay`** | 2.3.1 | Completing a payment once merchant, item, and price are known: `setup` → `status` → `sessions create` → `sessions poll` → checkout. |
| **`prava-shopping`** | 1.5.1 | Discover **and** buy a product: `shop search` → `shop product` → `shop quote` → `shop checkout`, with pacing and confirmation gates. |
| **`prava-sdk-integration`** | 1.1.0 | For coding agents *integrating the [SDK](/sdk/overview)* into an app (sessions, iframe, polling); not for CLI payments. |
| **`swiggy-prava-skill`** | — | Order from Swiggy via its MCP (Swiggy's tool server for agents), pay with Prava credentials. |
| **`zepto-prava-skill`** | — | Order from Zepto via its MCP, pay with Prava credentials. |
Most agents want **`prava-pay`** (to pay for a known item) or **`prava-shopping`** (to find *and* buy).
## Install a skill
Install into the agent's global skills directory (e.g. `~/.claude/skills//SKILL.md`):
```bash One skill (recommended) theme={null}
npx --yes skills add https://github.com/Prava-Payments/prava-skills \
--skill prava-shopping --global --yes --full-depth
```
```bash All skills theme={null}
npx --yes skills add https://github.com/Prava-Payments/prava-skills \
--global --yes --full-depth
```
Pass **`--full-depth`** with `--skill `. Otherwise sibling skills are pulled in alongside the
one you asked for.
Valid `--skill` names: `prava-pay`, `prava-shopping`, `prava-sdk-integration`, `swiggy-prava-skill`,
`zepto-prava-skill`.
## Keeping skills current
The CLI checks your installed skill version on every call. If it's behind the server minimum, it prints:
```
Skill update required (minimum: 2.3.1).
Run: npx skills update prava-pay -g
```
Update, then re-run your command. If the notice repeats, restart the agent session so it reloads the
skill. See [Troubleshooting](/prava-pay/troubleshooting#keeping-the-cli-up-to-date).
## Platforms
Skills work across coding agents: Claude Code, Cursor, Codex, Gemini CLI, Hermes, and
[many more](/prava-pay/linking#prava-setup). The agent's platform is set with `--platform` when you run
[`prava setup`](/prava-pay/linking).
## Next steps
Connect and approve the agent before it can pay.
The search → quote → checkout flow the shopping skill guides.
# Troubleshooting
Source: https://docs.prava.space/prava-pay/troubleshooting
What each message means and exactly how to recover.
Prava Pay is designed to fail **loudly and safely**: every error tells you what happened and what to
do next, and no failure ever leaves you double-charged. This page maps the messages you'll see to a
recovery step.
**Exit codes** let an agent branch without parsing text: `0` = success · `1` = error/declined ·
`2` = not linked or confirmation refused.
## Linking problems
Nothing is linked on this machine yet. Run [`prava setup`](/prava-pay/linking) and approve in the
browser.
The approval window (15 minutes) passed. Start over with `prava setup`, open the new URL, and
approve promptly.
The owner rejected the agent on the approval screen. If that was a mistake, run `prava setup` again
and approve it.
Linking is time-sensitive. Sync the machine's clock (enable automatic network time) and re-run
`prava setup`.
A network issue. Check connectivity and retry. If you're behind a proxy or firewall, make sure
outbound HTTPS to `prava.space` is allowed.
A session or shop command ran before the agent was approved. Complete linking first
(`prava setup` → approve → `prava setup poll`), then retry.
## Card entry & payment sessions
The owner didn't finish card entry within the 10-minute window. Re-create the session and have the
card ready before opening the payment URL.
The card couldn't be processed. Try the payment session again, or use a different card. If it keeps
failing, contact [support](mailto:support@prava.space).
A `--product` value isn't valid JSON. Use the exact shape, quoted for your shell:
`'{"description":"…","unit_price":"…","quantity":1}'`.
The order was rejected. Check that `--total-amount` equals the sum of `unit_price × quantity` and
that `--merchant-url` includes `https://`.
The token and cryptogram (the one-time card number and security code) from `sessions poll` are
**single-use and short-lived**. If you wait too long to check out, mint a fresh session; don't
try to reuse old credentials.
## Shopping: quotes & checkout
Quotes and checkouts drive a live merchant checkout and can take 20–40 seconds. For **quotes**,
retry (the CLI retries once by default; raise it with `--retries`). For **checkout**, see the
note on unknown outcomes below before retrying.
A quote is valid for about 15 minutes and locks the price. Once it lapses, run `prava shop quote`
again to get a fresh `checkout-session-id` and total.
You've hit the limit on concurrent open quotes. Wait for existing quotes to be paid or to expire
(\~15 min), then quote again.
The account's purchase limit has been reached. This is an owner-level guardrail: the owner needs to
raise the limit or contact [support](mailto:support@prava.space). See [Guardrails](/concepts/guardrails).
Physical goods need a delivery address and a contact phone. Add them in the Prava dashboard, or via
`prava shop address add … --phone "+1 …"`. See [Agentic shopping](/prava-pay/shopping#delivery-addresses).
`quote` and `checkout` require confirmation. At a terminal, answer `y`. For an agent, pass `--yes`
**only after the user has approved** the seller, item, and total.
## Payment outcomes
The result of a checkout is read from the payment itself rather than the network response alone,
so the status you see is the real one.
Success. The charge went through; an order id is shown when the merchant provides one.
The payment was declined or the merchant rejected it. This checkout is done; start a new
[quote](/prava-pay/shopping#quote) to try again.
This checkout was already completed earlier. Prava recognized the repeat and did **not** charge
again. Treat the earlier success as authoritative.
**Unknown outcome (rare).** If a checkout times out or the merchant response is lost, Prava does **not**
guess: it leaves the payment un-finalized rather than risk a double charge, and asks you to wait
rather than blindly retry. Do **not** immediately re-run checkout with the same credentials; re-quote
if you need to try again, and reconcile the earlier attempt first if in doubt.
## Keeping the CLI up to date
Prava tells you when a version is behind:
The CLI is too old to talk to the server. Update it, then retry the command (you do **not** need to
re-link):
```bash theme={null}
npm update -g @prava-sdk/cli
```
An optional bug-fix update is available. Not required, but recommended:
```bash theme={null}
npm update -g @prava-sdk/cli
```
A Prava skill (e.g. `prava-pay`, `prava-shopping`) is behind. Update it, then retry. If the notice
repeats, restart the agent session so it reloads the skill:
```bash theme={null}
npx skills update prava-shopping -g
```
## Still stuck?
Run `prava status` to confirm the agent is linked and active, and re-check the command flags against
the [Payment sessions](/prava-pay/sessions) and [Agentic shopping](/prava-pay/shopping) references.
For anything else, reach us at [support@prava.space](mailto:support@prava.space).
# Your Prava Pay Dashboard
Source: https://docs.prava.space/prava-pay/your-wallet
For agent owners: what your AI agent can and can't do with your card, and how you stay in control. No code required.
This page is for the **person whose card gets used**: the agent owner. You don't need to read any
other page in these docs to use Prava safely.
Your home base is the **Prava Pay dashboard** at [pay.prava.space](https://pay.prava.space). That's
where you approve agents, add your card, and control spending. (If you're a developer looking for
API keys, you want [dashboard.prava.space](https://dashboard.prava.space) instead. Different product.)
## What your agent can never do
You enter card details only on Prava's secure page. The agent receives a **one-time credential**
for a single approved purchase, never your real card number.
Every purchase needs your explicit approval: a **passkey** (Touch ID / Face ID) and/or an in-chat
confirmation before any money moves.
The credential is locked to the merchant and amount you approved. It can't be reused, resized, or
pointed at another store.
Revoke an agent any time from the wallet; its access ends immediately.
## Setting up (once, \~2 minutes)
Your agent asks to link by showing you a URL (it ran `prava setup`). Open it, sign in at
[pay.prava.space](https://pay.prava.space), check the agent's name, and click **Approve**. The link
expires after 15 minutes, so approve promptly.
When the agent makes its first payment, you'll get a secure Prava page to enter your card;
it's saved to your account for future purchases. Your card details go to Prava, never to the agent.
If your agent shops for physical goods, add a delivery address and contact phone in the dashboard
once. The agent only ever sees a masked summary.
## Every purchase, from your side
1. The agent finds or is told what to buy, and shows you the **merchant, items, and total**.
2. You confirm — in the conversation, and with a **passkey** for card actions.
3. Prava issues the one-time credential and the checkout runs.
4. The result shows up in your dashboard's transaction history.
If the agent skips the confirmation, the purchase doesn't happen: the confirmation is enforced by
Prava, not by the agent's goodwill.
## Your controls
| Control | What it does |
| --------------------------- | -------------------------------------------------------------------------------------- |
| **Approve / revoke agents** | An agent is either approved by you or it can do nothing. Revocation is instant. |
| **Checkout quota** | A cap on how many purchases your agents can make in total. |
| **Concurrency limit** | How many checkouts can be open at once. |
| **Per-purchase approval** | Nothing is charged without your confirmation. |
| **Passkey security** | Card actions require your device's biometrics; there is no password fallback to phish. |
More detail on how these are enforced: [Guardrails](/concepts/guardrails) ·
[Accounts & Agents](/concepts/accounts).
## Common questions
**What if my agent goes rogue or gets compromised?** Revoke it at
[pay.prava.space](https://pay.prava.space). Anything already approved is still merchant- and
amount-locked; anything new requires approvals it can no longer get.
**Can my agent see my address or phone?** No. It sees masked summaries. Full details stay on
Prava's servers and go only to the merchant fulfilling your order.
**What if a payment looks wrong?** Check the transaction in your dashboard, then contact
[support@prava.space](mailto:support@prava.space). Refunds follow the standard card-network process.
**Do I need to understand the developer docs?** No. This page plus the dashboard is everything an
owner needs. The curious can read [How Prava Works](/concepts/how-it-works).
# Quickstart
Source: https://docs.prava.space/quickstart
Start building with Prava
Getting started with Prava is **self-serve**. Sign up at the
[Prava Dashboard](https://dashboard.prava.space) ([what it offers](/dashboard)),
create an API key, and you'll have sandbox credentials in a few minutes, with no invite code and no waiting.
**Want to see Prava in action first?** Try the [Interactive Playground](https://playground.prava.space/) to visualize the full payment flow before you build.
## Set up your account
Go to [dashboard.prava.space](https://dashboard.prava.space) and create an account with your email
(one-time code) or Google. You'll land in the dashboard right away.
In the dashboard, create an API key by entering your **entity name** (and optional website). Prava
provisions your merchant and issues your keys instantly:
* A **publishable key** (`pk_test_*`) — safe for the browser, used to initialize the SDK.
* A **secret key** (`sk_test_*`) — server-side only, used to create sessions.
New keys are **sandbox** by default. See [Authentication & Environments](/authentication) for how keys and environments work.
Add the frontend domain(s) that will host the SDK iframe (the embedded frame that renders
Prava's card form) to your **allowed domains** in the dashboard. Requests from other origins are rejected.
**Going to production?** Production is a separate environment you switch to from the dashboard, and
going live may require some additional verification. Reach out at
[support@prava.space](mailto:support@prava.space) when you're ready. You can build and
test everything in sandbox first.
## Create your first session
A session is the workspace for a single payment. Once you have your credentials, create a session
from your backend:
```bash theme={null}
curl -X POST https://sandbox.api.prava.space/v1/sessions \
-H "Authorization: Bearer sk_test_xxx" \
-H "Content-Type: application/json" \
-d '{
"user_id": "user_123",
"user_email": "user@example.com",
"total_amount": "49.99",
"currency": "USD",
"purchase_context": [{
"merchant_details": {
"name": "Your Store",
"url": "https://yourstore.com",
"country_code_iso2": "US"
},
"product_details": [{
"description": "Example Product",
"unit_price": "49.99",
"quantity": 1
}]
}]
}'
```
The response gives you a `session_token` and an `iframe_url`.
## Choose how to collect the card
From that one session you can collect the card two ways:
* **Hosted (default)**: redirect the cardholder to the `iframe_url`; Prava returns them to your
`callback_url` when done. No frontend SDK needed.
* **Embedded**: set `integration_type: "embedding"` on the session and mount Prava's card UI inside
your own page with the SDK (below).
See [Integration Modes](/sdk/integration-modes) for a side-by-side comparison and the hosted snippet.
## Embed the card form (embedded mode)
```typescript theme={null}
import { PravaSDK } from '@prava-sdk/core';
const prava = new PravaSDK({
publishableKey: 'pk_test_xxx',
});
await prava.collectPAN({
sessionToken: session.session_token,
iframeUrl: session.iframe_url,
container: '#card-form',
onSuccess: (data) => console.log(`Collected: ${data.brand} •••• ${data.last4}`),
onError: (err) => console.error(err.code, err.message),
});
```
See the [SDK Overview](/sdk/overview) for the full API reference.
## Where next
The complete integration in your app: backend, frontend, credentials, reporting.
The same flow with hosted card entry and nothing but cURL.
Test cards, sandbox behaviors, and a full test run.
# Collect Card Details
Source: https://docs.prava.space/sdk/cards/collect-pan
Securely collect and tokenize user payment cards
## Overview
The `collectPAN()` method securely collects credit card details from users through an isolated iframe. The card data is tokenized via Prava's PCI DSS vault and returns only safe metadata — your servers never see the raw PAN.
## Method Signature
```typescript theme={null}
prava.collectPAN(options: CollectPANOptions): Promise
```
## Parameters
Configuration options for card collection
Session token obtained from your backend
Iframe URL from your backend session response
CSS selector or DOM element to mount the card form (e.g., `#card-form`)
Called when the iframe is loaded and ready for input
Real-time validation state on every keystroke
Called on successful card enrollment
Called on error
Called when the user dismisses the payment flow
Custom styles for the card form. Supports `base`, `invalid`, and `focus` states as CSS property maps.
## Return Value
Unique identifier for the collected card
Last 4 digits of the card number
Card brand (`visa`, etc.)
Card expiration month (1-12)
Card expiration year (4 digits)
The `enrollmentId` identifies the collected card on the server side. Use your backend and the
[API Reference](/api-reference/overview) to read payment results and report status for that card.
## Example
```typescript React theme={null}
import { useEffect, useRef, useState } from 'react';
import { PravaSDK } from '@prava-sdk/core';
import type { CollectPANResult } from '@prava-sdk/core';
function CardEnrollment({ session }: { session: { session_token: string; iframe_url: string } }) {
const containerRef = useRef(null);
const sdkRef = useRef(null);
const [enrolled, setEnrolled] = useState(null);
useEffect(() => {
const sdk = new PravaSDK({ publishableKey: 'pk_live_xxx' });
sdkRef.current = sdk;
return () => sdk.destroy();
}, []);
const handleEnroll = async () => {
if (!sdkRef.current || !containerRef.current) return;
const result = await sdkRef.current.collectPAN({
sessionToken: session.session_token,
iframeUrl: session.iframe_url,
container: containerRef.current,
onReady: () => console.log('Form loaded'),
onChange: (state) => {
console.log('Complete:', state.isComplete);
},
onSuccess: (data) => {
console.log(`Enrolled: •••• ${data.last4}`);
setEnrolled(data);
},
onError: (err) => console.error(err.code, err.message),
});
};
return (
{enrolled && Card enrolled: {enrolled.brand} •••• {enrolled.last4}
}
);
}
```
```typescript Vanilla JavaScript theme={null}
import { PravaSDK } from '@prava-sdk/core';
const prava = new PravaSDK({ publishableKey: 'pk_live_xxx' });
// Get session from your backend
const session = await fetch('/api/session').then(r => r.json());
// Collect card via secure iframe
const card = await prava.collectPAN({
sessionToken: session.session_token,
iframeUrl: session.iframe_url,
container: '#card-form',
onReady: () => console.log('Form loaded'),
onChange: (state) => {
document.getElementById('submit-btn').disabled = !state.isComplete;
},
onSuccess: (data) => {
console.log(data.enrollmentId, data.last4, data.brand);
},
onError: (err) => console.error(err.code, err.message),
});
```
```html theme={null}
```
## Flow Diagram
```mermaid theme={null}
sequenceDiagram
participant App as Your App
participant SDK as Prava SDK
participant Iframe as Secure Iframe
participant Backend as Prava Backend
participant Network as Card Network
App->>SDK: collectPAN(sessionToken, iframeUrl)
SDK->>Iframe: Mount iframe
Iframe-->>SDK: onReady()
SDK-->>App: Form ready
Note over Iframe: User enters card details
Iframe->>Backend: Tokenize via PCI DSS vault
Backend->>Network: Provision token
Network-->>Backend: Enrollment complete
Backend-->>Iframe: Success
Iframe-->>SDK: onSuccess(result)
SDK-->>App: CollectPANResult
```
## Under the Hood
When you call `collectPAN()`, here's what happens:
The SDK injects a secure, sandboxed iframe into your specified container. The iframe is served from Prava's domain — card data never touches your DOM or servers.
The iframe validates the session token with Prava's backend to ensure the request is legitimate and not expired.
The user enters their card details (number, expiry, CVV) in the iframe form with real-time validation.
When the user submits, the iframe tokenizes the PAN via Prava's PCI DSS vault. Your servers never see the raw card number.
The enrollment result (with `enrollmentId`, `last4`, `brand`, `expMonth`, `expYear`) is returned to your app.
## Validation States
The `onChange` callback receives a `CardValidationState` object on every keystroke:
```typescript theme={null}
interface CardValidationState {
cardNumber: FieldState;
expiry: FieldState;
cvv: FieldState;
isComplete: boolean; // true when all fields are valid
}
interface FieldState {
isEmpty: boolean;
isValid: boolean;
isFocused: boolean;
error?: string;
}
```
Use `isComplete` to enable/disable your submit button:
```typescript theme={null}
onChange: (state) => {
submitButton.disabled = !state.isComplete;
}
```
## Error Handling
### Common Errors
| Code | Cause | Resolution |
| -------------------- | ----------------------------------------------------------- | -------------------------------------------------------- |
| `SDK_ALREADY_ACTIVE` | `collectPAN` called while another collection is in progress | Call `destroy()` first |
| `INVALID_CONFIG` | Missing `iframeUrl` (or a bad `publishableKey`) | Pass the `iframe_url` from your backend session response |
| `IFRAME_LOAD_ERROR` | Iframe failed to load | Check network, verify `iframeUrl` |
| `SDK_INIT_ERROR` | SDK initialization failed | Verify browser compatibility and config |
## Security Notes
**Never** attempt to bypass the iframe or collect card data directly. The iframe is sandboxed with `allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox` and a permissions policy of `payment; publickey-credentials-get; publickey-credentials-create`. Card data never touches your DOM, JS, or servers.
PostMessage communication is origin-locked. The iframe resolves its backend from its own hostname — merchants cannot inject a fake backend URL.
## Next Steps
Retrieve a customer's enrolled cards server-side
Read the outcome after the card is collected
# Integration Modes
Source: https://docs.prava.space/sdk/integration-modes
Two ways to collect a card from one session — embedded (SDK) or hosted (redirect).
Every card collection starts the same way: your backend creates a **session**
([`POST /v1/sessions`](/api-reference/create-session)) and gets back a `session_token` and an
`iframe_url`. From there you choose **how** the cardholder enters their card — and you pick by setting
one field, `integration_type`, on the session.
Prava's card UI runs **inside your app** via the [SDK](/sdk/overview). You control the surrounding
page and get the result in-app.
You **redirect** the cardholder to a Prava-hosted page. No SDK, minimal code — Prava returns them to
your `callback_url` when done.
Either way, the card is entered on **Prava's secure surface** and tokenized — your app never touches
the raw card number. There is no mode where you collect the PAN yourself.
## At a glance
| | **Embedded** (`"embedding"`) | **Hosted** (`"full_checkout"`, default) |
| ------------------------- | ------------------------------------------------ | ---------------------------------------------------- |
| SDK required | Yes (`@prava-sdk/core`) | No |
| Where the card UI renders | An iframe inside your page | A full Prava-hosted page you redirect to |
| What you build | A container + SDK wiring | A redirect + a `callback_url` route |
| How you get the result | `onSuccess` callback, in-app | Prava redirects the user to your `callback_url` |
| Best when | You want the flow to feel native to your product | You want the fastest, lowest-maintenance integration |
Both modes use the **same session** and the **same `iframe_url`** — embed it, or open it directly.
## Embedded mode
Create the session with `integration_type: "embedding"`, then mount the card UI with the SDK.
```typescript theme={null}
// 1. Backend: create the session
const session = await createSession({
/* user_id, user_email, total_amount, currency, purchase_context … */
integration_type: 'embedding',
});
// 2. Frontend: mount Prava's secure iframe in your page
import { PravaSDK } from '@prava-sdk/core';
const prava = new PravaSDK({ publishableKey: 'pk_test_xxx' });
await prava.collectPAN({
sessionToken: session.session_token,
iframeUrl: session.iframe_url,
container: '#card-form',
onSuccess: (data) => console.log(`Collected •••• ${data.last4}`),
onError: (err) => console.error(err.code, err.message),
});
```
```html theme={null}
```
The result arrives in `onSuccess` without leaving your page. See
[Collect Card Details](/sdk/cards/collect-pan) for every option and callback, and remember to call
`prava.destroy()` on cleanup.
## Hosted mode (default)
Leave `integration_type` off (it defaults to `"full_checkout"`) and give Prava a `callback_url`. Then
send the cardholder to the returned `iframe_url` — no SDK needed.
```typescript theme={null}
// 1. Backend: create the session (hosted is the default)
const session = await createSession({
/* user_id, user_email, total_amount, currency, purchase_context … */
callback_url: 'https://yourapp.com/checkout/complete', // must be https
});
// 2. Frontend: send the user to the hosted page
const url = new URL(session.iframe_url);
url.searchParams.set('session_token', session.session_token);
window.location.href = url.toString(); // or window.open(url, '_blank')
```
When the cardholder finishes, Prava redirects them back to your `callback_url`. Your backend then reads
the outcome with [Get Payment Result](/api-reference/get-payment-result) and reports the final status
with [Report Status](/api-reference/report-status).
`callback_url` must be an **https** URL. Without it, a hosted checkout has nowhere to return the user.
## Which should I use?
You want the card step to feel native, keep the user on your page, and react to the result in-app.
You want to ship fast with minimal frontend code and are happy to redirect out and back.
## Next steps
The library behind embedded mode.
`integration_type`, `callback_url`, and the full request/response.
# SDK Overview
Source: https://docs.prava.space/sdk/overview
Embed secure, PCI-safe card collection into your web app with the Prava SDK.
*Not sure this is the right path? See [Choosing Your Integration](/choosing-your-integration).*
## What is the Prava SDK?
The **Prava SDK** (`@prava-sdk/core`) is a lightweight browser library for **securely collecting a
card** in your web app. It mounts a PCI-compliant iframe, handles the card entry and authentication
inside it, and hands you back a safe result — the raw card number never touches your page, your
JavaScript, or your servers.
The SDK is the **client-side** piece of a session-based integration. Your **backend** creates the
session (see the [API Reference](/api-reference/overview)); the SDK renders the secure card form for
that session. If instead you want an **AI agent** to pay from the command line, use
[Prava Pay](/prava-pay/overview).
The SDK powers the **embedded** integration mode — Prava's card UI inside your own page. Don't want
to embed? Use **hosted** mode (a redirect, no SDK). See
[Integration Modes](/sdk/integration-modes) to choose.
## What it does (and doesn't)
The SDK's job is deliberately small — one secure operation, done well:
| The SDK handles | Your backend handles |
| ---------------------------------------------------- | ------------------------------------------ |
| Rendering the secure card iframe | Creating the session (`POST /v1/sessions`) |
| Card entry, validation, and in-iframe authentication | Reading the payment result |
| Returning a safe, tokenized result | Reporting the final transaction status |
Everything money-related — mandates, one-time payment credentials, spending limits — is enforced by
Prava and the card network, not by code running in your page. See [Payments](/concepts/payments) and
[Guardrails](/concepts/guardrails) for how that works.
## Install
```bash npm theme={null}
npm install @prava-sdk/core
```
```bash yarn theme={null}
yarn add @prava-sdk/core
```
```bash pnpm theme={null}
pnpm add @prava-sdk/core
```
## Quick start
```typescript theme={null}
import { PravaSDK } from '@prava-sdk/core';
const prava = new PravaSDK({
publishableKey: 'pk_live_xxx', // from the Prava Dashboard
});
```
The publishable key is safe to ship to the browser. Your **secret key** stays on your server and is
never used here.
Your server creates a session with your secret key and returns the `session_token` and `iframe_url`
to your frontend:
```typescript theme={null}
// Server-side (never expose your secret key to the browser)
const session = await fetch('https://api.prava.space/v1/sessions', {
method: 'POST',
headers: {
Authorization: `Bearer ${SECRET_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
user_id: 'user_123',
user_email: 'user@example.com',
total_amount: '49.99',
currency: 'USD',
purchase_context: [{
merchant_details: { name: 'Amazon', url: 'https://amazon.com', country_code_iso2: 'US' },
product_details: [{ description: 'Wireless Headphones', unit_price: '49.99', quantity: 1 }],
}],
}),
}).then((r) => r.json());
// → { session_id, session_token, iframe_url, expires_at, order_id }
```
Full request/response details: [Create Session](/api-reference/create-session).
```typescript theme={null}
const result = await prava.collectPAN({
sessionToken: session.session_token,
iframeUrl: session.iframe_url,
container: '#card-form',
onSuccess: (data) => console.log(`Collected •••• ${data.last4}`),
onError: (err) => console.error(err.code, err.message),
});
```
```html theme={null}
```
See [Collect Card Details](/sdk/cards/collect-pan) for every option and callback.
After the card is collected and the transaction runs, your backend reads the outcome and reports the
final status. See [Get Payment Result](/api-reference/get-payment-result) and
[Report Status](/api-reference/report-status).
## API surface
The SDK exposes exactly two methods:
| Method | Description |
| ----------------------------------------------- | -------------------------------------------------------------------------------- |
| [`collectPAN(options)`](/sdk/cards/collect-pan) | Mounts the secure iframe, collects the card, resolves with a `CollectPANResult`. |
| `destroy()` | Tears down the iframe and listeners. Call it on unmount/cleanup. |
```typescript theme={null}
// Clean up when your component unmounts
prava.destroy();
```
## Authentication
| Key | Where it's used | Where it lives |
| ----------------------------------------------- | --------------------------------- | -------------- |
| **Publishable key** (`pk_live_*` / `pk_test_*`) | Initialize the SDK in the browser | Frontend |
| **Secret key** (`sk_*`) | Create sessions, read results | Backend only |
Never put your secret key in client-side code or version control.
## Result & error types
```typescript theme={null}
interface CollectPANResult {
enrollmentId: string;
last4: string;
brand: string; // "visa", "mastercard", …
expMonth: number;
expYear: number;
}
interface PravaError {
code: string;
message: string;
details?: Record;
}
```
Error codes, every `collectPAN` option, and validation states are documented on
[Collect Card Details](/sdk/cards/collect-pan) — the canonical `collectPAN` reference.
## Requirements
* **Browsers:** Chrome 80+, Firefox 80+, Safari 14+, Edge 80+.
* **Backend:** Your server implements the session API (`POST /v1/sessions`) with your secret key.
* **Keys:** Obtain `publishableKey` and `secretKey` from the [Prava Dashboard](https://dashboard.prava.space).
## Next steps
Every `collectPAN` option, callback, and validation state.
Create sessions, read payment results, and report status server-side.
The lifecycle behind a Prava payment.
Let an AI agent pay from the command line.
# Use Cases
Source: https://docs.prava.space/use-cases
What people build with Prava, shown as concrete flows.
Prava does one thing: it lets an AI agent pay with a real card, without ever holding that card. Here
is what that looks like in practice.
## An agent buys something for you
You tell your agent "order me a phone case under \$20". The agent, connected via
[MCP](/mcp/overview) (Model Context Protocol) or the [CLI](/prava-pay/overview):
1. Searches products across merchants and picks a case: \$18.50.
2. Locks the live price with a quote.
3. Sends you a payment link. You open it and approve with Face ID.
4. The agent completes the checkout. You get an order id; the agent never saw your card.
If the total had changed mid-checkout, the charge would not have gone through. The credential is
locked to that merchant and that amount.
**Start here:** [Connect via MCP](/mcp/connect) or [Prava Pay quickstart](/prava-pay/quickstart).
## An agent pays a bill you already know
No product discovery needed. You say "pay my $54.20 internet bill at Comcast". The agent creates a
payment session for exactly $54.20, you approve with a passkey, and it pays with the one-time
credentials. Wrong-amount or wrong-merchant use is declined.
**Start here:** [Payment sessions (CLI)](/prava-pay/sessions) or
[create\_payment\_session (MCP)](/mcp/tools#create-payment-session--checkoutrun).
## Your AI app takes payments from its users
You're building a shopping assistant, a travel planner, or a booking bot. Your users need to pay
*inside your product*. You integrate the [SDK + API](/sdk/overview): your backend creates a session,
your frontend shows Prava's secure card form, and your app receives one-time credentials to complete
the purchase. Your servers never touch a card number, which keeps you out of PCI scope (the
security compliance that applies once you handle card data).
Example: a stylist app's user picks a $79 jacket. The app creates a session for $79, the user enters
their card and approves in the embedded form, the app checks out at the boutique with the one-time
token, then reports the result.
**Start here:** [Add Payments to Your AI App](/guides/add-payments-to-your-ai-app).
## Human-in-the-loop, by design
Every flow above has the same shape: **the agent proposes, the human approves, Prava executes.**
The approval is a passkey (Touch ID / Face ID) on the user's own device. An agent cannot skip it,
and an approval for one purchase authorizes nothing else.
## Recurring payments (planned)
A mandate is the user's recorded permission to pay. Today every mandate is one-time: one approval,
one purchase. Recurring mandates are planned. Once
shipped, a user will approve a subscription once (say, \$12/month at one merchant), and the agent can
invoke it monthly within those limits, without a fresh passkey each time. Until then, each payment
needs its own approval.
## Where to next
Agent, embedded, or hosted. Decided in 10 seconds.
The lifecycle behind every flow above.
For merchants: what works with zero changes.