Display sensitive card details and PIN in a secure hosted iframe — card data and PIN each require a separate view token
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.
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:
Your backend calls POST /yativo-card/{yativoCardId}/cards/{cardId}/view-token
Yativo returns a secure_view_url — a hosted, tokenized page
Your frontend embeds that URL in an <iframe>
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.
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.
interface ViewTokenResponse { 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 enabled_views: string[]; // Which views are active: ["data"] | ["pin"] | ["data", "pin"] requires_access_code: boolean; // True if access_code was set; user must enter it before details are shown}
Your frontend must never call the Yativo API directly with your credentials. Always proxy the token request through your backend:
Express.js
TypeScript SDK
Frontend (after fetch)
React
Copy
Ask AI
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']; 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/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 } : {}), }), } ); if (!response.ok) { return res.status(502).json({ error: 'Failed to get view token' }); } const data = await response.json(); // Return only the URL — do not cache it res.json(data.data);});
Copy
Ask AI
import { YativoSDK } from '@yativo/crypto-sdk';const sdk = new YativoSDK({ baseURL: 'https://crypto-api.yativo.com/api/', apiKey: process.env.YATIVO_API_KEY!, apiSecret: process.env.YATIVO_API_SECRET!,});// In your backend route handler:const result = await sdk.cards.getViewToken( 'yc_01HX9KZMB3F7VNQP8R2WDGT4E5', // yativoCardId 'card_01HX9KZMB3F7VNQP8R2WDGT4E5', // cardId { enabled_views: ['data', 'pin'], theme: { accent_color: '#813AE3', border_radius: 16, }, });// Send result.data.secure_view_url to your frontendreturn res.json({ url: result.data.secure_view_url });
Copy
Ask AI
<!-- Your frontend fetches the URL from your backend, then sets it on the iframe --><div id="card-view-wrapper"> <iframe id="card-iframe" title="Secure Card View" width="420" height="740" style="border:0;border-radius:16px;" allow="clipboard-read; clipboard-write"> </iframe></div><script> async function showCardDetails() { // Fetch from YOUR backend — never call Yativo directly from the browser const res = await fetch('/api/card-view-token', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ enabled_views: ['data', 'pin'] }), }); if (!res.ok) { console.error('Failed to get card view token'); return; } const { secure_view_url } = await res.json(); document.getElementById('card-iframe').src = secure_view_url; } showCardDetails();</script>
View current PIN / set new PIN — no card number tab
["data", "pin"]
Both tabs, user can switch between them
Card data and PIN are rendered by separate internal components on the hosted page. Each view type is independently secured — requesting ["data"] does not expose any PIN functionality.
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
Copy
Ask AI
# Request with access codecurl -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
Use the test script in the backend repo to verify the full flow against your running server:
Copy
Ask AI
# Basic test (picks first card on the account)node scripts/test-card-secure-view.js <api_key> <api_secret># Target a specific cardnode scripts/test-card-secure-view.js <api_key> <api_secret> <yativo_card_id> <card_id># With custom views and themeENABLED_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.