Get Encrypted Card Details
curl --request GET \
--url https://api.fyatu.com/api/v3.20/cards/{id}/secure-details \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.fyatu.com/api/v3.20/cards/{id}/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.20/cards/{id}/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.20/cards/{id}/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.20/cards/{id}/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.20/cards/{id}/secure-details")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.fyatu.com/api/v3.20/cards/{id}/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": "VW4X0V9Zr0l8CgZFdnU2lj7uGvKRSBJAKsLdvkmJmhx2aEeYWkt5/8UlC6c=",
"cvv": "CtC78v00261fzBy7BLGb+hK/y8Vk1EzPLfbvii4CKw==",
"expirationDate": "MddEgF739E6whHNljrtSlyUw4cXjStAo4RoxSvUpxEIu6vY="
},
"meta": {
"requestId": "req_fe99bfa3728ad8e37bb7c3a9",
"platform": "Fyatu CaaS",
"timestamp": "2026-10-21T02:40:14Z"
}
}Cards
Get Encrypted Card Details
Fetch the card number, CVV and expiry encrypted to your own API key. GET /cards//secure-details.
GET
/
cards
/
{id}
/
secure-details
Get Encrypted Card Details
curl --request GET \
--url https://api.fyatu.com/api/v3.20/cards/{id}/secure-details \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.fyatu.com/api/v3.20/cards/{id}/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.20/cards/{id}/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.20/cards/{id}/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.20/cards/{id}/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.20/cards/{id}/secure-details")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.fyatu.com/api/v3.20/cards/{id}/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": "VW4X0V9Zr0l8CgZFdnU2lj7uGvKRSBJAKsLdvkmJmhx2aEeYWkt5/8UlC6c=",
"cvv": "CtC78v00261fzBy7BLGb+hK/y8Vk1EzPLfbvii4CKw==",
"expirationDate": "MddEgF739E6whHNljrtSlyUw4cXjStAo4RoxSvUpxEIu6vY="
},
"meta": {
"requestId": "req_fe99bfa3728ad8e37bb7c3a9",
"platform": "Fyatu CaaS",
"timestamp": "2026-10-21T02:40:14Z"
}
}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 API key you used to make the request. You already have that key, 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/{id} 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/{id} 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:" + apiKey) — the literal prefix, then the key
you authenticated with, hashed to 32 bytes.
function fyatuOpen(string $sealed, string $apiKey): string {
$key = hash('sha256', 'fyatu/card-secrets/v1:' . $apiKey, 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'], $apiKey) . "\n";
echo 'CVV: ' . fyatuOpen($card['cvv'], $apiKey) . "\n";
echo 'Expiry: ' . fyatuOpen($card['expirationDate'], $apiKey) . "\n";
const crypto = require('crypto');
function fyatuOpen(sealed, apiKey) {
const key = crypto.createHash('sha256')
.update('fyatu/card-secrets/v1:' + apiKey).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, apiKey));
console.log('CVV:', fyatuOpen(card.cvv, apiKey));
console.log('Expiry:', fyatuOpen(card.expirationDate, apiKey));
import base64, hashlib
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
def fyatu_open(sealed: str, api_key: str) -> str:
key = hashlib.sha256(b"fyatu/card-secrets/v1:" + api_key.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"], api_key))
print("CVV:", fyatu_open(card["cvv"], api_key))
print("Expiry:", fyatu_open(card["expirationDate"], api_key))
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": "VW4X0V9Zr0l8CgZFdnU2lj7uGvKRSBJAKsLdvkmJmhx2aEeYWkt5/8UlC6c=",
"cvv": "CtC78v00261fzBy7BLGb+hK/y8Vk1EzPLfbvii4CKw==",
"expirationDate": "MddEgF739E6whHNljrtSlyUw4cXjStAo4RoxSvUpxEIu6vY="
},
"meta": {
"requestId": "req_fe99bfa3728ad8e37bb7c3a9",
"platform": "Fyatu CaaS",
"timestamp": "2026-10-21T02:40:14Z"
}
}
5371001234567890, 123 and 10/2029. The response is sent with
Cache-Control: no-store, so nothing on the path between us keeps a copy.
If you hold several API keys, or are mid-rotation, the key to use is simply the one the request
was made with — a response is only ever encrypted to the credential that fetched it.
Things worth knowing
Decrypt as you read. The key is your API key, so rotating or revoking that key makes any
response encrypted to it permanently unreadable. 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
API key from the FYATU CaaS portal. Pass as Authorization: Bearer <key>.
Path Parameters
Card ID

