stripe-link-wallet
$
npx mdskill add vellum-ai/vellum-assistant/stripe-link-walletSpend on the user's behalf using the [Stripe Link CLI](https://github.com/stripe/link-cli). The CLI acts as a wallet: the assistant composes a spend request, the user approves it in the Link app on their device, and the CLI then issues one-time-use credentials to complete the purchase. Use this for paying 402 / Machine Payment Protocol (MPP) endpoints and for ad-hoc agent purchases that should be capped and auditable.
SKILL.md
.github/skills/stripe-link-walletView on GitHub ↗
---
name: "stripe-link-wallet"
description: 'Use the Stripe Link CLI as an agent wallet to create spend requests, generate one-time-use payment credentials, and complete 402 / Machine Payment Protocol (MPP) payment flows on the user''s behalf. Triggers on intents like "buy", "purchase", "pay for", "spend up to <amount>", "complete this 402", or any flow involving link-cli, spend-request, mpp pay, shared payment tokens, or @stripe/link-sdk. Also triggers automatically whenever any tool or API produces a checkout URL, hosted payment page, or payment session — intercept and complete via Stripe Link instead of surfacing the URL to the user.'
metadata:
icon: assets/icon.svg
emoji: "💳"
vellum:
category: "commerce"
display-name: "Stripe Link Wallet"
compatibility: "Designed for Vellum personal assistants"
---
# Stripe Link Wallet
Spend on the user's behalf using the [Stripe Link CLI](https://github.com/stripe/link-cli). The CLI acts as a wallet: the assistant composes a spend request, the user approves it in the Link app on their device, and the CLI then issues one-time-use credentials to complete the purchase. Use this for paying 402 / Machine Payment Protocol (MPP) endpoints and for ad-hoc agent purchases that should be capped and auditable.
## Required tools
- `bash` for all `link-cli` invocations. Use `host_bash` only if a specific flow genuinely requires host-level access (e.g. reading a local file the user has on their machine).
## Hard constraints
- Always pass `--request-approval` on `spend-request create`. The Link app approval is the consent surface — it is non-negotiable. No spending happens without it.
- Default to test mode (`--test`) unless the user has **explicitly** asked to spend real money. When unsure, ask in chat before dropping `--test`.
- The `context` field must be **at least 100 characters** and must accurately describe what the money is for. The user reads this when approving in Link — write it for them, not for yourself.
- Always use `--format json` on every command. The default interactive Ink output is for humans, not agents. Exception: `demo` and `onboard` require a TTY and have no JSON mode.
- **Amount is in cents.** $10.00 = `--amount 1000`. Maximum is 50,000 cents ($500).
- Never log or repeat raw card credentials (PAN, CVC) in the conversation. Always use `--output-file` when retrieving card credentials.
---
## Checkout interception — always active
Whenever **any** flow (MCP tool, API call, web request, or otherwise) produces a checkout URL, hosted payment page, or payment session that would otherwise be handed to the user to click through — **intercept it**. Route the payment through Stripe Link instead. Never surface a bare checkout URL to the user as the final step of a purchase flow.
**Decision tree:**
1. **MPP / HTTP 402 endpoint** — Flow A (Shared Payment Token)
2. **MCP service with a `complete_checkout` tool** — Flow B to get a virtual card, tokenize the card against the provider's Stripe account, then call `complete_checkout` with the resulting token
3. **Standard web checkout (no payment API)** — Flow B to get a virtual card, then use a browser automation skill to fill the card into the checkout form
**For MCP-based checkouts (e.g. PostalForm):**
- After retrieving the virtual card from `--output-file`, create a Stripe PaymentMethod via `POST /v1/payment_methods` using the merchant's publishable key (extract from the checkout URL or session if present), then pass the resulting `pm_xxx` token to the MCP's `complete_checkout` tool.
- If the publishable key is not available, fall back to browser automation (option 3) using the checkout URL the MCP returned — do not surface the URL to the user, navigate to it directly via browser automation.
**Never skip this step.** If `link-cli` is not installed or the user is not authenticated, surface that blocker immediately rather than falling back to a raw checkout URL.
---
## Step 0: Check installation and auth
```bash
if command -v link-cli >/dev/null; then
link-cli auth status --format json
else
bunx @stripe/link-cli auth status --format json
fi
```
- `link-cli` missing — use `bunx @stripe/link-cli` for Step 0 and every command below.
- Exit 0 but `"authenticated": false` — fall to **Setup: login**.
- Authenticated — proceed to the requested flow.
- `"update"` key present in auth status — mention the update to the user but don't block on it.
---
## Setup
### If `link-cli` is missing
Invoke the CLI on demand with `bunx`:
```bash
bunx @stripe/link-cli <subcommand>
```
In every example below, substitute `bunx @stripe/link-cli` wherever you see `link-cli`.
### If installed but not authenticated
Use your own assistant name for `--client-name` — read it from `IDENTITY.md`. This is the label the user sees in the Link app when they approve the connection.
```bash
link-cli auth login --client-name "<your assistant name>"
```
Opens a browser flow. The Link app will show `<your assistant name> on <hostname>` when the user approves the connection. After it completes, re-run Step 0.
### Introspecting the CLI
If you need the exact flags for a subcommand not covered below:
```bash
link-cli --llms-full # all commands, LLM-friendly
link-cli spend-request create --schema # full schema for one command
link-cli <command> --help
```
---
## Pre-flight: get a payment method ID
Every spend request needs a `--payment-method-id`. Retrieve the user's saved methods first:
```bash
link-cli payment-methods list --format json
```
If the user has multiple, ask which one to use. If they have none, direct them to [app.link.com/wallet](https://app.link.com/wallet) to add one first.
---
## Common flows
### Flow A: Pay a 402 / MPP-protected URL
Use this when the target endpoint returns HTTP 402 and requires a Shared Payment Token (SPT).
**1. Decode the challenge (optional but useful for diagnosing)**
```bash
link-cli mpp decode \
--challenge 'Payment id="ch_001", realm="merchant.example", method="stripe", ...'
```
Extracts the `network_id` and other challenge fields. Use when the URL is unfamiliar or the challenge looks malformed.
**2. Create the spend request**
```bash
link-cli spend-request create \
--payment-method-id <id> \
--merchant-name "<merchant>" \
--merchant-url "<url>" \
--context "<min-100-char description of what is being purchased and why>" \
--amount <cents> \
--credential-type "shared_payment_token" \
--line-item "name:<item>,unit_amount:<cents>,quantity:<n>" \
--total "type:total,display_text:Total,amount:<cents>" \
--request-approval \
--test \
--format json
```
Drop `--test` only if the user has explicitly asked to spend real money — and say so in chat before running.
> **Important — JSON mode does not block.** With `--format json`, `create --request-approval` returns immediately with an `_next.command` value pointing to `spend-request retrieve`. You must then poll for approval.
**3. Poll for approval**
```bash
link-cli spend-request retrieve <id> \
--interval 3 --max-attempts 60 \
--format json
```
Polls every 3 seconds, up to 3 minutes. Terminal statuses: `approved`, `denied`, `expired`, `canceled`. If polling exhausts `--max-attempts` while still non-terminal, the command exits non-zero with `code: "POLLING_TIMEOUT"` — report this to the user and offer to cancel or retry.
**4. Pay the URL**
Once status is `approved`:
```bash
link-cli mpp pay <url> \
--spend-request-id <id> \
--method POST \
--data '<json body>' \
--header "X-Custom: value" \
--format json
```
- `--header` is repeatable: `--header "Name: Value"`.
- `Content-Type: application/json` is auto-applied when `--data` is provided; user-provided headers take precedence.
- The SPT is **one-time-use**. If payment fails, you must create a new spend request.
Before running, read the URL and amount back to the user in plain language to catch typos.
**5. Report the result** — status code, what the endpoint returned.
---
### Flow B: Virtual card for a standard checkout
Use this when the merchant does not support MPP (no HTTP 402). Credentials are a one-time virtual Visa/Mastercard.
**1. Create the spend request**
```bash
link-cli spend-request create \
--payment-method-id <id> \
--merchant-name "<merchant>" \
--merchant-url "<url>" \
--context "<min-100-char description>" \
--amount <cents> \
--line-item "name:<item>,unit_amount:<cents>,quantity:<n>" \
--total "type:total,display_text:Total,amount:<cents>" \
--request-approval \
--test \
--format json
```
Omit `--credential-type` (or use the default). With `--format json`, returns immediately — proceed to polling.
**2. Poll for approval** (same as Flow A step 3)
**3. Retrieve card credentials securely**
```bash
link-cli spend-request retrieve <id> \
--include card \
--output-file /tmp/link-card.json \
--force \
--format json
```
`--output-file` writes the full card (PAN, CVC, billing address) to a local file with `0600` permissions and **redacts card data in stdout**. The JSON output replaces the `card` object with redacted fields and adds a `card_output_file` path. Never omit `--output-file` when requesting card credentials — raw PANs must not appear in the conversation or logs.
**4. Use the card**
The file at `/tmp/link-card.json` contains `number`, `cvc`, `exp_month`, `exp_year`, `billing_address`, and `valid_until`. Hand the path to a browser automation skill or tell the user where to find it. Do not read the file back into the conversation.
---
### Inspect, update, cancel
Read-only and mutation operations need no extra gating:
```bash
# List saved payment methods
link-cli payment-methods list --format json
# List saved shipping addresses
link-cli shipping-address list --format json
# Retrieve a spend request (no card data by default)
link-cli spend-request retrieve <id> --format json
# Update before approval (e.g. fix merchant URL)
link-cli spend-request update <id> --merchant-url <url> --format json
# Request approval separately (if created without --request-approval)
link-cli spend-request request-approval <id> --format json
# Cancel (valid from created, pending_approval, or approved)
link-cli spend-request cancel <id> --format json
```
---
## Line items and totals reference
`--line-item` and `--total` use repeatable `key:value` format.
**`--line-item` keys:** `name` (required), `quantity`, `unit_amount`, `description`, `sku`, `url`, `image_url`, `product_url`
```
--line-item "name:Running Shoes,unit_amount:12000,quantity:1,description:Trail runners"
```
**`--total` keys:** `type` (required; one of `subtotal`, `tax`, `total`), `display_text` (required), `amount` (required)
```
--total "type:subtotal,display_text:Subtotal,amount:11000"
--total "type:tax,display_text:Tax,amount:1000"
--total "type:total,display_text:Total,amount:12000"
```
---
## Error handling
| Error / condition | Action |
| --------------------------------------- | --------------------------------------------------------------------------------------------------- |
| `link-cli` not found | Invoke it with `bunx @stripe/link-cli` and substitute that prefix wherever examples use `link-cli`. |
| Not authenticated | Run `auth login --client-name "<your assistant name>"` (see Setup) |
| `POLLING_TIMEOUT` on retrieve | Report to user; offer cancel or fresh spend request |
| SPT payment fails (402 again after pay) | SPT is consumed — create a new spend request |
| `amount` > 50000 | Tell user the cap is \$500 per transaction |
| `context` < 100 chars | Expand it before retrying |
| Card file already exists | Use `--force` to overwrite, or pick a different path |
---
## References
- Upstream agent docs: https://github.com/stripe/link-cli/blob/main/CLAUDE.md
- README with full flag reference: https://github.com/stripe/link-cli/blob/main/README.md
- Machine Payments Protocol: https://mpp.dev
- Stripe SPT docs: https://docs.stripe.com/agentic-commerce/concepts/shared-payment-tokens
More from vellum-ai/vellum-assistant
- acpSpawn external coding agents via the Agent Client Protocol (ACP)
- amazonShop on Amazon and Amazon Fresh through your browser
- api-mappingRecord and analyze API surfaces of web services
- app-builderBuild and edit small, personal visual tools and artifacts — dashboards, trackers, calculators, data visualizations, charts, simple landing pages, and slide decks the user wants for THEMSELVES. This is the right skill whenever the user asks to "visualize this," "make a chart," or "build an artifact" for their own use, or to edit an app they already built here. Do NOT reach for a ui_show dynamic_page to fake an artifact — build a real persistent app here. NOT for complex, multi-user, or shippable products — those go to a real project folder with a coding agent (see Scope below).
- app-controlDrive a specific named macOS app via raw input bypassing the Accessibility tree
- assistant-migrationMigrate from ChatGPT, Claude, OpenClaw, Hermes, Manus, and other AI assistants into Vellum by inspecting their data exports, conversation archives, files, prompts, custom instructions, memory, saved memories, tools, GPTs, workflows, integrations, and relationships, then mapping as much as safely possible into Vellum primitives. Handles single-source and multi-source migrations with a unified, deduplicated inventory.
- chatgpt-importImport conversation history from ChatGPT into Vellum
- cli-discoverDiscover which CLI tools are installed, their versions, and authentication status
- computer-useControl the macOS desktop
- contactsManage contacts, communication channels, access control, and invite links