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.
assistant skills install stripe-link-walletSpend on the user's behalf using the 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.
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).--request-approval on spend-request create. The Link app approval is the consent surface — it is non-negotiable. No spending happens without it.--test) unless the user has explicitly asked to spend real money. When unsure, ask in chat before dropping --test.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.--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 1000. Maximum is 50,000 cents ($500).--output-file when retrieving card credentials.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:
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 tokenFor MCP-based checkouts (e.g. PostalForm):
--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.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.
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."authenticated": false — fall to Setup: login."update" key present in auth status — mention the update to the user but don't block on it.link-cli is missingInvoke the CLI on demand with bunx:
bunx @stripe/link-cli <subcommand>
In every example below, substitute bunx @stripe/link-cli wherever you see link-cli.
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.
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.
If you need the exact flags for a subcommand not covered below:
link-cli --llms-full # all commands, LLM-friendly
link-cli spend-request create --schema # full schema for one command
link-cli <command> --help
Every spend request needs a --payment-method-id. Retrieve the user's saved methods first:
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 to add one first.
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)
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
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-approvalreturns immediately with an_next.commandvalue pointing tospend-request retrieve. You must then poll for approval.
3. Poll for approval
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:
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.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.
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
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
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.
Read-only and mutation operations need no extra gating:
# 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-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 / 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 |