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

# Check for stored cards

> Ask what a user already has stored, so a returning user is never sent through enrollment twice.

Ask what a user already has before you send them anywhere. A user with a card on file goes straight to a purchase; only a user with an empty vault needs a vault session.

```bash theme={null}
curl "https://api.agentcard.sh/api/v2/vault_cards?user_id=usr_8f3k2m" \
  -H "Authorization: Bearer $ORG_TOKEN"
```

```json theme={null}
{
  "object": "list",
  "data": [
    {
      "object": "vault_card",
      "id": "cmturxopj0004cbpswhqdeyju",
      "brand": "visa",
      "last4": "4832",
      "expiry_month": 12,
      "expiry_year": 2029,
      "created_at": "2026-09-02T18:41:07Z"
    }
  ]
}
```

`$ORG_TOKEN` must be a client-credentials access token. An API key is refused with `400 client_credentials_required`.

## Read the response

Display fields only, newest card first. The response never includes a card number, and never anything that could decrypt one.

| Field                         | Type    | Description                                                                                                   |
| ----------------------------- | ------- | ------------------------------------------------------------------------------------------------------------- |
| `id`                          | string  | An opaque id with no prefix, such as `cmturxopj0004cbpswhqdeyju`. Pass it as `card_id` to pay with this card. |
| `brand`                       | string  | `visa`, `mastercard`, `amex`, `discover`, …                                                                   |
| `last4`                       | string  | The last four digits, for showing the user which card you mean.                                               |
| `expiry_month`, `expiry_year` | integer |                                                                                                               |
| `created_at`                  | string  | When the user stored it.                                                                                      |

## Send a link only when empty

An empty `data` array means the user has a vault and nothing in it. A 404 means the user is not yours yet, and carries no `data` at all, so check the status before you read the body:

```ts theme={null}
const res = await fetch(
  `https://api.agentcard.sh/api/v2/vault_cards?user_id=${userId}`,
  { headers: { Authorization: `Bearer ${orgToken}` } },
);

if (res.status === 404) {
  await sendVaultLink(userId);        // not your user yet, enroll them
  return;
}
if (!res.ok) throw new Error((await res.json()).error.message);

const { data: cards } = await res.json();

if (cards.length === 0) {
  await sendVaultLink(userId);        // create a vault session, send its url
} else {
  await startPurchase(userId, cards[0].id);
}
```

Tell the user which card you are about to use: "your Visa ending 4832". They approve every purchase anyway, and naming the card is what turns the approval into a yes or no instead of a question.

## Handle a user you don't know

A user id your company has never enrolled is not an empty vault. It is a 404, because there is no user of yours to read:

```text theme={null}
{
  "error": {
    "code": "connection_not_found",
    "message": "No production connection exists for that user. Sandbox and production users are separate: run the connect flow (POST /api/v2/connect/start) for this user with your production credentials first."
  }
}
```

In the Vault, enrollment is a vault session, not a connect call: [create one](/vault/adding-a-card) and send the user its link. Treat `connection_not_found` and an empty `data` array as the same next step.

## Keep the modes apart

A sandbox token reads sandbox users, and a production token reads production users. A production `user_id` asked for with sandbox credentials is a `connection_not_found` 404, not an empty vault, and the message names the mode you asked in.

## Learn when a card lands

Point a [webhook endpoint](/webhooks/overview) at your server and Agentcard tells you when a card lands through a link you minted.

| Event                                                          | When                                                                                                                |
| -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| [`vault.session_linked`](/webhooks/vault/vault-session_linked) | The session got its user. Carries the `user_id` to store.                                                           |
| [`vault.card_stored`](/webhooks/vault/vault-card_stored)       | A card landed through one of your sessions, while that session was still live. Carries `card_id`, `brand`, `last4`. |

`vault.card_stored` reports the cards Agentcard can attribute to you, not every card the user owns. A user who signs in to the Vault on their own and adds a card, or who adds one after your link's window closed, stores it successfully and sends you no event. So treat the webhook as a prompt, and the read above as the answer: check the vault when you are about to act for a user, rather than trusting a tally you kept from events.
