Skip to main content
Cardholders must set their PIN before card details can be viewed.If pin_set is false the API returns a 422 PIN_NOT_SET error with a ready-to-use pin_setup_url. Open that URL so the cardholder can set their PIN, then retry the view-token request. See Setting a Card PIN.
Card details (PAN, CVV, expiry) and the card PIN each use separate view tokens. You must request the correct enabled_views for each use case. Never reuse a token between sessions.

How it works

Sensitive card data is never returned in plain API responses. Instead, Yativo hosts a secure card view page and provides you with a short-lived URL to embed in your application:
  1. Your backend calls POST /yativo-card/{yativoCardId}/cards/{cardId}/view-token
  2. If pin_set is false, the API returns PIN_NOT_SET and a pin_setup_url — direct the cardholder there first
  3. Once PIN is set, Yativo returns a secure_view_url — a hosted, tokenized page
  4. Your frontend embeds that URL in an <iframe>
  5. The hosted page renders the sensitive data directly — your application never touches the raw values
View tokens expire quickly. Always request a fresh token immediately before rendering and never cache or log secure_view_url.

Step 1 — Request a view token

Call this from your backend. The token is scoped to the views you request.
POST /yativo-card/{yativoCardId}/cards/{cardId}/view-token
yativoCardId
string
required
The Yativo Card account ID (yativo_card_id) returned during onboarding.
cardId
string
required
The card ID from card creation.
enabled_views
array
Which views to enable on the hosted page. Accepted values:
ValueTab shown
"data"Card number, CVV, expiry
"pin-view"Reveal current PIN
"pin-set"Set or change PIN
Combine freely, e.g. ["data", "pin-view", "pin-set"]. The page renders one tab per value.
access_code
string
Optional extra PIN or unlock code the user must enter before the card details are revealed. Adds an additional layer of protection for high-security flows.
theme
object
Optional per-request theme tokens for the hosted page wrapper. For full CSS control — including card data text colors and fonts — set persistent customization via PUT /api/card-issuer/pse-customization (see Custom CSS below). All theme fields are optional.
theme.accent_color
string
Primary brand color (hex). Default: #813AE3
theme.background_color
string
Page background color (hex).
theme.panel_color
string
Card panel background color (hex). When this value (or background_color) is a dark color, the page automatically applies color-scheme: dark to the card data frame, rendering card digits in white.
theme.text_color
string
Primary text color (hex).
theme.muted_color
string
Secondary/muted text color (hex).
theme.border_radius
number
Border radius in pixels.
theme.font_family
string
CSS font-family string (e.g. "Inter, sans-serif").
theme.logo_url
string
URL of your logo to display on the hosted page.
curl -X POST 'https://crypto-api.yativo.com/api/v1/yativo-card/yc_01HX9KZMB3F7VNQP8R2WDGT4E5/cards/card_01HX9KZMB3F7VNQP8R2WDGT4E5/view-token' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "enabled_views": ["data"]
  }'
{
  "success": true,
  "data": {
    "secure_view_url": "https://crypto-api.yativo.com/api/yativo-card/view/eyJhbGciOiJIUzI1NiIs...",
    "expires_at": "2026-03-26T12:01:00Z",
    "last_four": "4242",
    "card_type": "virtual",
    "holder_name": "Jane Doe",
    "pin_set": true,
    "enabled_views": ["data", "pin-view", "pin-set"],
    "requires_access_code": false
  }
}
interface ViewTokenSuccess {
  secure_view_url: string;          // Hosted page URL — embed this in an iframe
  expires_at: string;               // ISO 8601 — token is invalid after this
  last_four: string;                // Last 4 digits — safe to display without the iframe
  card_type: string;                // "virtual" | "physical"
  holder_name: string | null;       // Cardholder display name
  pin_set: boolean;                 // Always true when this response is returned
  enabled_views: string[];          // Which views are active — any combination of "data" | "pin-view" | "pin-set"
  requires_access_code: boolean;    // True if access_code was set; user must enter it before details are shown
}

interface ViewTokenPinNotSet {      // Returned when pin_set is false (HTTP 422)
  pin_setup_url: string;            // Hosted PIN setup page — direct the cardholder here
  expires_at: string;
  last_four: string;
  card_type: string;
  holder_name: string | null;
  pin_set: false;
}

Step 2 — Embed the iframe

Pass the secure_view_url directly as the src of an iframe. No SDK or additional JavaScript is required.
<iframe
  src="https://crypto-api.yativo.com/api/v1/card-view?token=eyJhbGciOiJIUzI1NiIs..."
  title="Secure Card View"
  width="420"
  height="740"
  style="border: 0; border-radius: 16px;"
  allow="clipboard-read; clipboard-write"
></iframe>
The hosted page is fully responsive. Recommended dimensions: 420 × 740px for card details, 420 × 520px for PIN-only views.

