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

# Viewing card details (PAN / CVV)

> Two ways to show full card numbers — Widget.js (default) or encrypted server-side reveal for PCI DSS Level 1.

`GET /v1/cards` and `GET /v1/cards/{id}` never return full PAN or CVV. They only include safe fields such as `lastFour`, status, and expiry label.

To show the full number, expiry, and CVV you use one of the two reveal methods below. Pick based on whether sensitive data may touch your servers.

## Which method should I use?

|                        | **Method 1 — Widget (default)**       | **Method 2 — Server-side (PCI)**                              |
| ---------------------- | ------------------------------------- | ------------------------------------------------------------- |
| **Who**                | Most tenants                          | Orgs with PCI DSS Level 1 (Apocor enables `pciRevealEnabled`) |
| **Where PAN/CVV live** | PCI iframes in the browser            | Your PCI-scoped backend after decrypt                         |
| **API**                | `GET /v1/cards/{id}/secure-details`   | `POST /v1/cards/{id}/payment-details`                         |
| **SDK / crypto**       | Apocor Widget.js + `widget.bootstrap` | RSA-OAEP + AES-256-GCM envelope                               |
| **PCI burden**         | Low (display only)                    | High (you handle card data)                                   |

<Tip>
  If you only need to **display** card details in a web or mobile WebView UI, use **Method 1**. Use **Method 2** only when your backend must process PAN/CVV and you have PCI DSS Level 1 attestation.
</Tip>

API Reference endpoints for both methods live under **Cards — reveal PAN/CVV**.

***

## Method 1 — Widget.js (recommended)

Your servers receive a short-lived `revealToken` only. The browser loads Apocor Widget.js; PAN/CVV render inside PCI iframes and never pass through your backend.

### Steps

1. Authenticate → Bearer token ([Authentication](/authentication)).
2. Call `GET /v1/cards/{id}/secure-details`.
3. Load the Apocor Widget SDK (sandbox vs live must match the token environment).
4. Call `widget.bootstrap({ clientAccessToken: revealToken, ... })`.
5. Ask Apocor to **allowlist your page origin** (the domain of your app, not `api.apocor.ai`).

### Get a reveal token

```bash cURL theme={null}
curl -s 'https://api.apocor.ai/v1/cards/card_abc123/secure-details' \
  -H 'Authorization: Bearer YOUR_TOKEN'
```

```json theme={null}
{ "data": { "revealToken": "rvl_9f8e...", "expiresIn": 300 } }
```

### Widget SDK URLs

| Environment    | Script URL                                                  |
| -------------- | ----------------------------------------------------------- |
| Sandbox / TEST | `https://api.apocor.ai/sdk/card/sandbox/1.0.0/index.min.js` |
| Live           | `https://api.apocor.ai/sdk/card/1.0.0/index.min.js`         |

Optional CDN (same paths): `https://static.apocor.ai/sdk/card/...`

Reference (API Reference → **Cards — reveal PAN/CVV**):
[`GET …/secure-details`](/api-reference/get-cards-by-id-secure-details) ·
[`Widget.js` sandbox](/api-reference/get-sdk-card-sandbox-100-indexminjs) ·
[`Widget.js` live](/api-reference/get-sdk-card-100-indexminjs).

### Frontend example

```html theme={null}
<div id="card-pan"></div>
<div id="card-exp"></div>
<div id="card-cvv"></div>

<script src="https://api.apocor.ai/sdk/card/sandbox/1.0.0/index.min.js"></script>
<script>
  widget.bootstrap({
    clientAccessToken: "REVEAL_TOKEN_FROM_SECURE_DETAILS",
    component: {
      showPan: {
        cardPan: { domId: "card-pan", format: true },
        cardExp: { domId: "card-exp", format: true },
        cardCvv: { domId: "card-cvv" },
      },
    },
    callbackEvents: {
      onSuccess: () => console.log("card fields ready"),
      onFailure: (err) => console.error(err),
    },
  });
</script>
```

<Warning>
  A `revealToken` expires in about **5 minutes** and is single-purpose. Never log it. Match **Sandbox** SDK with TEST tokens and **Live** SDK with LIVE tokens.
</Warning>

***

## Method 2 — Server-side encrypted reveal (PCI DSS Level 1)

Apocor never returns plaintext PAN/CVV over HTTP. You send an RSA public key; we return a hybrid encrypted envelope. Decrypt only inside a PCI-scoped environment (HSM/KMS for the private key).

**Requires:** `pciRevealEnabled = true` on your organization (ask Apocor after PCI attestation).

**Algorithm:** `RSA_OAEP_SHA256_AES_256_GCM`

### Steps

1. Generate an RSA-2048+ key pair; keep the private key in HSM/KMS.
2. `POST /v1/cards/{id}/payment-details` with `encryption.public_key_pem`.
3. Unwrap `encryptedKey` with RSA-OAEP (SHA-256).
4. Decrypt `ciphertext` with AES-256-GCM using `iv` + `authTag`.

Reference: [`POST …/payment-details`](/api-reference/post-cards-by-id-payment-details).

### Request

```bash cURL theme={null}
curl -s -X POST 'https://api.apocor.ai/v1/cards/card_abc123/payment-details' \
  -H 'Authorization: Bearer YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "encryption": {
      "public_key_pem": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----"
    }
  }'
```

### Response envelope

```json theme={null}
{
  "data": {
    "algorithm": "RSA_OAEP_SHA256_AES_256_GCM",
    "encryptedKey": "<base64>",
    "iv": "<base64>",
    "authTag": "<base64>",
    "ciphertext": "<base64>",
    "expiresIn": 120
  }
}
```

### Decrypted payload

```json theme={null}
{
  "pan": "411111…",
  "cvv": "123",
  "expMonth": "12",
  "expYear": "29",
  "bin": "49387519",
  "lastFour": "0063",
  "cardId": "card_abc123"
}
```

### Node.js decrypt example

```js theme={null}
import crypto from "node:crypto";

function decryptPaymentDetails(privateKeyPem, envelope) {
  const aesKey = crypto.privateDecrypt(
    {
      key: privateKeyPem,
      padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
      oaepHash: "sha256",
    },
    Buffer.from(envelope.encryptedKey, "base64"),
  );
  const decipher = crypto.createDecipheriv(
    "aes-256-gcm",
    aesKey,
    Buffer.from(envelope.iv, "base64"),
  );
  decipher.setAuthTag(Buffer.from(envelope.authTag, "base64"));
  const json = Buffer.concat([
    decipher.update(Buffer.from(envelope.ciphertext, "base64")),
    decipher.final(),
  ]).toString("utf8");
  return JSON.parse(json);
}
```

<Warning>
  Call payment-details only from a PCI-scoped service. Never log PAN/CVV or the private key. Never send the private key to Apocor. If you are not PCI Level 1, use Method 1 instead (`403` if `pciRevealEnabled` is false).
</Warning>

***

## Related API Reference

| Endpoint                                                                                         | Method              |
| ------------------------------------------------------------------------------------------------ | ------------------- |
| [`GET /v1/cards/{id}/secure-details`](/api-reference/get-cards-by-id-secure-details)             | Method 1 token      |
| [`GET /sdk/card/sandbox/1.0.0/index.min.js`](/api-reference/get-sdk-card-sandbox-100-indexminjs) | Method 1 SDK (TEST) |
| [`GET /sdk/card/1.0.0/index.min.js`](/api-reference/get-sdk-card-100-indexminjs)                 | Method 1 SDK (live) |
| [`POST /v1/cards/{id}/payment-details`](/api-reference/post-cards-by-id-payment-details)         | Method 2 envelope   |
