> ## 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.

# Token catalog

> The exact shape of the token catalog, field by field, including what each optional field's absence means.

Read the catalog without guessing what a missing field means.

## Overview

The **token catalog** lists the mock tokens one API key may use, plus the
non-sensitive details an agent needs to choose between them: card brand, last
four digits, real expiry, and which sites a card or login applies to. Everything
sensitive in it is a [mock token](/reference/glossary#mock-token), so it is safe to
hand to an agent.

Fetch it three ways:

<CodeGroup>
  ```ts SDK theme={null}
  import { listTokens, tokensForUrl, defaultIdentity } from "@agentvault/playwright";

  const catalog = await listTokens();
  const narrowed = await listTokens({ url: "https://shop.example.com/checkout" });
  const { cards, logins, relatedLogins } = await tokensForUrl("https://shop.example.com/checkout");
  const me = defaultIdentity(catalog);
  ```

  ```text MCP theme={null}
  get_token_catalog, with an optional url argument.
  ```

  ```bash HTTP theme={null}
  curl -sG -H "Authorization: Bearer $SELF_AGENT_PAY_API_KEY" \
    "$SELF_AGENT_PAY_API_URL/v1/tokens" \
    --data-urlencode "url=https://shop.example.com/checkout"
  ```
</CodeGroup>

## Example

```json theme={null}
{
  "userId": "u7x2k",
  "identity": [
    {
      "label": "default",
      "tokens": {
        "first_name": "av_id_first_name_u7x2k",
        "last_name": "av_id_last_name_u7x2k",
        "full_name": "av_id_full_name_u7x2k",
        "email": "av_id_email_u7x2k",
        "phone": "av_id_phone_u7x2k",
        "phone_country": "av_id_phone_country_u7x2k",
        "phone_national": "av_id_phone_national_u7x2k",
        "phone_area": "av_id_phone_area_u7x2k",
        "phone_prefix": "av_id_phone_prefix_u7x2k",
        "phone_line": "av_id_phone_line_u7x2k"
      }
    }
  ],
  "addresses": [
    {
      "label": "ship",
      "tokens": {
        "street1": "av_addr_ship_street1_u7x2k",
        "street2": "av_addr_ship_street2_u7x2k",
        "city": "av_addr_ship_city_u7x2k",
        "state": "av_addr_ship_state_u7x2k",
        "zip": "av_addr_ship_zip_u7x2k",
        "country": "av_addr_ship_country_u7x2k"
      }
    }
  ],
  "cards": [
    {
      "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
    }
  ],
  "logins": [
    {
      "label": "github",
      "website": "github.com",
      "hasTotp": true,
      "hasUsername": true,
      "tokens": {
        "username": "av_login_github_username_u7x2k",
        "password": "av_login_github_password_u7x2k",
        "totp": "av_login_github_totp_u7x2k"
      }
    }
  ],
  "relatedLogins": []
}
```

## `TokenCatalog`

<ResponseField name="userId" type="string" required>
  The account id. It is also the suffix on every token.
</ResponseField>

<ResponseField name="connection" type="object">
  How the agent is connected, and what its card fills will type.

  * `auth`: `oauth` for a hosted MCP connection, whose settings are on
    [Connected apps](/dashboard/api-keys#connected-apps), or `api_key` for a pasted key,
    whose settings are on [API keys](/dashboard/api-keys).
  * `testCard`: `true` when every card fill types a test card. Nothing is
    charged, and a real store refuses the order, though a demo checkout or
    payment sandbox may accept it. The agent tells the user before it starts a
    checkout. This reflects the setting now; a browser session reads it when it
    starts, so a session opened before a change keeps filling the way it began
    (see [Fill a test card instead](/dashboard/api-keys#fill-a-test-card-instead)). Start a new session after a change.
  * `approveEveryPurchase`: `true` when the owner approves every purchase.
    Always `false` while `testCard` is `true`, since a test-card fill is never
    held for approval.
</ResponseField>

<ResponseField name="identity" type="TokenCatalogIdentity[]" required>
  Identity entries, shaped like `addresses`, with the default identity first.
</ResponseField>

<ResponseField name="addresses" type="TokenCatalogAddress[]" required />

<ResponseField name="cards" type="TokenCatalogCard[]" required />

<ResponseField name="logins" type="TokenCatalogLogin[]" required />

<ResponseField name="relatedLogins" type="TokenCatalogRelatedLogin[]">
  Present only on a catalog narrowed by URL.
</ResponseField>

## `TokenCatalogIdentity`

<ResponseField name="label" type="string" required>
  `default` is the account profile, and it always comes first. Every other
  entry is an identity the account holder added under **Other identities**,
  such as `work`, and carries its label in its tokens, for example
  `av_id_work_email_u7x2k`. Use `default` unless the account holder names
  another label.
</ResponseField>

<ResponseField name="missing" type="string[]">
  The fields the account holder has not filled in, for example `["last_name", "phone"]`.
  Their tokens are still listed, but a fill with one throws `token_not_found`
  instead of typing an empty value. Check it before a form that needs those
  fields, and ask the account holder to add them: to their profile for the
  `default` identity, or to that labelled identity under **Other identities**.
  One exception: for a stored number outside +1, the US phone segments
  (`phone_area`, `phone_prefix`, `phone_line`) are not listed but still throw
  `token_not_found`, since such a number has no segments to give. Use
  `phone_national` for it instead.
</ResponseField>

<ResponseField name="tokens.first_name" type="string" required />

<ResponseField name="tokens.last_name" type="string" required />

<ResponseField name="tokens.full_name" type="string" required />

<ResponseField name="tokens.email" type="string" required />

<ResponseField name="tokens.phone" type="string" required>
  The whole number in E.164 form, for example `+15550100134`, when the phone
  was saved with the current **Country code** and number fields. Fill it into a
  single international field. If the field's `maxlength` would cut it short, the
  fill is refused.

  A phone saved before those fields existed may have only its old free-text
  value. Then `phone` fills that text exactly as it was typed, for example
  `5550100134` with no country code, and is not converted to E.164. The account
  holder fixes this by saving the phone again on the **Identity** page. See
  [Identity and addresses](/dashboard/identity-and-addresses#older-phone-numbers).
</ResponseField>

<ResponseField name="tokens.phone_country" type="string">
  The country calling code, for example `+1` or `+91`. Resolves for any country.
  For an older free-text phone, it resolves only when the text is a readable
  international number starting with `+`, or a ten-digit North American number.
  Otherwise it throws `token_not_found`, and so does `phone_national`.
</ResponseField>

<ResponseField name="tokens.phone_national" type="string">
  The national number without the country code, for example `5550100134`.
  Resolves for any country. Use it with `phone_country` whenever a form splits
  the number into a country box and a number box.
</ResponseField>

<ResponseField name="tokens.phone_area" type="string">
  Area code, 3 digits.
</ResponseField>

<ResponseField name="tokens.phone_prefix" type="string">
  Exchange prefix, 3 digits.
</ResponseField>

<ResponseField name="tokens.phone_line" type="string">
  Line number, 4 digits.

  <Warning>
    **North American numbers only.** The 3-3-4 shape exists only under the +1
    numbering plan. For any other number these three throw `token_not_found`. That
    means use `phone_national`, not that the number is unusable.
  </Warning>
</ResponseField>

<Note>
  The five phone fields after `phone` are optional in the type. A deployment that
  predates them returns an identity without them, so check a key is present before
  filling it.
</Note>

## `TokenCatalogAddress`

<ResponseField name="label" type="string" required>
  Whatever the account holder named it, such as `ship`, or `card1` for an
  address typed while adding a card. The catalog does not link an address to a
  card, so ask the account holder which label is a card's billing address.
</ResponseField>

<ResponseField name="tokens" type="object" required>
  `street1`, `street2`, `city`, `state`, `zip` and `country`. Always all present.
</ResponseField>

Address and identity tokens are not locked to one site the way a login is. They
resolve on any [authorized destination](/dashboard/destinations), and anywhere
else the fill is refused with `destination_unauthorized`.

## `TokenCatalogCard`

<ResponseField name="brand" type="string" required />

<ResponseField name="last4" type="string" required />

<ResponseField name="nickname" type="string">
  The account holder's own name for the card, such as `Revolut gray`, set on the
  [Cards](/dashboard/cards) page. Absent when the card has no name. Match the
  user's words against it, and when more than one card could fit, ask which one
  to use, naming each with its brand and last four, since two cards can share a
  name. With a single candidate, use it without asking.
</ResponseField>

<ResponseField name="expiryMonth" type="number" required>
  **A real value, not a token.** Fill it directly.
</ResponseField>

<ResponseField name="expiryYear" type="number" required>
  A real value, like `expiryMonth`.
</ResponseField>

<ResponseField name="number" type="string" required>
  The mock card number token.
</ResponseField>

<ResponseField name="cvv" type="string" required>
  The mock security code token. Nothing is stored behind it: filling it starts
  [live CVV entry](/guides/live-cvv) on a deployment that offers it, and
  otherwise fails with `cvv_entry_required`.
</ResponseField>

<ResponseField name="websites" type="string[]">
  The hosts this card is registered for: the merchants named by its active
  allowlist agent cards. Absent for every other configuration, so its absence
  says nothing on its own. Read `anySite`.
</ResponseField>

<ResponseField name="testCard" type="boolean">
  Present and `true` when this card fills as a test card for the asking
  connection, the same as `connection.testCard`. Before the final pay click the
  agent names the card it is paying with (nickname, brand and last four), and
  says when it is a test card.
</ResponseField>

<ResponseField name="anySite" type="boolean">
  The card is not limited to a list of sites, so a catalog narrowed by URL
  returns it for every host.

  <Warning>
    `anySite` does not promise the fill will succeed. A **blocklist** card carries
    it too, because the hosts it excludes are the account holder's private
    settings and are never shown to the agent. Those hosts are refused at fill time
    with `merchant_blocked`. A site that is not an
    [authorized destination](/dashboard/destinations) is refused with
    `destination_unauthorized`.
  </Warning>
</ResponseField>

## `TokenCatalogLogin`

<ResponseField name="label" type="string" required>
  Derived from the website, for example `github`.
</ResponseField>

<ResponseField name="website" type="string">
  The registered host. Matches that host **or any subdomain**. Absent on older
  records.
</ResponseField>

<ResponseField name="extraWebsites" type="string[]">
  Additional sign-in hosts the account holder authorized, for example
  `authenticator.cursor.sh` on a `cursor.com` login. These match **exactly**,
  unlike `website`. Absent when there are none.
</ResponseField>

<ResponseField name="hasTotp" type="boolean">
  The login has authenticator-app 2FA. It says only that 2FA exists, never the
  secret. Absent otherwise.
</ResponseField>

<ResponseField name="hasEmail" type="boolean">
  The login has an agent-supplied mailbox on file. Only the flag is shared, never
  the address.
</ResponseField>

<ResponseField name="hasUsername" type="boolean">
  The login has a stored username.
</ResponseField>

<ResponseField name="tokens.password" type="string" required>
  Always present.
</ResponseField>

<ResponseField name="tokens.username" type="string">
  **Absent, not empty,** until a username is set. Present on a login opened with
  a `username`, because approval stores it. Otherwise it appears after
  `setLoginUsername()`.
</ResponseField>

<ResponseField name="tokens.email" type="string">
  The mailbox the login registered with. Fill **this** into a sign-in form's
  email field.

  Absent on a login that predates the field, on one the account holder saved with
  a single identifier, or when the account holder cleared it, which the dashboard
  allows only when a username exists.
</ResponseField>

<ResponseField name="tokens.totp" type="string">
  Present only when the login has 2FA. At fill time the backend turns it into the
  current code, usually 6 digits, and the proxy types it.
</ResponseField>

## `TokenCatalogRelatedLogin`

A login registered on another host of the **same site** as the URL the catalog
was narrowed to. Hosts only, no tokens.

<ResponseField name="label" type="string" required />

<ResponseField name="website" type="string" required>
  The host the login is registered for. Go there to sign in: a login is locked to
  its site and will not fill on the page you are on.
</ResponseField>

## What an absent field means

| Absent field | Means |
| - | - |
| `tokens.username` | No username is set. **Not** that it is empty |
| `tokens.email` | No agent-supplied mailbox, or the account holder cleared it |
| `tokens.totp` | No stored 2FA secret. Never invent a code |
| `websites` on a card | Any configuration other than an active allowlist. Read `anySite` |
| `anySite` on a card that has `websites` | The card **is** limited to certain sites. `websites` lists them |
| Both `anySite` and `websites` on a card | The card fills nowhere right now. Every agent card governing it is paused or at its limit, or its allowlist names no usable host. Fills are refused (`card_paused`, `limit_reached` or `merchant_blocked`) until a governing agent card is active again |
| `phone_country` and the other phone parts | The deployment predates them |
| `extraWebsites` | There are none |
| An empty `logins` with a non-empty `relatedLogins` | An account exists on a sibling host. Go there |
| An empty `logins` **and** an empty `relatedLogins` | No login for this site is visible to this key. A key limited to **Only selected** logins does not see the others, so ask the account holder to check before opening an account |
| An empty `cards` under a URL filter | No card for this site is visible to this key. A key limited to **Only selected** cards drops the others before the URL filter, so ask the account holder to check its card access |

## Related

* [Token grammar](/reference/token-grammar)
* [URL locking](/security/url-locking)
* [HTTP API](/reference/http-api)


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