> ## 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 access token. GET /cards/{cardId}/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 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.

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

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

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

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

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

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

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.

## Things worth knowing

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


## OpenAPI

````yaml v3/openapi.json GET /cards/{cardId}/secure-details
openapi: 3.1.0
info:
  title: FYATU API v3
  description: >-
    FYATU API v3 with JWT authentication for Collections, Payouts, and Card
    Issuing.
  version: 3.0.0
  contact:
    name: FYATU Support
    url: https://fyatu.com
    email: support@fyatu.com
servers:
  - url: https://api.fyatu.com/api/v3
    description: Production
security: []
tags:
  - name: Authentication
    description: JWT token management endpoints
  - name: Account
    description: Business account, wallet, and address management
  - name: Collections
    description: Accept payments from customers via checkout sessions
  - name: Refunds
    description: Issue refunds for completed collections
  - name: Payouts
    description: Send money to Fyatu account holders
  - name: Cardholders
    description: Cardholder management for card issuing programs
  - name: Cards
    description: Issue, fund, freeze, and manage virtual cards
  - name: Webhooks
    description: Webhook configuration and management
paths:
  /cards/{cardId}/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
        access token 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 access token, a response stays decryptable only
        while that token is valid. Decrypt it as you read it rather than storing
        the encrypted values for later.


        This replaces the `cardNumber` and `cvv` fields on `GET
        /cards/{cardId}`, 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: cardId
          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: ddrOoIvbcDUatUz+BZix3Xx9D7yeMMk8JQh4fzJU37HTuOpjBv87AdnGNiI=
                  cvv: 4utlLEFDG74XdMbQYlGFkQgK5kHZHjEiq4vPgOi+eg==
                  expirationDate: xEXskjuLolxR0nECcWFlCaUPeHTi0BgMzURt2BEUVTPdG0U=
                meta:
                  requestId: req_0b35110e09acca7a6dbd8ac8
                  timestamp: '2026-10-21T05:39:54+03:00'
        '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
      bearerFormat: JWT
      description: JWT access token obtained from /auth/token

````

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