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

# Get Encrypted Card Details

> Fetch the card number, CVV and expiry encrypted to your own API key. GET /cards/{id}/secure-details.

## 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.

> [!NOTE]
>
> **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] )
```

The key is `SHA-256("fyatu/card-secrets/v1:" + apiKey)` — the literal prefix, then the key
you authenticated with, hashed to 32 bytes.

```php PHP theme={null}
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";
```

```javascript Node.js theme={null}
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));
```

```python Python theme={null}
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.

```json theme={null}
{
  "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"
  }
}
```

Those three values decrypt to `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

> [!WARNING]
>
> **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 is `503` — retry shortly.


## OpenAPI

````yaml v3.20/openapi.json GET /cards/{id}/secure-details
openapi: 3.1.0
info:
  title: FYATU CaaS API v3.20
  description: >-
    FYATU Cards-as-a-Service API â€” API key authentication, Cardholder
    lifecycle, Card issuance, Transactions, Webhooks, and Programs.
  version: 3.20.0
  contact:
    name: FYATU Support
    url: https://fyatu.com
    email: support@fyatu.com
servers:
  - url: https://api.fyatu.com/api/v3.20
    description: >-
      FYATU CaaS API â€” the environment (LIVE or SANDBOX) is determined by the
      API key, not the URL
security:
  - BearerAuth: []
tags:
  - name: Meta
    description: Liveness, account info, and supported event types
  - name: Account
    description: Account-level balance and funding status
  - name: Programs
    description: Read card program configuration
  - name: Cardholders
    description: Create and manage cardholder profiles
  - name: Cards
    description: Issue, fund, freeze, and terminate virtual cards
  - name: Transactions
    description: Read-only card transaction history
  - name: Webhooks
    description: Manage webhook endpoints for real-time event delivery
  - name: Products
    description: Read card product configurations
paths:
  /cards/{id}/secure-details:
    get:
      tags:
        - Cards
      summary: Get Encrypted Card Details
      description: >-
        Returns the card number, CVV and expiry **encrypted**, so the card data
        is unreadable to anything sitting between us and you: proxies, API
        gateways, log drains and error trackers included.


        Each field is encrypted with AES-256-GCM under a key derived from the
        API key you used to make this request. You already hold that credential,
        so there is nothing to configure first and no key for us to store
        anywhere. Decrypting is about five lines in any language.


        Because the key is your API key, rotating that key makes any previously
        returned response undecryptable. Decrypt as you read.


        This replaces the `cardNumber` and `cvv` fields on `GET /cards/{id}`,
        which will stop being returned. Nothing else about that response
        changes, so if you only read the balance from it you do not need this
        endpoint and do not need to change anything.
      operationId: getCardSecureDetails
      parameters:
        - name: id
          in: path
          required: true
          description: Card ID
          schema:
            type: string
      responses:
        '200':
          description: Encrypted card details retrieved
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CardSecureDetailsResponse'
              example:
                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'
        '401':
          description: Missing or invalid credentials
        '404':
          description: Card not found
        '409':
          description: The card is still being provisioned and has no details yet
        '503':
          description: >-
            Card details are temporarily unavailable from the issuer. Retry
            shortly.
      security:
        - BearerAuth: []
components:
  schemas:
    CardSecureDetailsResponse:
      type: object
      properties:
        success:
          type: boolean
          example: true
        status:
          type: integer
          example: 200
        message:
          type: string
          example: Card details retrieved securely
        data:
          type: object
          properties:
            cardNumber:
              type: string
              description: >-
                Encrypted card number. Decrypts to the full PAN, digits only.
                Empty when the issuer did not return one.
            cvv:
              type: string
              description: Encrypted CVV. Empty when the issuer did not return one.
            expirationDate:
              type: string
              description: Encrypted expiry. Decrypts to MM/YYYY.
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: >-
        API key from the FYATU CaaS portal. Pass as `Authorization: Bearer
        <key>`.

````

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