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
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 |
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-Address': 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-Address": 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-Address", 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-Address': 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-Address": 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-Address", 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-Address': 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-Address": 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-Address", 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-Address': 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-Address": 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-Address", 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-Address': 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-Address": 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-Address", 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-Address': 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-Address": 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-Address", 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 8 days ago
