Skip to content

Get card information

GET
/card
curl --request GET \
--url https://web.exactly.app/api/card \
--cookie credential_id=<credential_id>

Retrieve the card profile, encrypted card data, and (optionally) a signature challenge for an authenticated user.

The sessionid header and the scope query parameter are independent and may be used together or separately:

  • Provide sessionid to receive encryptedPan, encryptedCvc, and pin. Without it, only the card profile is returned.
  • Provide scope=siwe or scope=webauthn to receive a challenge to be signed and submitted via PATCH /. siwe and webauthn are mutually exclusive within a single request.

Successful responses include push-provisioning credentials in the provisioning field only when the scope=provisioning query parameter is sent.

Retrieving encrypted card details

  1. Generate a session ID: Encrypt a 32‑character hexadecimal secret (no spaces/dashes) with the provided public RSA key using RSA‑OAEP.
  2. Send the request: Include the encrypted secret in the header sessionid when calling this endpoint.
  3. Decrypt the response: Use the original secret to decrypt encryptedPan, encryptedCvc, and pin (each returned as { data, iv }).

Requesting a signature challenge

Pass scope=siwe to receive a fully formed Sign-In with Ethereum message in challenge, or scope=webauthn to receive the plain authorization statement to be signed by a passkey. The signed result is submitted to PATCH / to bind the card to the user.

Step 1: Generate a sessionid and secret

import crypto from "node:crypto";

function session(): { sessionid: string; secret: string } {
  const secret = crypto.randomUUID().replaceAll("-", "");
  const secretKeyBase64 = Buffer.from(secret, "hex").toString("base64");
  const secretKeyBase64Buffer = Buffer.from(secretKeyBase64, "utf8");
  const secretKeyBase64BufferEncrypted = crypto.publicEncrypt(
    { key: pem, padding: crypto.constants.RSA_PKCS1_OAEP_PADDING },
    secretKeyBase64Buffer,
  );
  return {
    sessionid: secretKeyBase64BufferEncrypted.toString("base64"),
    secret,
  };
}

The sessionid is required to make an API request. The secret will be needed for decryption later.

Step 2: Send the request

Use the sessionid in the header when calling this endpoint.

Step 3: Decrypt the response

Use the secret from Step 1 to decrypt the data.

import crypto from "node:crypto";

function decrypt(base64Secret: string, base64Iv: string, secretKey: string): string {
  const secret = Buffer.from(base64Secret, "base64");
  const iv = Buffer.from(base64Iv, "base64");
  const decipher = crypto.createDecipheriv("aes-128-gcm", Buffer.from(secretKey, "hex"), iv);
  decipher.setAutoPadding(false);
  decipher.setAuthTag(secret.subarray(-16));
  return Buffer.concat([decipher.update(secret.subarray(0, -16)), decipher.final()]).toString("utf8");
}
sessionid
string
scope
Any of:
string
Allowed values: provisioning siwe webauthn

Card information

Media typeapplication/json
object
cardId
required
string format: uuid
displayName
required
string
encryptedPan
object
data
required
string
iv
required
string
encryptedCvc
object
data
required
string
iv
required
string
expirationMonth
required
string
expirationYear
required
string
lastFour
required
string
mode
required
number
pin
Any of:
object
data
required
string
iv
required
string
provider
required
Allowed values: panda
status
required
string
Allowed values: ACTIVE FROZEN
limit
required
object
amount
required
number
frequency
required
string
Allowed values: per24HourPeriod per7DayPeriod per30DayPeriod perYearPeriod allTime perAuthorization
productId
required
string
Allowed values: 408 406 424
challenge
string
provisioning
object
id
required
string
secret
required
string
Example
{
"cardId": "123e4567-e89b-12d3-a456-426655440000",
"displayName": "John Doe",
"expirationMonth": "12",
"expirationYear": "2025",
"lastFour": "1234",
"mode": 0,
"provider": "panda",
"status": "ACTIVE",
"limit": {
"frequency": "per24HourPeriod"
},
"productId": "408",
"challenge": "1a2b3c",
"provisioning": {
"id": "card_abc123",
"secret": "otp_xyz"
}
}

Bad request

Media typeapplication/json
Any of:
object
code
required
Allowed values: bad request
Example
{
"code": "bad request"
}

Forbidden

Media typeapplication/json
object
code
required
Allowed values: no panda
Example
{
"code": "no panda"
}

Not found

Media typeapplication/json
object
code
required
Allowed values: no card
Example
{
"code": "no card"
}