Get Encrypted Card Details
curl --request GET \
--url https://api.fyatu.com/api/v3/cards/{cardId}/secure-details \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.fyatu.com/api/v3/cards/{cardId}/secure-details"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.fyatu.com/api/v3/cards/{cardId}/secure-details', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.fyatu.com/api/v3/cards/{cardId}/secure-details",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.fyatu.com/api/v3/cards/{cardId}/secure-details"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.fyatu.com/api/v3/cards/{cardId}/secure-details")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.fyatu.com/api/v3/cards/{cardId}/secure-details")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"success": true,
"status": 200,
"message": "Card details retrieved securely",
"data": {
"cardNumber": "ddrOoIvbcDUatUz+BZix3Xx9D7yeMMk8JQh4fzJU37HTuOpjBv87AdnGNiI=",
"cvv": "4utlLEFDG74XdMbQYlGFkQgK5kHZHjEiq4vPgOi+eg==",
"expirationDate": "xEXskjuLolxR0nECcWFlCaUPeHTi0BgMzURt2BEUVTPdG0U="
},
"meta": {
"requestId": "req_0b35110e09acca7a6dbd8ac8",
"timestamp": "2026-10-21T05:39:54+03:00"
}
}Cards
Get Encrypted Card Details
Fetch the card number, CVV and expiry encrypted to your own access token. GET /cards//secure-details.
GET
/
cards
/
{cardId}
/
secure-details
Get Encrypted Card Details
curl --request GET \
--url https://api.fyatu.com/api/v3/cards/{cardId}/secure-details \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.fyatu.com/api/v3/cards/{cardId}/secure-details"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.fyatu.com/api/v3/cards/{cardId}/secure-details', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.fyatu.com/api/v3/cards/{cardId}/secure-details",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.fyatu.com/api/v3/cards/{cardId}/secure-details"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.fyatu.com/api/v3/cards/{cardId}/secure-details")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.fyatu.com/api/v3/cards/{cardId}/secure-details")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"success": true,
"status": 200,
"message": "Card details retrieved securely",
"data": {
"cardNumber": "ddrOoIvbcDUatUz+BZix3Xx9D7yeMMk8JQh4fzJU37HTuOpjBv87AdnGNiI=",
"cvv": "4utlLEFDG74XdMbQYlGFkQgK5kHZHjEiq4vPgOi+eg==",
"expirationDate": "xEXskjuLolxR0nECcWFlCaUPeHTi0BgMzURt2BEUVTPdG0U="
},
"meta": {
"requestId": "req_0b35110e09acca7a6dbd8ac8",
"timestamp": "2026-10-21T05:39:54+03:00"
}
}Overview
This endpoint returns the card number, CVV and expiry encrypted, so the card data cannot be read by anything sitting between us and you — your API gateway, a reverse proxy, a logging service, an error tracker, or a response cache. The three fields are encrypted with AES-256-GCM under a key derived from the access token you used to make the request. You already have that token, so there is nothing to configure and no key for Fyatu to store on your behalf. Decrypting takes about five lines in any language.If you only read the balance, you do not need this endpoint.
GET /cards/{cardId} continues
to return the balance, pending authorizations, masked number, expiry and status exactly as it does
now. Only cardNumber and cvv are moving here.Replacing the old fields
GET /cards/{cardId} will stop returning cardNumber and cvv. If your integration displays a
card to an end user, switch that one call to this endpoint. Everything else stays where it is.
How to decrypt
Each encrypted field is base64 and unpacks as:base64( nonce[12 bytes] || ciphertext || tag[16 bytes] )
SHA-256("fyatu/card-secrets/v1:" + accessToken) — the literal prefix, then the token
you authenticated with, hashed to 32 bytes.
function fyatuOpen(string $sealed, string $accessToken): string {
$key = hash('sha256', 'fyatu/card-secrets/v1:' . $accessToken, true);
$raw = base64_decode($sealed, true);
$nonce = substr($raw, 0, 12);
$tag = substr($raw, -16);
$ct = substr($raw, 12, -16);
$out = openssl_decrypt($ct, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $nonce, $tag);
if ($out === false) {
throw new RuntimeException('Card details could not be decrypted');
}
return $out;
}
$card = $result['data'];
echo 'Card Number: ' . fyatuOpen($card['cardNumber'], $accessToken) . "\n";
echo 'CVV: ' . fyatuOpen($card['cvv'], $accessToken) . "\n";
echo 'Expiry: ' . fyatuOpen($card['expirationDate'], $accessToken) . "\n";
const crypto = require('crypto');
function fyatuOpen(sealed, accessToken) {
const key = crypto.createHash('sha256')
.update('fyatu/card-secrets/v1:' + accessToken).digest();
const raw = Buffer.from(sealed, 'base64');
const decipher = crypto.createDecipheriv('aes-256-gcm', key, raw.subarray(0, 12));
decipher.setAuthTag(raw.subarray(raw.length - 16));
return Buffer.concat([
decipher.update(raw.subarray(12, raw.length - 16)),
decipher.final(),
]).toString('utf8');
}
const card = result.data;
console.log('Card Number:', fyatuOpen(card.cardNumber, accessToken));
console.log('CVV:', fyatuOpen(card.cvv, accessToken));
console.log('Expiry:', fyatuOpen(card.expirationDate, accessToken));
import base64, hashlib
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
def fyatu_open(sealed: str, access_token: str) -> str:
key = hashlib.sha256(b"fyatu/card-secrets/v1:" + access_token.encode()).digest()
raw = base64.b64decode(sealed)
return AESGCM(key).decrypt(raw[:12], raw[12:], None).decode()
card = result["data"]
print("Card Number:", fyatu_open(card["cardNumber"], access_token))
print("CVV:", fyatu_open(card["cvv"], access_token))
print("Expiry:", fyatu_open(card["expirationDate"], access_token))
The response
Three encrypted fields and nothing else. The card is the one you named in the path, and the encryption is exactly what this page describes — fixed, published, and the same on every call, so there is nothing in the body to branch on.{
"success": true,
"status": 200,
"message": "Card details retrieved securely",
"data": {
"cardNumber": "ddrOoIvbcDUatUz+BZix3Xx9D7yeMMk8JQh4fzJU37HTuOpjBv87AdnGNiI=",
"cvv": "4utlLEFDG74XdMbQYlGFkQgK5kHZHjEiq4vPgOi+eg==",
"expirationDate": "xEXskjuLolxR0nECcWFlCaUPeHTi0BgMzURt2BEUVTPdG0U="
},
"meta": {
"requestId": "req_0b35110e09acca7a6dbd8ac8",
"timestamp": "2026-10-21T05:39:54+03:00"
}
}
5371001234567890, 123 and 10/2029. The response is sent with
Cache-Control: no-store, so nothing on the path between us keeps a copy.
Things worth knowing
Decrypt as you read. The key is your access token, so once that token expires a stored
response can no longer be decrypted. Do not persist the encrypted values expecting to open them
later.
- A field the issuer did not return arrives as an empty string, not as an encrypted empty value — so you can tell “no CVV available” from “here is the CVV” without decrypting anything.
- Decryption fails rather than returning wrong data if a value was altered in transit. That is AES-GCM’s authentication tag, and it is why a tampered card number can never silently become a different valid-looking one.
- Every field in every response uses a fresh random nonce, so the same card number never produces the same encrypted value twice.
- A card that is still being provisioned has no details yet and returns
409. If the issuer is briefly unreachable the response is503— retry shortly.
Authorizations
JWT access token obtained from /auth/token
Path Parameters
Card ID