Step 3 — Backend proxy (required)

Your frontend must never call the Yativo API directly with your credentials. Always proxy the token request through your backend:
import express from 'express';

const app = express();
app.use(express.json());

app.post('/api/card-view-token', async (req, res) => {
  // 1. Authenticate your own user first
  const user = await authenticateRequest(req);
  if (!user) return res.status(401).json({ error: 'Unauthorized' });

  const { enabled_views, access_code, theme } = req.body;

  // 2. Validate enabled_views — only accept known values
  const VALID_VIEWS = ['data', 'pin-view', 'pin-set'];
  if (enabled_views && !enabled_views.every(v => VALID_VIEWS.includes(v))) {
    return res.status(400).json({ error: 'Invalid enabled_views' });
  }

  // 3. Look up this user's card IDs from your own database
  const { yativoCardId, cardId } = await getUserCardIds(user.id);

  // 4. Call Yativo from your backend — credentials never leave the server
  const response = await fetch(
    `https://crypto-api.yativo.com/api/v1/yativo-card/${yativoCardId}/cards/${cardId}/view-token`,
    {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.YATIVO_API_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        ...(enabled_views ? { enabled_views } : {}),
        ...(access_code ? { access_code } : {}),
        ...(theme ? { theme } : {}),
      }),
    }
  );

  const payload = await response.json();

  // PIN not yet set — return the setup URL so your frontend can redirect the cardholder
  if (!response.ok && payload.error_code === 'PIN_NOT_SET') {
    return res.status(422).json({
      error_code: 'PIN_NOT_SET',
      message: payload.message,
      pin_setup_url: payload.data?.pin_setup_url,
    });
  }

  if (!response.ok) {
    return res.status(502).json({ error: 'Failed to get view token' });
  }

  // Return only the URL — do not cache it
  res.json(payload.data);
});

View options reference

ValueTab labelWhat it renders
"data"Card DetailsCard number (PAN), CVV, expiry
"pin-view"View PINReveals the cardholder’s current PIN
"pin-set"Change PINForm to set or change the PIN
Pass an array with any combination:
  • ["data"] — card details only
  • ["pin-set"] — PIN setup only (e.g. initial onboarding)
  • ["data", "pin-view"] — card details + reveal PIN
  • ["data", "pin-set"] — card details + change PIN
  • ["data", "pin-view", "pin-set"] — all three tabs
pin_set reflects the last known state from card activity webhooks. If pin_set is false the view-token request returns a PIN_NOT_SET error and a pin_setup_url — not a secure_view_url. Direct the cardholder to the setup URL, wait for the webhook confirmation, then retry.

Access code flow

When you pass access_code in the token request, requires_access_code: true is returned. The hosted page will prompt the user to enter that code before revealing any card details. Use this for:
  • Confirming user intent before showing the full card number
  • Adding a secondary verification step in high-value contexts
# Request with access code
curl -X POST '.../view-token' \
  -d '{ "enabled_views": ["data"], "access_code": "7291" }'

# Your user will see a prompt to enter 7291 before card details are shown

Customization

The secure view page supports two levels of customization: per-request theme tokens and persistent CSS overrides.

Theme tokens (per request)

Pass a theme object in the view-token request to apply brand colors, fonts, and a logo. These take effect immediately on every URL you generate.
FieldWhat it affectsDefault
accent_colorActive tab, status indicators, spinner, eyebrow label#0f766e
background_colorPage background#f5f1ea
panel_colorHero header, tab strip, and iframe frame background#fffaf7
text_colorPrimary text#1e2a24
muted_colorInactive tab labels, secondary text#6b746e
border_radiusAll rounded corners in px (clamped 8–36)24
font_familyFont for the page shellsystem sans-serif
logo_urlLogo displayed in the header alongside the cardholder namenone
All fields are optional. Unset fields fall back to the defaults above.
curl -X POST '.../view-token' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "enabled_views": ["data", "pin-view", "pin-set"],
    "theme": {
      "accent_color": "#6366f1",
      "background_color": "#f8f8fc",
      "panel_color": "#ffffff",
      "border_radius": 16,
      "font_family": "Inter, sans-serif",
      "logo_url": "https://yourapp.com/logo.png"
    }
  }'

Custom CSS (persistent, issuer-level)

For fine-grained control over colors, typography, and spacing, you can supply raw CSS that is stored against your issuer account and applied to every secure view URL you generate. The page has two layers — your CSS affects them differently: Layer 1 — Page shell (injected directly into the page <style> block) The Yativo-rendered wrapper: background, header, tabs, status bar, frame container. All standard CSS applies normally. Layer 2 — Card data area (forwarded to the secure card SDK) The card number, expiry, and CVV are rendered inside a cross-origin secure iframe. Yativo extracts a specific set of selectors from your stylesheet and forwards them to the secure card SDK, which applies them to the card data elements. Background colors, borders, label fonts, and copy-button styles apply reliably. Card digit text color (color on .pse-card-value) is controlled by the card network’s own stylesheet — it does not update through this API alone. To enable custom digit text colors (e.g. white digits on a dark background), contact Yativo to activate program-level card data theming for your account.
For security, the following patterns are stripped before storage: @import, external url() references, expression(), and behavior:. Inline data URIs for images (data:image/png;base64,...) are allowed.

