Read Card Details
Retrieve sensitive card data (PAN, CVV, PIN) with user confirmation.
Before You Start
Read the following guides before proceeding:
| Guide | Why |
|---|---|
| Getting Started | Platform overview and setup |
| Api Basics | Required headers and request configuration |
| Authentication | How to obtain access tokens |
| Onboarding | User and wallet registration |
| KYC | KYC verification requirements |
Overview
Sensitive card data (PAN, CVV, PIN) requires user confirmation before retrieval. Two confirmation methods are supported:
- Signature-Based — User signs a message with their wallet private key (recommended)
- SMS OTP — User receives and enters a code sent to their registered phone
Important: This endpoint returns sensitive cardholder data and must be called directly from the client application via the SDK. Proxying cardholder data through your backend is only permitted if your backend environment is PCI DSS compliant.
Confirmation Methods
Signature-Based Confirmation (Recommended)
User signs a message with their wallet private key.
POST /api/v1/confirmation/signature/verify
Message format: By signing this I confirm that I am executing action {ActionType} at {Nonce}
| Parameter | Description |
|---|---|
action_type | GetCardDetails, 3dsChallenge, VerifyPhone |
message_signature | Signature of the message |
nonce | Unix timestamp when message was signed (valid for 5 minutes) |
const nonce = Math.floor(Date.now() / 1000);
const message = `By signing this I confirm that I am executing action GetCardDetails at ${nonce}`;
const signature = await wallet.signMessage(message);
const response = await fetch(`${baseUrl}/api/v1/confirmation/signature/verify`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'X-User-Wallet': userEoaAddress,
'X-Chain-Id': chainId,
'Content-Type': 'application/json'
},
body: JSON.stringify({
action_type: 'GetCardDetails',
message_signature: signature,
nonce: nonce
})
});
const { action_token } = await response.json();import time
nonce = int(time.time())
message = f"By signing this I confirm that I am executing action GetCardDetails at {nonce}"
signature = wallet.sign_message(message)
response = requests.post(
f"{base_url}/api/v1/confirmation/signature/verify",
headers={
"Authorization": f"Bearer {access_token}",
"X-User-Wallet": user_eoa_address,
"X-Chain-Id": chain_id,
"Content-Type": "application/json"
},
json={
"action_type": "GetCardDetails",
"message_signature": signature,
"nonce": nonce
}
)
action_token = response.json()["action_token"]nonce := time.Now().Unix()
message := fmt.Sprintf("By signing this I confirm that I am executing action GetCardDetails at %d", nonce)
signature, _ := wallet.SignMessage(message)
body, _ := json.Marshal(map[string]interface{}{
"action_type": "GetCardDetails",
"message_signature": signature,
"nonce": nonce,
})
req, _ := http.NewRequest("POST", baseURL+"/api/v1/confirmation/signature/verify", bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("X-User-Wallet", userEoaAddress)
req.Header.Set("X-Chain-Id", chainId)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
var result struct{ ActionToken string `json:"action_token"` }
json.NewDecoder(resp.Body).Decode(&result)SMS OTP Confirmation
User receives an SMS code to their registered phone number.
Step 1: Request SMS code
POST /api/v1/confirmation/sms
const response = await fetch(`${baseUrl}/api/v1/confirmation/sms`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'X-User-Wallet': userEoaAddress,
'X-Chain-Id': chainId,
'Content-Type': 'application/json'
},
body: JSON.stringify({
action_type: 'GetCardDetails'
})
});
const { session_id, code_length, expires_at, resend_at } = await response.json();response = requests.post(
f"{base_url}/api/v1/confirmation/sms",
headers={
"Authorization": f"Bearer {access_token}",
"X-User-Wallet": user_eoa_address,
"X-Chain-Id": chain_id,
"Content-Type": "application/json"
},
json={"action_type": "GetCardDetails"}
)
session = response.json()
session_id = session["session_id"]body, _ := json.Marshal(map[string]string{"action_type": "GetCardDetails"})
req, _ := http.NewRequest("POST", baseURL+"/api/v1/confirmation/sms", bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("X-User-Wallet", userEoaAddress)
req.Header.Set("X-Chain-Id", chainId)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
var session struct {
SessionID string `json:"session_id"`
CodeLength int `json:"code_length"`
}
json.NewDecoder(resp.Body).Decode(&session)Response:
{
"session_id": "550e8400-e29b-41d4-a716-446655440000",
"attempts_left": 3,
"expires_at": "2024-01-15T10:05:00Z",
"code_length": 6,
"resend_at": "2024-01-15T10:01:00Z"
}Step 2: Verify SMS code
POST /api/v1/confirmation/sms/verify
const response = await fetch(`${baseUrl}/api/v1/confirmation/sms/verify`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'X-User-Wallet': userEoaAddress,
'X-Chain-Id': chainId,
'Content-Type': 'application/json'
},
body: JSON.stringify({
session_id: sessionId,
code: userEnteredCode
})
});
const { token: action_token } = await response.json();response = requests.post(
f"{base_url}/api/v1/confirmation/sms/verify",
headers={
"Authorization": f"Bearer {access_token}",
"X-User-Wallet": user_eoa_address,
"X-Chain-Id": chain_id,
"Content-Type": "application/json"
},
json={
"session_id": session_id,
"code": user_entered_code
}
)
action_token = response.json()["token"]body, _ := json.Marshal(map[string]string{
"session_id": sessionID,
"code": userEnteredCode,
})
req, _ := http.NewRequest("POST", baseURL+"/api/v1/confirmation/sms/verify", bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("X-User-Wallet", userEoaAddress)
req.Header.Set("X-Chain-Id", chainId)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
var result struct{ Token string `json:"token"` }
json.NewDecoder(resp.Body).Decode(&result)
actionToken := result.TokenGet Card Number and Expiry
POST /api/v1/cards/{cardId}/details
const response = await fetch(`${baseUrl}/api/v1/cards/${cardId}/details`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'X-User-Wallet': userEoaAddress,
'X-Chain-Id': chainId,
'Content-Type': 'application/json'
},
body: JSON.stringify({
action_token: actionToken
})
});
const { card_number, expiry_date } = await response.json();response = requests.post(
f"{base_url}/api/v1/cards/{card_id}/details",
headers={
"Authorization": f"Bearer {access_token}",
"X-User-Wallet": user_eoa_address,
"X-Chain-Id": chain_id,
"Content-Type": "application/json"
},
json={"action_token": action_token}
)
details = response.json()
card_number = details["card_number"]
expiry_date = details["expiry_date"]body, _ := json.Marshal(map[string]string{"action_token": actionToken})
req, _ := http.NewRequest("POST", baseURL+"/api/v1/cards/"+cardID+"/details", bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("X-User-Wallet", userEoaAddress)
req.Header.Set("X-Chain-Id", chainId)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
var details struct {
CardNumber string `json:"card_number"`
ExpiryDate string `json:"expiry_date"`
}
json.NewDecoder(resp.Body).Decode(&details)Response:
{
"card_number": "4111111111111234",
"expiry_date": "12/27"
}Get CVV
POST /api/v1/cards/{cardId}/cvv
const response = await fetch(`${baseUrl}/api/v1/cards/${cardId}/cvv`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'X-User-Wallet': userEoaAddress,
'X-Chain-Id': chainId,
'Content-Type': 'application/json'
},
body: JSON.stringify({
action_token: actionToken
})
});
const { cvv } = await response.json();response = requests.post(
f"{base_url}/api/v1/cards/{card_id}/cvv",
headers={
"Authorization": f"Bearer {access_token}",
"X-User-Wallet": user_eoa_address,
"X-Chain-Id": chain_id,
"Content-Type": "application/json"
},
json={"action_token": action_token}
)
cvv = response.json()["cvv"]body, _ := json.Marshal(map[string]string{"action_token": actionToken})
req, _ := http.NewRequest("POST", baseURL+"/api/v1/cards/"+cardID+"/cvv", bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("X-User-Wallet", userEoaAddress)
req.Header.Set("X-Chain-Id", chainId)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
var result struct{ CVV string `json:"cvv"` }
json.NewDecoder(resp.Body).Decode(&result)Response:
{
"cvv": "123"
}Get PIN
PIN is only available for physical cards.
POST /api/v1/cards/{cardId}/pin
const response = await fetch(`${baseUrl}/api/v1/cards/${cardId}/pin`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'X-User-Wallet': userEoaAddress,
'X-Chain-Id': chainId,
'Content-Type': 'application/json'
},
body: JSON.stringify({
action_token: actionToken
})
});
const { pin } = await response.json();response = requests.post(
f"{base_url}/api/v1/cards/{card_id}/pin",
headers={
"Authorization": f"Bearer {access_token}",
"X-User-Wallet": user_eoa_address,
"X-Chain-Id": chain_id,
"Content-Type": "application/json"
},
json={"action_token": action_token}
)
pin = response.json()["pin"]body, _ := json.Marshal(map[string]string{"action_token": actionToken})
req, _ := http.NewRequest("POST", baseURL+"/api/v1/cards/"+cardID+"/pin", bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("X-User-Wallet", userEoaAddress)
req.Header.Set("X-Chain-Id", chainId)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
var result struct{ PIN string `json:"pin"` }
json.NewDecoder(resp.Body).Decode(&result)Response:
{
"pin": "1234"
}Action Token Validity
| Property | Value |
|---|---|
| Validity period | 5 minutes |
| Single use | Yes |
| Reusable across endpoints | Yes (same token works for PAN, CVV, and PIN) |
Error Handling
Action Token Errors
Errors returned when the action token is invalid or expired.
| Error Reason | Error Details | Description |
|---|---|---|
ErrorInvalidField | action_token: invalid_token | Token format is invalid or malformed |
ErrorInvalidField | action_token: invalid_claims | Token claims cannot be parsed |
ErrorExpired | action_token: expired | Token has expired (5 minute validity) |
ErrorInvalidField | action_token: invalid_purpose | Token was issued for a different action type |
ErrorMissingField | action_token: missing | Action token not provided in request |
Card Status Errors
Errors returned when the card is not in a valid state for retrieving details.
| Error Reason | Error Details | Description |
|---|---|---|
ErrorInvalidStatus | card_status: not_active | Card must be active to retrieve details |
ErrorNotSupported | card_format: not_physical | PIN is only available for physical cards |
Signature Confirmation Errors
Errors returned when verifying a wallet signature.
| Error Reason | Error Details | Description |
|---|---|---|
ErrorMissingField | action_type: missing | Action type not provided |
ErrorInvalidField | action_type: invalid_value | Invalid action type value |
ErrorMissingField | message_signature: missing | Signature not provided |
ErrorMissingField | nonce: missing | Nonce not provided |
ErrorInvalidField | nonce: expired | Nonce is older than 5 minutes |
ErrorInvalidField | signature: invalid_signature | Signature does not match the user's wallet |
SMS Confirmation Errors
Errors returned during SMS OTP verification.
| Error Reason | Error Details | Description |
|---|---|---|
ErrorMissingField | code: missing | OTP code not provided |
ErrorMissingField | session_id: missing | Session ID not provided |
ErrorInvalidField | session_id: invalid_uuid | Session ID is not a valid UUID |
Error Response Format
{
"error_reason": "ErrorInvalidField",
"error_description": "Invalid action token",
"error_category": {
"category": "CategoryValidationFailure",
"http_status_code": 400
},
"error_details": [
{ "key": "field", "details": "action_token" },
{ "key": "issue", "details": "expired" }
]
}Updated 18 days ago

