> ## Documentation Index
> Fetch the complete documentation index at: https://conto.finance/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Stripe Link cards for agents

> Conto integrates with Stripe Link so your agent asks Stripe Link for a card only after Conto approves the purchase.

Stripe Link is Stripe's payment wallet. With Link CLI, Stripe's command-line tool, an agent can ask
Stripe Link for a virtual card for one purchase. The person approves each card in their Link app,
and Stripe Link issues it.

Conto integrates with Link CLI so the agent asks Conto first. Conto checks the purchase against the
owner's rules. Only after Conto approves does Link CLI ask Stripe Link for a card, for exactly the
approved amount at that store.

If Conto does not approve, Stripe Link is never asked, so no card exists.

## How it works

1. At the payment step, once the final total is known, the agent's code calls
   `conto.checkout.getLinkCard()`.
2. Conto checks the purchase against the agent's rules and holds the amount against its limits.
   When a rule requires it, the owner approves in Conto first.
3. After Conto approves, Stripe's Link CLI on your machine asks Stripe Link for a card for that amount at that
   store. The person approves it in the Link app.
4. Your code gets the card and fills in the store's card form. Keep the card out of any model
   prompt, tool result, or log.
5. Right before paying, the agent calls `confirm()`. After paying, it reports the outcome.

Stripe Link sends the card to your machine. Conto does not receive the card number or security code.
Conto stores only the Link request ID, so the owner can see which Link request paid.

## Set up

Install Stripe's Link CLI on the machine where your agent runs, and log in with the Link account that pays:

```bash theme={null}
npm i -g @stripe/link-cli
link-cli auth login
npm install @conto_finance/sdk
```

The person who approves needs the Link app. Nobody connects Link to Conto, and there is no Link
setup in the Conto dashboard. You also need an agent and its standard SDK key, as for
[checkout checkpoints](/docs/guides/checkout-checkpoints).

## Get a card with the SDK

```typescript theme={null}
import { Conto } from '@conto_finance/sdk';
import { linkCliWallet } from '@conto_finance/sdk/link-wallet';

const conto = new Conto({ apiKey: process.env.CONTO_API_KEY! });
const wallet = linkCliWallet({ test: true });

// At the payment step, once the final total with shipping and tax is known.
const result = await conto.checkout.getLinkCard({
  idempotencyKey: order.id,
  merchant: { id: 'acme-retail', name: 'ACME Retail', checkoutUrl: page.url() },
  amountMinor: 1500,
  purpose: 'Phone charger the shopper chose',
  lineItems: [{ name: 'USB-C phone charger', quantity: 1, unitAmountMinor: 1500 }],
  wallet,
});

// Fill in result.card in the store's card form.

// Right before paying.
const confirmed = await conto.checkout.confirm(result.checkpoint, {
  amountMinor: 1500,
  paymentReference: result.paymentReference,
});

// Pay, then report what happened.
await conto.checkout.reportOutcome(confirmed.id, {
  outcome: 'PAID',
  reference: orderNumber,
  paymentReference: result.paymentReference,
});
```

`getLinkCard()` takes the same purchase details as `checkout.begin()`, plus the wallet. The
checkout method is always `LINK`. It waits for the owner when a rule requires approval, then for
the person in Link. Two optional settings limit each wait, and both default to 10 minutes:
`ownerApprovalTimeoutMs` and `linkApprovalTimeoutMs`. Pass `signal` to stop early.

Retrying `getLinkCard()` with the same input resumes the same checkpoint and the same Link
request, so the person is not asked twice.

### The card

| Field | Meaning |
| - | - |
| `number` | Card number |
| `securityValue` | The card's CVC |
| `expMonth` | Expiry month |
| `expYear` | Expiry year, four digits |
| `validUntil` | When Link stops accepting the card |
| `last4` | Last four digits |
| `brand` | Card brand, when Link reports it |

The card stays in your agent's process. Printing or serializing the result shows only the last
four digits, the brand, and the expiry. Use the card only in the store's card form for this
purchase.