Supported selectors

Page shell
SelectorWhat it styles
bodyPage background
.panelOuter layout wrapper (no visual style by default)
.heroHeader card (background, border, shadow)
.eyebrow”Secure Card View” label
.tab, .tab.activeTab strip buttons (Card Details / View PIN / Change PIN)
.frameContainer around the secure card data area
.status, .status.ready, .status.errorStatus bar text
.footerCountdown timer
Card data area
SelectorWhat it overrides
.pse-containerCard data container (background, border, border-radius)
.pse-card-fieldIndividual field rows
.pse-card-labelField labels (“CARD NUMBER”, “EXPIRES”, “CVV”)
.pse-card-valueField value layout (background, font-size, padding). Digit text color is set automatically when a dark theme is detected — see Dark theme.
.pse-copy-buttonCopy-to-clipboard button (background, border, color)

Manage your customization

# Set
curl -X PUT 'https://crypto-api.yativo.com/api/v1/card-issuer/pse-customization' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{ "custom_css": "body { background: #f0f0f5; } .hero { background: #ffffff; } ..." }'

# Retrieve
curl 'https://crypto-api.yativo.com/api/v1/card-issuer/pse-customization' \
  -H 'Authorization: Bearer YOUR_API_KEY'

# Clear (revert to defaults)
curl -X PUT 'https://crypto-api.yativo.com/api/v1/card-issuer/pse-customization' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{ "custom_css": "" }'

Example: brand colours + Inter font

/* ── Page shell ── */
body  { background: #f8f8fc; }
.hero { background: #ffffff; border-color: rgba(0,0,0,0.08); }
.tab  { color: #6b7280; background: #ffffff; border-color: rgba(0,0,0,0.08); }
.tab.active { background: #6366f1; border-color: #6366f1; color: #ffffff; }
.frame { background: #ffffff; border-color: rgba(0,0,0,0.08); }
.status.ready { color: #6366f1; }
.footer { color: #9ca3af; }

/* ── Card data area ── */
.pse-container  { background-color: #ffffff; border: 1px solid rgba(0,0,0,0.08); border-radius: 12px; }
.pse-card-field { background-color: #ffffff; border-bottom-color: rgba(0,0,0,0.06); }
.pse-card-label { color: #9ca3af; font-size: 11px; font-family: Inter, sans-serif; letter-spacing: 0.05em; text-transform: uppercase; }
.pse-card-value { font-size: 14px; font-family: Inter, sans-serif; }
.pse-copy-button { color: #374151; background: transparent; border-color: rgba(0,0,0,0.12); }

Dark theme

Passing a dark color in theme.panel_color or theme.background_color is all that is needed to enable dark mode. The page wrapper measures the luminance of those colors and automatically sets color-scheme: dark on the card data frame — the card SDK responds by rendering digits in white. No extra flag or request parameter is required.
# Dark panel → white digits (automatic)
curl -X POST '.../view-token' \
  -d '{
    "enabled_views": ["data"],
    "theme": {
      "panel_color": "#1c1c24",
      "background_color": "#0f0f13",
      "text_color": "#f4f4f5",
      "accent_color": "#a78bfa"
    }
  }'
If card digits still appear dark after passing dark colors, the card program may not have dark-mode digit support configured at the card network level. Contact Yativo to verify your program’s configuration.

Known limitations

Copy button icon color cannot be changed. The clipboard SVG inside .pse-copy-button has a hardcoded fill. Setting color on .pse-copy-button affects the button border and background only — not the icon itself.
View PIN and Change PIN are always light-mode. These tabs are rendered entirely by the card network’s secure iframe and cannot be themed via custom CSS.

Testing

Use the test script in the backend repo to verify the full flow against your running server:
# Basic test (picks first card on the account)
node scripts/test-card-secure-view.js <api_key> <api_secret>

# Target a specific card
node scripts/test-card-secure-view.js <api_key> <api_secret> <yativo_card_id> <card_id>

# With custom views and theme
ENABLED_VIEWS=data,pin THEME_ACCENT_COLOR=#813AE3 \
  node scripts/test-card-secure-view.js <api_key> <api_secret>
The script will print the secure_view_url and a ready-to-paste iframe snippet.

Virtual Cards

Create and manage virtual cards.

API Reference — View Token

Full parameter reference for the view-token endpoint.