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

# VAN Reveal

> Mount a Fluz-hosted viewer in your page to show a user the account and routing number of their own virtual account number, without the data touching your servers or your page's JavaScript.

<Note>
  This page assumes you've read [Secure Elements
  Overview](/build-a-platform/secure-elements-overview) — it covers minting a
  client token, loading the SDK, and styling fields, all of which apply here.
</Note>

VAN Reveal is the same capability as [Card Reveal](/build-a-platform/card-reveal), applied to a [virtual account number](/features/virtual-account-numbers) instead of a virtual card: it shows a user the account number and routing number for one of their own VANs, inside Fluz-hosted frames your page can't read into.

## Mint a reveal token

Call [`POST /v1/client-token`](/build-a-platform/secure-elements-overview#mint-a-client-token) with `"purpose": "van-reveal"` and the `virtualAccountNumberId` you want to show:

```json theme={null}
{
  "purpose": "van-reveal",
  "virtualAccountNumberId": "9c1f6b2e-4d7a-4c3b-9f11-2a5e8b0d6c74"
}
```

Pass the `clientToken` and `loadToken` it returns straight into `createAccountViewer` below.

Minting this token requires the `LIST_PAYMENT` OAuth scope — the same scope [`getSpendAccountVirtualAccountNumbers`](/features/virtual-account-numbers#retrieve-a-user-s-virtual-account-numbers) requires, so an integration already listing VANs needs no new provisioning to reveal one. This is distinct from Card Reveal's `CREATE_VIRTUALCARD` and Secure Card Input's `MANAGE_PAYMENT`.

## Create the viewer

```js theme={null}
const viewer = createAccountViewer({
  clientToken,
  loadToken,
  frameHostOrigin: "https://staging.secure.fluz.app",
  fields: ["accountNumber", "routingNumber"],
});
```

`fields` controls which pieces of the VAN render, in order — omit it and you get `["accountNumber", "routingNumber"]`. Each entry is either a bare field name or a `{ field, individualReveal }` object, the same shape as `createCardViewer`'s `fields`. `accountNumber` and `routingNumber` are the only valid field names — anything else throws a `FluzElementsError` (`error.code === "INVALID_FIELD"`) synchronously, from `createAccountViewer` itself, before you ever call `mount()`.

| Field | Masked placeholder, before any reveal | Notes |
| :- | :- | :- |
| `accountNumber` | `••••{last4}` | Last 4 digits come from the VAN metadata on your client token |
| `routingNumber` | The real routing number — not masked | Not vault data — renders in cleartext as soon as the field mounts, before any `reveal()` call |

<Note>
  Only `accountNumber` is vault data. `routingNumber` is cleartext metadata
  Fluz already has at grant-mint time, so it renders immediately and
  `reveal()`/`reveal("routingNumber")` has no effect on it — there's nothing
  to fetch. `individualReveal` on `accountNumber` behaves exactly like it does
  on [Card Reveal](/build-a-platform/card-reveal#create-the-viewer)'s fields:
  it defaults to `true`, and setting it to `false` prevents that field from
  being revealed on its own via `reveal("accountNumber")` — see [Reveal a
  single field](/build-a-platform/card-reveal#reveal-a-single-field) for the
  same pattern applied to a card field.
</Note>

## Mount it

```js theme={null}
await viewer.mount(document.getElementById("account-viewer"));
```

`mount()` appends one sandboxed iframe per configured field into the container element you pass it, and returns a promise that resolves once every frame has completed its handshake. It rejects with a `FluzElementsError` the same way `createCardViewer`'s viewer does — see [Card Reveal's Mount it](/build-a-platform/card-reveal#mount-it) for the full list of `INVALID_STYLE` / `MOUNT_TIMEOUT` / `MOUNT_FAILED` cases.

## Reveal and mask fields

```js theme={null}
await viewer.reveal(); // fetch and show every configured field at once
await viewer.reveal("accountNumber"); // fetch and show just the account number
viewer.setMask("accountNumber", true); // re-mask a field that's already been revealed
viewer.setMask("accountNumber", false); // un-mask it again -- see below
```

`reveal()`, `reveal(field)`, and `setMask()` behave exactly as they do on [Card Reveal](/build-a-platform/card-reveal#reveal-and-mask-fields) — the same async/sync split, the same `{ hidden: true }` option to blank a field completely instead of showing its masked placeholder, and the same `destroy()` to tear down every frame. Calling either against `routingNumber` is a no-op beyond toggling its own masked/unmasked display state, since that field is never fetched from the reveal endpoint.

## Handle events

```js theme={null}
const unsubscribeMount = viewer.onMount(() => {
  // all configured fields have finished rendering
});

const unsubscribeError = viewer.onError((error) => {
  // error.code, error.message
});
```

Same shape, and the same error codes, as [Card Reveal's Handle events](/build-a-platform/card-reveal#handle-events) — `INVALID_FIELD`, `INVALID_FRAME_HOST_ORIGIN`, `INVALID_STYLE`, `MOUNT_TIMEOUT`, `MOUNT_FAILED`, `INDIVIDUAL_REVEAL_DISABLED`, `FIELD_ERROR`, and `RATE_LIMITED`.

## Full example

```html theme={null}
<div id="account-viewer"></div>

<script src="https://secure-cdn.fluz.app/secure-elements/v0.3.0/index.global.js"></script>
<script>
  (async () => {
    const { createAccountViewer } = FluzSecureElements;

    const res = await fetch("/mint-van-reveal-token", { method: "POST" });
    const { clientToken, loadToken } = await res.json();

    const viewer = createAccountViewer({
      clientToken,
      loadToken,
      frameHostOrigin: "https://staging.secure.fluz.app",
      style: {
        fontFamily: "Inter",
        fontSize: "16px",
        color: "#1a1a1a",
      },
    });

    viewer.onError((error) => console.error(error.code, error.message));
    viewer.onMount(() => console.log("account viewer ready"));

    await viewer.mount(document.getElementById("account-viewer"));
  })();
</script>
```

`/mint-van-reveal-token` is your own backend route — the one that calls `POST /v1/client-token` with `"purpose": "van-reveal"` and your Fluz OAuth access token, as described in [Mint a client token](/build-a-platform/secure-elements-overview#mint-a-client-token).

## Next steps

<CardGroup cols={2}>
  <Card title="Secure Elements Overview" icon="book-open" href="/build-a-platform/secure-elements-overview">
    Token minting, SDK loading, styling, and CSP.
  </Card>

  {" "}

  <Card title="Card Reveal" icon="eye" href="/build-a-platform/card-reveal">
    The same capability, applied to a virtual card's PAN, expiry, and CVV.
  </Card>

  {" "}

  <Card title="Virtual Account Numbers" icon="building-columns" href="/features/virtual-account-numbers">
    How a VAN is provisioned, and how to list the ones on a spend account.
  </Card>

  <Card title="Secure Card Input" icon="credit-card" href="/build-a-platform/secure-card-input">
    Collect a physical card and add it as a payment method.
  </Card>
</CardGroup>


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