`linkCliWallet()` runs Link CLI without a shell and reads the card from a private temporary file
that it deletes before the call returns. Its options are `binary` (defaults to `link-cli` on PATH),
`test`, `paymentMethodId` (defaults to the person's default payment method), `timeoutSeconds`, and
`env`.

## When the total changes

Call `getLinkCard()` at the payment step, when the total includes shipping and tax. The card
covers the amount Conto approved, so `confirm()` refuses a different total for it with
`LINK_CARD_TOTAL_CHANGED`.

If the total changes anyway, get a new card for the final total:

```typescript theme={null}
const replacement = await conto.checkout.replaceLinkCard(result, {
  wallet,
  amountMinor: finalTotal,
});
```

`replaceLinkCard()` cancels the old Link request and checkpoint, checks the final total against
the agent's rules, and gets a new card. Confirm `replacement.checkpoint` with the final total.

## When something goes wrong

If any call throws, do not pay.

| Error | When | What Conto did |
| - | - | - |
| `ContoError` | The agent's rules denied the purchase | Held nothing. Link was never asked |
| `CheckoutApprovalDeniedError` | The owner rejected the purchase | Released the spend. Link was never asked |
| `CheckoutApprovalExpiredError` | The owner did not decide in time | Released the spend. Link was never asked |
| `CheckoutApprovalTimeoutError` | `ownerApprovalTimeoutMs` passed first | Kept the spend held. Retry with the same input, or cancel |
| `LinkCardDeniedError` | The person declined the card in Link | Cancelled the checkpoint and released the spend |
| `LinkCardExpiredError` | The Link request expired before the person approved it | Cancelled the checkpoint and released the spend |
| `LinkCardTimeoutError` | `linkApprovalTimeoutMs` passed first | Cancelled the Link request and the checkpoint |
| `LinkWalletUnavailableError` | Link CLI is not installed or not logged in on this machine | Cancelled the checkpoint and released the spend |

The four Link errors extend `LinkCardError`, which also covers other Link failures. Each has
`holdReleased`, which is `false` when Conto could not cancel the checkpoint. Cancel it with
`conto.checkout.cancel(error.checkpoint.id)` in that case. Error messages never contain card data.

A `LinkCardError` with code `LINK_CARD_NOT_CANCELLED` means Conto could not cancel the Link
request, so Link could still issue its card. Conto keeps the spend held and returns the
`linkRequestId`. Retry with the same input, or cancel the request with
`wallet.cancel(error.linkRequestId)` before `conto.checkout.cancel(error.checkpoint.id)`. The
same code without a `linkRequestId` means the wallet failed while asking Link for the card, so Conto
cannot tell whether Link created the request. Retry with the same input to resume it.

## Test mode

`linkCliWallet({ test: true })` creates test-mode Link requests. Link returns test card data and
charges nothing. The person still approves each request in the Link app. Conto checks and records
test purchases like any other purchase.

## MCP

The Conto MCP server does not issue Link cards. A tool result goes into the model's context and
often into the MCP host's logs, so a card number and CVC must never be in one. Get the card with
`conto.checkout.getLinkCard()` in code the model does not see, and pass the model only the
`linkRequestId` and the card's last four digits.

The MCP checkpoint tools still take a `linkRequestId`. `checkout_checkpoint_confirm` and
`checkout_checkpoint_report_outcome` send it to Conto as the payment reference. With
`CONTO_LINK_WALLET` set to `link-cli`, `checkout_checkpoint_cancel` with a `linkRequestId` also
cancels that Link request, and keeps the spend held until Link cancels it or reports it closed. See [MCP tools](/docs/mcp/tools#stripe-link-cards).

## What this protects

This integration makes Conto's approval part of how the agent pays, as long as Conto is the agent's
only way to get a Link card. It stops:

* **An agent going off script.** Link is asked for a card only for a purchase Conto approved.
* **Overspending.** The card is limited to the approved amount, and the amount counts toward the
  agent's limits.
* **The wrong store.** Conto checks the store's domain against the owner's rules before any card
  exists, and the person sees the store in Link.
* **Too many purchases.** Count and velocity rules apply to each card request.

It does not stop a builder who also gives the agent raw Link access. An agent that can run Link CLI
itself, use a shell, or call Link with your credentials can get a card without asking Conto. Give
the agent the Conto SDK or the MCP tool, not Link CLI or a shell.

## What the owner sees

Each purchase appears in **Transactions** like any checkout. Its page shows the Link request ID
next to the order number, and each step that reported it. A purchase that needs approval appears
in **Alerts & Approvals** first.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.