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

# Card storage

> Where a card number is stored, how it reaches a merchant form, and why it never reaches Agent Vault's backend.

A card number is stored only by Agent Vault's card vault provider, and it is
revealed only to the proxy, only for one authorized fill.

## The guarantee

**The card number is tokenized in the account holder's browser by the vault
provider's own iframe, never reaches the Agent Vault backend or its data store,
and is revealed only inside the proxy for a single fill that has already passed
every authorization check.**

The proxy is run by Agent Vault. At fill time it fetches the number from the
vault and holds it in memory while it types it into the merchant's field. After
that one fill, it keeps the number's spellings in memory until the session
closes, used only to scrub them from later output to the agent. See
[Screenshot redaction](/security/redaction#values-kept-for-scrubbing). See [What this does not protect against](#what-this-does-not-protect-against).

## How it works

### Adding a card

```mermaid theme={null}
sequenceDiagram
    autonumber
    participant U as Holder's browser
    participant V as Card vault provider
    participant AV as Agent Vault backend

    U->>U: Types the number into the vault provider's iframe
    Note over U,AV: The iframe is a different origin.<br/>Agent Vault's page cannot read it.
    U->>V: The iframe sends the number directly to the vault
    V-->>U: An opaque token id
    U->>AV: Save the card: token id, last four, brand, expiry, name, billing address
    Note over AV: The card number is never in this request
```

The property is structural rather than procedural. The dashboard page cannot read
the card field because the browser's same-origin policy forbids it. There is no
code path in which the number arrives at the Agent Vault backend and is then
discarded.

Our card vault provider holds a current PCI DSS Level 1 attestation as a
service provider. Agent Vault does not name the provider on this site, and makes
no other compliance claim. See
[Compliance scope](/security/model#compliance-scope).

### What Agent Vault stores

| Field | What it is |
| - | - |
| `userId` | The account the card belongs to |
| `cardId` | The card's own id, used in its tokens |
| `btTokenId` | An opaque reference to the card in the vault. Not a secret |
| `btFingerprint` | A deterministic, non-secret fingerprint, used to detect duplicates |
| `last4`, `brand` | Card metadata, not secret |
| `expiryMonth`, `expiryYear` | Card metadata, not secret |
| `cardholderName` | Personal data, not authentication data |
| `nickname` | Optional. Your own name for the card, such as "Revolut gray". Shown to your agent so it can ask which card to use |
| `billingAddressId` | A reference to a saved address |
| `createdAt` | When the card was added |
| `networkToken` | Optional, and empty on every card today. See [Network tokenization](#network-tokenization) |

The database also adds its own record id and creation time to every row. The
record has three more optional columns from earlier designs that nothing writes
any more; see [Legacy fields](#legacy-fields). Neither a card number nor a CVV is
written to a card record.

<Accordion title="Why the expiry is stored and the CVV is not">
  PCI DSS requirement 3.3.1 forbids storing sensitive authentication data after
  authorization, and the CVV is sensitive authentication data. The expiry is not:
  it is card metadata in the same class as the brand and the last four digits.
  Storing it lets the dashboard display a card without a call to the vault, and lets
  the agent fill the expiry directly from the catalog. See
  [CVV policy](/security/cvv-policy).

  The stored copy is the expiry a normal checkout uses. The token catalog gives the
  agent `expiryMonth` and `expiryYear` from the card record, and the agent types
  those values into the form. The proxy substitutes only the card number and the
  security code, never the expiry. If the stored expiry is out of date, the merchant
  receives the out of date expiry, even when the card vault holds a newer one.

  You cannot edit a saved card's expiry in the dashboard. When a card is reissued
  with a new expiry, delete the card on **Cards** and add it again. Agent cards on
  the deleted card show **No funding card** until you pick the new card as their
  **Underlying card**, and a limited API key needs the new card added to its access
  again. See [Delete a card](/dashboard/cards#delete-a-card).
</Accordion>

### Revealing a card at fill time

```mermaid theme={null}
sequenceDiagram
    autonumber
    participant A as Agent
    participant P as Proxy
    participant B as Agent Vault backend
    participant V as Card vault provider
    participant M as Merchant page

    A->>P: fill(#cardNumber, av_card_1_number_u7x2k)
    P->>P: Read the order total from the page
    P->>B: Authorize: scope, merchant rules, limits, approval
    B->>B: Record the transaction
    B-->>P: Allowed, with the vault token id
    P->>V: Reveal this token
    V-->>P: The card number and expiry
    P->>P: Binding checks: token owner matches the card's<br/>account, and the last four match
    P->>M: Types the number into the field
```

Two details of this ordering matter:

* **The transaction is recorded before the reveal.** This is what makes spending
  limits reliable, because an amount is reserved before any card number exists
  outside the vault. It also means a fill that fails after recording can leave a
  recorded spend; the [error codes](/sdk/error-codes) say which failures do.
* **The binding checks run after the reveal and before typing.** If the vault
  token's owner does not match the card's account, or the revealed last four do
  not match the card's, nothing is typed and the fill fails with `card_blocked`.

### What the agent receives

```json theme={null}
{
  "brand": "visa",
  "last4": "4242",
  "nickname": "Revolut gray",
  "expiryMonth": 11,
  "expiryYear": 2029,
  "number": "av_card_1_number_u7x2k",
  "cvv": "av_card_1_cvv_u7x2k",
  "anySite": true
}
```

The brand, last four and expiry are not secret, and the agent needs them to
complete a form and choose between cards. `nickname` is the name you gave the
card, if any, so the agent can match "my Revolut card" or ask which card you mean. The number and CVV are
[mock tokens](/reference/token-grammar).

### Duplicate cards

The vault deduplicates tokenization, so the same card number can produce the same
token id on more than one record: the same card added twice, or added by two
accounts. Deleting one record never revokes a token that another record still
uses, and a token tokenized for another account is never revoked on this
account's behalf.

### Deleting a card or an account

Deleting a card removes Agent Vault's record of it at once, and from that moment
no Agent Vault path can resolve its token. Revocation of the token in the vault is
scheduled immediately afterwards and retried if the vault is temporarily
unavailable. Account deletion does the same for every card. See
[Data and deletion](/dashboard/data-and-deletion).

## What this does not protect against

* **Exposure in the proxy.** The card number is in the proxy's memory while it
  is typed, and its spellings stay there, for scrubbing only, until the session
  closes. The proxy is Agent Vault infrastructure.
  [Attested detokenization](/security/attested-detokenization) is designed to
  narrow this and is off by default.
* **The merchant page.** Once typed, the number belongs to the merchant's form,
  including any script the merchant runs on that page.
* **A compromised dashboard account.** Someone signed in as the holder can add
  cards and change the rules that govern them.

## How to verify

<Steps>
  <Step title="Watch the add-card request">
    Open your browser's developer tools on the **Network** tab, then add a test
    card on the [Cards](/dashboard/cards) page.
  </Step>

  <Step title="Find where the number went">
    The card number is sent only to the vault provider's domain, from inside its
    iframe. The request to Agent Vault carries the token id, last four, brand,
    expiry, cardholder name, card name if you gave one, and billing address
    reference, and no card number.
  </Step>

  <Step title="Check what the agent can read">
    Call `get_token_catalog`. The card appears with its metadata and two tokens,
    as in the example above.
  </Step>
</Steps>

## Legacy fields

<Accordion title="Fields in the data model that are no longer written">
  Three optional columns remain in the card record from earlier storage designs.
  No code path writes them, a cleanup migration removes any remaining values, and
  they are dropped from the data model once empty.

  | Column | Previously held |
  | - | - |
  | `encryptedNumber` | Card number ciphertext under a key the backend held |
  | `envelope` | A key-management envelope used to reveal a card |
  | `encryptedCvv` | CVV ciphertext. Never returned to any client |

  Cards are now stored only in the vault, so authorization returns a vault token id
  rather than a decrypted number.
</Accordion>

## Network tokenization

<Warning>
  **Status: not active.** No card carries a network token.
</Warning>

The card record has an optional `networkToken` object for a card-scheme network
token. The data model and code support it, and it stays empty until scheme
onboarding is in place.

When it is populated, it will hold **non-sensitive references only**: an opaque,
scoped, revocable token reference, the token requestor id, the scheme, a
scheme-side reference and a provisioning status. Never a card number, never a
CVV, and never a cryptogram, which is single-use per transaction and is never
stored.

## Related

<CardGroup cols={2}>
  <Card title="CVV policy" icon="keyboard" href="/security/cvv-policy">
    Why the security code is never stored.
  </Card>

  <Card title="Attested detokenization" icon="microchip" href="/security/attested-detokenization">
    Revealing a card inside an attested enclave. Off by default.
  </Card>
</CardGroup>


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