Enable agents to spend
Let your agent retrieve one-time-use payment credentials to transact online.
A spend request is your agent’s request to spend a specific amount at a specific seller. Your agent creates a spend request, the customer approves it, and Link returns a one-time-use payment credential for that purchase. The customer’s real card or bank number is never exposed.
Each spend request represents one intent to purchase from the customer. You can create many spend requests per customer as they ask your agent to make different purchases.
Before you begin, make sure that you’ve authorized your agent with the payment_methods.agentic scope.
How it works
- Your agent evaluates how the seller takes payment, which determines the credential type needed.
- Your agent creates a spend request with the amount, the seller, and a description of the purchase.
- The customer approves it on the Link website or in the mobile app.
- Your agent polls until the request is approved or denied.
- Your agent retrieves the credential and pays the seller.
Integration checklist
To get the best experience for your customers and your agent, follow these recommendations.
- Use idempotency keys When creating a spend request, provide an idempotency key that represents that purchase intent. Your agent can safely retry a spend request if it encounters any errors. Idempotency keys
- Use {% $link.brand_name%} Pay Tokens If your agent needs to purchase from a Stripe enabled checkout, it doesn’t need to use a virtual card. Use a Link Pay Token instead to instantly complete the entire checkout. Link Pay Tokens
- Handle payment errors If Link encounters any errors or issues while making a payment, you can see the details back on the spend request. Look for the
next_actionblock to handle errors and retry. Handle next actions. - Use skills Link provides skill files that are already written for Link Agent Wallet:
- Skill for purchasing
- Skill for financial insights
Choose a credential type
Link Agent Wallet can generate a number of different types of payment credential, depending on what the seller supports.
| How the seller accepts payments | Which payment credential to use | How to complete payment |
|---|---|---|
| A Stripe-hosted checkout page with Link enabled | A Link pay token | Enter the Link Pay Token into the checkout page. |
An API accepting shared payment tokens, or x402 with method="stripe" | A shared payment token | Enter the shared payment token through the machine-payment endpoint. |
| A regular card form | A one-time use card number (the default) | Enter the virtual card into the checkout form. |
Create a spend request
Once you know which credential type to use, create a spend request. --amount and --context are always required. --merchant-name and --merchant-url are required for virtual cards, and are omitted for shared payment tokens and Link Pay Tokens. Link resolves the seller’s identity itself in those flows.
Create a spend request
link-cli spend-request create \
--amount 3500 \
--context "Purchasing 'Working in Public' from press.stripe.com. The customer initiated this purchase through the shopping assistant." \
--merchant-name "Stripe Press" \
--merchant-url "https://press.stripe.com" \
--line-item "name:Working in Public,unit_amount:3500,quantity:1" \
--total "type:total,display_text:Total,amount:3500"
Response
Present approval_url to the customer.
Approval is requested by default. Pass --no-request-approval to create a request without asking for approval, then call spend-request request-approval <id> when you’re ready.
Provide context for the customer
--context must be at least 100 characters, and it’s what your customer reads when deciding whether to approve. Name the item, the seller, and why your agent is buying it.
Parameters
| Parameter | Required | Description |
|---|---|---|
--amount | Yes | Total to authorize, in the smallest currency unit. 3500 is 35.00 USD. |
--context | Yes | What’s being bought and why, minimum 100 characters. Shown to the customer for approval. |
--merchant-name | Conditional | Required for virtual cards. Omit for shared_payment_token and link_pay_token. |
--merchant-url | Conditional | The product or seller page. Required for virtual cards. Omit for shared_payment_token and link_pay_token. |
--credential-type | No | card (default) or shared_payment_token. |
--network-id | Conditional | Required for shared_payment_token. Get it from link-cli mpp decode. |
--execution-method | No | Set to link_pay_token to bind the request to a Stripe checkout page. Requires --merchant-account-id. |
--merchant-account-id | Conditional | The acct_... value read from data-stripe-merchant-account. Required with --execution-method link_pay_token. |
--currency | No | Three-letter ISO code. Defaults to usd. |
--payment-method-id | No | Which saved method to charge. Defaults to the customer’s default, or the first eligible one. |
--line-item | No | An item on the approval screen. Repeatable. See Line items and totals. |
--total | No | An amount line on the approval screen. Repeatable. |
--request-approval | No | Request approval immediately. Defaults to true. |
--metadata | No | Arbitrary key:value data. Repeatable. Maximum 50 keys, keys up to 40 characters, values up to 500. |
--idempotency-key | No | Reuse only when retrying the same logical creation. Maximum 255 UTF-8 bytes. |
--expires-at | No | Unix timestamp, in seconds, after which the request expires. |
--test | No | Create a test mode request. Defaults to false. See Test your integration. |
Line items and totals
You can optionally also include details of what is being purchased.
--line-item and --total take repeatable key:value pairs and control what the customer sees at approval.
--line-item keys: name (required), quantity, unit_amount, description, sku, url, image_url, product_url.
--total keys: type (required), display_text (required), and amount (required). type is one of subtotal, tax, total, items_base_amount, items_discount, discount, fulfillment, shipping, fee, gift_wrap, tip, or store_credit.
Itemized request
link-cli spend-request create \
--amount 13100 \
--context "Purchasing trail running shoes from example.com. The customer selected these after comparing three options with the shopping assistant." \
--merchant-name "Example Store" \
--merchant-url "https://example.com/running-shoes" \
--line-item "name:Running Shoes,unit_amount:12000,quantity:1,description:Trail runners" \
--total "type:subtotal,display_text:Subtotal,amount:12000" \
--total "type:shipping,display_text:Shipping,amount:1100" \
--total "type:total,display_text:Total,amount:13100"
Idempotency keys
Pass --idempotency-key when creating a spend request to allow safely retrying for the same purchase intent. The server returns the original response for any repeated call that uses the same key.
Use a key that’s unique to the logical purchase attempt — for example, a UUID generated when the user initiates the purchase. Don’t reuse the same key for a different purchase. The key is opaque and non-sensitive: don’t include personal data or payment details in it. It must be between 1 and 255 UTF-8 bytes.
Retry a failed creation
link-cli spend-request create \
--amount 3500 \
--context "Purchasing 'Working in Public' from press.stripe.com. The customer initiated this purchase through the shopping assistant." \
--merchant-name "Stripe Press" \
--merchant-url "https://press.stripe.com" \
--idempotency-key "purchase-attempt-a1b2c3d4"
Wait for approval
Poll until the customer approves or declines the request:
Poll for a decision
link-cli spend-request retrieve lsrq_abc123 --interval 2 --max-attempts 300
Polling only happens when --interval is greater than zero.
| Flag | Default | Behavior |
|---|---|---|
--interval | 0 | Seconds between polls. 0 means retrieve once and return. |
--max-attempts | 0 | Maximum polls. 0 means unlimited. |
--timeout | 600 | Seconds before giving up. The default outlasts the server-side expiry, so polling outlives the request itself. |
Customers have 10 minutes from the approval request to respond, after which the status becomes expired.
Retrieve a virtual card
By default, card details are not returned when retrieving a spend request. For hosted agents, write card details directly to a file rather than returning them to stdout, where they can persist in model transcripts and application logs:
Write credentials to a file
The file is created with 0600 permissions. The command fails instead of overwriting an existing file unless you pass --force. Standard output then carries only brand, last4, and expiry, plus a card_output_file path.
Keep card details out of stdout
Passing --include card without --output-file writes the full PAN, CVC, and billing address to stdout, where it can be captured in model context, agent transcripts, and log aggregation. Use --output-file for hosted agents.
To retrieve card details directly to stdout (for example, in a local development context):
Retrieve with card details
link-cli spend-request retrieve lsrq_abc123 --include card --format json
Approved response
{
"id": "lsrq_abc123",
"status": "approved",
"card": {
"brand": "visa",
"number": "4242424242424242",
"cvc": "100",
"exp_month": 6,
"exp_year": 2029,
"billing_address": {
"name": "Jenny Rosen",
"line1": "123 Main St",
"city": "San Francisco",
"state": "CA",
"postal_code": "94015",
"country": "US"
},
"valid_until": "2026-06-13T03:40:10Z"
}
}
Enter the card number, CVC, expiry, and billing address into the seller’s checkout form.
Spend request statuses
Spend requests transition through multiple states during their lifecycle. It is recommended to retrieve the spend request often to understand its current state. If your agent encounters issues or errors completing a payment, retrieve the latest spend request status and check for requires_action.
| Status | Meaning | What to do |
|---|---|---|
created | The request exists and approval hasn’t been requested yet. | Call request-approval, or keep polling if you passed --request-approval. |
pending_approval | Waiting on the customer to approve. | Keep polling. |
requires_action | Something needs attention before the request can proceed. | Branch on the next action. |
approved | The customer approved. Credentials are available. | Retrieve the credential and pay. |
submitted | The payment is in progress and will complete. | Nothing. Optionally report the outcome. |
succeeded | The payment completed. | Nothing. Optionally report the outcome. |
failed | The payment failed and the request is terminal. | Read payment_status_details for the reason before retrying. |
denied | The customer declined. | Tell the customer. Don’t retry without creating a new request. |
expired | The customer didn’t respond within the approval window, or the credential wasn’t consumed within the expiry window. | Create a new request if the customer still wants to proceed. |
canceled | The request was canceled before completion. | Create a new request if the customer still wants to proceed. |
Handle next actions
A status of requires_action means the payment method or the account needs attention. It’s not a decline, and it can appear at creation or later from retrieve. Read status_details.requires_action.next_action:
| Field | Description |
|---|---|
type | What the customer needs to do. |
resolution | How your agent should proceed to resolve the issue. |
display_message | Customer-facing text you can show as-is. |
action_url | A Stripe-hosted URL to send the customer to, or null. |
expires_at | When action_url stops working, as an ISO 8601 timestamp string. null when it doesn’t expire. |
Branch on resolution, not on type. The same underlying problem may appear as different types depending on context.
| Resolution | What it means | What to do |
|---|---|---|
auto_resume | Once the consumer completes the action, this spend request will be available. | Send the customer to action_url, then keep polling the same request. Only three_d_secure uses this today. |
create_new_spend_request | The request is unusable and a new one should be created once the issue is resolved. | Create a new request, adjusting whatever caused the problem. |
create_new_spend_request_after_completion | The request is unusable and a new one should be created once the issue is resolved. | Send them to action_url, wait for them to finish, then create a new request. |
The action types and their resolutions:
| Action type | Resolution | What happened |
|---|---|---|
three_d_secure | auto_resume | The issuer wants a 3D Secure challenge. The request resumes on its own. |
three_d_secure_retry | create_new_spend_request | The challenge wasn’t completed. |
select_payment_method | create_new_spend_request | The payment method was declined. Ask the customer to pick another. |
update_payment_method | create_new_spend_request | The payment method needs updating. |
re_authorize | create_new_spend_request | The charge exceeded the approved amount. Create a request for the correct amount. |
add_payment_method | create_new_spend_request_after_completion | No eligible payment method is on file. Ask the customer to add one. |
ssn_verification | create_new_spend_request_after_completion | The customer needs to verify their identity. |
identity_verification | create_new_spend_request_after_completion | The customer needs additional identity verification. |
contact_support | create_new_spend_request_after_completion | Send the customer to Link support. |
Only auto_resume keeps the existing request alive. For all other types, the old request expires on its own. Don’t cancel it first, and don’t poll it.
Note
You can also check for known blockers before you start, which saves your customer a failed approval:
Check limits and verification state
link-cli user-info retrieve --format json
The response includes agent_wallet_spend_limits, with per_transaction, daily, and thirty_day entries carrying limit, used, and remaining. Finite values are in cents; null means unlimited. It also includes agent_wallet_verification_requirement, whose status and nullable action_url tell you whether the customer must verify something first.
Read a failed payment
When a payment fails, the reason is on payment_status_details:
| Field | Description |
|---|---|
outcome | success or failure. |
code | The failure code, when there is one. |
decline_code | The issuer’s decline code, when the issuer declined. |
amount and currency | What was attempted. |
refund_details | Amount, currency, state, and creation time, when a refund exists. |
Increasing the amount on an approved request
Agents may not always know the final price at approval time. A checkout might need card details before it reveals shipping or tax. To increase the amount on an already approved request, update the request, then ask for approval again:
Raise the amount
link-cli spend-request update lsrq_abc123 --amount 7500
link-cli spend-request request-approval lsrq_abc123
link-cli spend-request retrieve lsrq_abc123 --interval 2 --max-attempts 300
Pass the new total, not the difference. Raising the amount requires the customer to approve again. On approval, Link increments the authorization so the card covers the higher amount.
Before requesting an increase, check that the customer has already approved the request and that you haven’t successfully used the card yet. A virtual card can’t be used twice.
update also accepts --merchant-url, --currency, --payment-method-id, --line-item, and --total, so you can correct what the customer sees along with the amount.
Increases aren’t guaranteed. A safe default is to try, and fall back to canceling and recreating on error. The spend request stays usable at its original amount in the case it could not be increased.
Pay with a Link Pay Token
Stripe checkout pages expose a field that accepts a Link Pay Token. This token allows your agent to complete the entire payment step, including billing and shipping addresses, without needing to manually form fill all the fields.
Link Pay Tokens are available alongside a virtual card, not as a separate credential type.
- Open the checkout page and find the steering block. It’s visually hidden and often inside a Stripe iframe, so search the top document and every Stripe frame for
.AiAgentPaymentSteeringor an “I am an AI agent” checkbox. Run the remaining steps in whichever frame contains it. - Check the checkbox with a DOM-level click, because the control is hidden from the keyboard: Reveal the block
document.querySelector('.AiAgentPaymentSteering input[type="checkbox"]').click(); - Confirm both markers appear in that same frame within a few seconds, and read the account ID: Read the merchant account ID
const merchantAccountId = document.querySelector('.AiAgentPaymentSteering [data-stripe-merchant-account]')?.getAttribute('data-stripe-merchant-account');Ifinput[name="link_pay_token"]is missing, ormerchantAccountIdis empty, stop and create an ordinary virtual card request instead. - Create the spend request. Include
--merchant-account-idand--execution-method link_pay_token, and omit--merchant-nameor--merchant-url: Create a bound requestlink-cli spend-request create \ --execution-method link_pay_token \ --merchant-account-id acct_YOUR_MERCHANT_ACCOUNT \ --amount 3500 \ --context "Purchasing a subscription from the checkout page the agent inspected. The customer initiated this purchase through the shopping assistant." \ --total "type:total,display_text:Total,amount:3500" - After approval, retrieve the token. Each token is valid for up to 30 minutes, or until the spend request expires: Retrieve the token
link-cli spend-request retrieve lsrq_abc123 --include link_pay_token --format json - Inject it with the native value setter instead of typing it character by character: Inject the token
const input = document.querySelector('input[name="link_pay_token"]'); Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set.call(input, token); input.dispatchEvent(new Event('input', {bubbles: true})); - Wait for the card form to be replaced by a single saved card showing the customer’s email, then submit the payment.
Using saved shipping addresses
When a checkout needs a shipping address, your agent can use the ones already saved in their Link Agent Wallet:
List shipping addresses
link-cli shipping-address list --format json
Each record has an id, an is_default flag, a nullable nickname, and a nullable address. Use the default unless the customer picks another.
Address fields use line_1, line_2, locality, dependent_locality, administrative_area, postal_code, sorting_code, and country_code, and any of them can be null.
Report outcomes
Reporting what happened after a purchase attempt is optional, and it’s how Stripe finds the checkout flows that break agents and need improving.
Report a successful purchase
link-cli report \
--domain shop.example.com \
--outcome success \
--spend-request-id lsrq_abc123
--outcome is success when the order was confirmed, blocked when something stopped your agent, or abandoned when your agent chose to stop.
Add --tag to classify what happened. It’s repeatable, and the values are stripe_checkout, captcha, anti_bot_script, cdn_block, waf_block, dns_block, rate_limited, login_required, 3ds_challenge, page_inaccessible, timeout, site_error, payment_declined, and other. Prefer the most specific tag, and describe anything else in --freeform-context.
Report a blocked attempt
link-cli report \
--domain shop.example.com \
--outcome blocked \
--spend-request-id lsrq_abc123 \
--tag captcha \
--step "checkout page" \
--freeform-context "Turnstile challenge appeared after clicking Pay now"
--step records where your agent was when the outcome happened, up to 500 characters. --attempt-trace records the whole path, as one numbered line per step giving the URL path, what your agent acted on, the action, and the result. Send a trace on failures too: the dead ends are what stop the next agent from repeating them.
Leave the buyer’s personal data out of a trace. Write [email] and [address] in its place. Traces longer than 8,000 characters are truncated by the server instead of rejected, so send the full narrative instead of skipping the report.
Manage spend requests
List, inspect, and cancel
link-cli spend-request list
link-cli spend-request retrieve lsrq_abc123
link-cli spend-request cancel lsrq_abc123
Use list to return active requests, and add --include-history for expired and terminal ones. retrieve shows the transaction’s details, including refunds and payment events. Use cancel when your agent needs to cancel an unused request.
Test your integration
Pass --test to create a test mode spend request. It returns test credentials, such as card number 4000009990001984, and never charges the underlying payment method:
Test mode spend request
link-cli spend-request create \
--test \
--amount 3500 \
--context "Purchasing 'Working in Public' from press.stripe.com. The customer initiated this purchase through the shopping assistant." \
--merchant-name "Stripe Press" \
--merchant-url "https://press.stripe.com"