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

# Direct API

> Retrieve encrypted card PAN, CVV, and expiry via API with RSA encryption

## Overview

Partners with **PCI DSS compliance** can retrieve sensitive card data (PAN, CVV, expiry) via a direct API call. The response is **RSA-encrypted** with your public key - Contro never exposes card data in plaintext.

<Info>
  If your organization is not PCI DSS compliant, use the [embedded widget](/partner/embedded-widget-reveal) method instead - no certification needed.
</Info>

## Prerequisites

1. Your organization must be PCI DSS compliant
2. An RSA-4096 (or larger) key pair
3. Your public key uploaded via the partner dashboard [https://partner.contro.me/settings](https://partner.contro.me/settings) <Icon icon="arrow-up-right-from-square" size={12} />

## Step 1: Generate an RSA key pair

```bash theme={null}
# Generate 4096-bit RSA private key
openssl genrsa -out private_key.pem 4096

# Extract the public key
openssl rsa -in private_key.pem -pubout -out public_key.pem
```

<Warning>
  Keep your private key secure. Contro only stores your **public** key and never has access to your private key.
</Warning>

## Step 2: Upload your public key

Upload your PEM-encoded public key from the partner dashboard:

1. Open [https://partner.contro.me/settings](https://partner.contro.me/settings) <Icon icon="arrow-up-right-from-square" size={12} />
2. Find the **RSA Public Key** section
3. Paste the contents of `public_key.pem` into **PEM-encoded RSA Public Key** and save. Example: `-----BEGIN PUBLIC KEY-----\nMIICIjAN...\n-----END PUBLIC KEY-----`

## Step 3: Call the reveal endpoint

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.contro.me/v1/partner/cards/{card_id}/reveal \
    -H "x-contro-api-key: $CONTRO_API_KEY"
  ```

  ```typescript SDK (Node) theme={null}
  const encrypted = await contro.cards.reveal("card_def456");
  ```

  ```python SDK (Python) theme={null}
  encrypted = contro.cards.reveal("card_def456")
  ```
</CodeGroup>

**Response:**

```json theme={null}
{
  "data": "base64-encoded-RSA-encrypted-payload..."
}
```

## Step 4: Decrypt the response

The `data` field is a Base64-encoded RSA-OAEP-SHA256 ciphertext. Decrypt with your private key:

```typescript theme={null}
import { privateDecrypt, constants } from "node:crypto";
import { readFileSync } from "node:fs";

const privateKey = readFileSync("private_key.pem", "utf-8");
const encrypted = Buffer.from(response.data, "base64");

const decrypted = privateDecrypt(
  {
    key: privateKey,
    padding: constants.RSA_PKCS1_OAEP_PADDING,
    oaepHash: "sha256",
  },
  encrypted,
);

const cardData = JSON.parse(decrypted.toString("utf-8"));
// { data: { cardNumber: "4111...", cvv: "123", expiryDate: "12/2030" } }
```

```bash theme={null}
# Using OpenSSL CLI
echo "BASE64_DATA" | base64 -d | openssl pkeyutl -decrypt \
  -inkey private_key.pem -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256
```

## Sandbox testing

In sandbox mode (`sk_test_` keys), the endpoint returns encrypted **test data**:

| Field | Value |
| - | - |
| cardNumber | `4000000000000000` |
| cvv | `123` |
| expiryDate | `12/2030` |

If you haven't uploaded an RSA key, sandbox mode uses a built-in sandbox key pair automatically. You can use the corresponding sandbox private key to verify your decryption logic.

<Info>
  In live mode, you **must** have an RSA public key configured. The endpoint returns a `400` error if no key is set.
</Info>

## Security considerations

* Card data is encrypted end-to-end - Contro's API response is encrypted with your key before leaving the server
* **Key rotation**: Replacing your public key takes effect immediately. Coordinate rotation to avoid decryption failures during the swap window
