Cards Service
API reference for managing payment cards
VERTEX ENGINE
Introduction
The Cards API allows you to manage payment cards for your organization. You can request new cards, view card details, activate cards, set PINs, and manage card status and limits.
Key Concepts
- ●
ID Reuse: The
idfrom "request card" serves as bothrequest_id(during approval) andcard_id(after approval) - ●
Visibility:
list_requeststracks approval status;list_cardsshows issued cards - ●
Security: Only the assigned cardholder can access card details/operations (via JWT token)
- ●
Virtual Cards: Created as
ACTIVE; no activation needed
1Step 1 — Request Card
- ▸
Call:
svc.card.{partner}.request - ▸
Send:
{ account_id, user_id, name, type: 1, use_type: 2, features, limits?, extras? } - ▸
Note:
use_typemust be2(multi-use); single-use cards not available - ▸
Receive:
{ id }— save this; it will be thecard_idonce approved
2Step 2 — Track Approval
- ▸
Call:
svc.card.{partner}.list_requests - ▸
Send:
{ account_id }(optionally adduser_idsto filter) - ▸
Look for:
status: "APPROVED"on your request (sameidfrom Step 1)
3Step 3 — Get Card Details (PAN/CVV)
- ▸
Call:
svc.card.{partner}.get_details - ▸
Send:
{ account_id, id }whereidis from Step 1 - ▸
Receive:
{ card_number, expiry_date: "MMYY", cvv } - ▸
Constraints: Caller must be the assigned cardholder; virtual cards are
ACTIVEby default
Optional — List Cards (UI/Discovery)
- ▸
Call:
svc.card.{partner}.list_cards - ▸
Send:
{ entity_id, account_id, user_ids? } - ▸
Receive:
{ id: entity_id, cards: [ { id, last_4, status, ... } ] } - ▸
Behavior:
last_4masked as"****"whenINACTIVE; visible whenACTIVE
Optional — Cardholder Actions
- ▸
set_pin (
svc.card.{partner}.set_pin):{ account_id, id, pin: "1234" }— 4 digits; 30-minute cooldown - ▸
update_status (
svc.card.{partner}.update_status):{ account_id, id, status: "ACTIVE" | "FROZEN" }— freeze/unfreeze card
Card Features and Limits
Card Features
Controls which transaction types and features are enabled for a card.
{
"domestic": true, // Allow domestic transactions
"international": false, // Allow international transactions (default: false)
"e_commerce": true, // Allow online/e-commerce purchases
"atm": true, // Allow ATM withdrawals
"pos": true, // Allow point-of-sale transactions
"contactless": true // Allow contactless payments
}Default Values & Behavior
When requesting a card, the following behavior applies:
domestic: Always forced totrue(ignores request payload)international: Always forced tofalse(ignores request payload)e_commerce: Honors request payload (default:trueif not specified)atm: Honors request payload (default:trueif not specified)pos: Honors request payload (default:trueif not specified)contactless: Honors request payload (default:trueif not specified)
Usage in Endpoints
request: ⚠️ Partial (forcesdomesticandinternational; honorse_commerce,atm,pos,contactless)edit: ⚠️ Partial (only updatesinternational,e_commerce,atm; ignoresdomestic,pos,contactless)list_requests,list_cards: ✅ Returns complete featuresget_details,update_status,activate_card,set_pin: ❌ Not used
Implementation Notes
⚠️ The international toggle is present in the API but full enablement may be staged.
Card Limits
Transaction spending limits that can be configured for a card. All amounts are in minor units (e.g., cents) of the account currency.
{
"transaction_enabled": true, // Enable per-transaction limit
"transaction": 100000, // Maximum per transaction (e.g., $1,000.00 = 100000 cents)
"daily_enabled": true, // Enable daily spending limit
"daily": 100000, // Maximum daily spend (e.g., $1,000.00)
"monthly_enabled": true, // Enable monthly spending limit
"monthly": 500000, // Maximum monthly spend (e.g., $5,000.00)
"yearly_enabled": false, // Enable yearly spending limit
"yearly": 0 // Maximum yearly spend (disabled)
}Validation Rules
- At least one limit required: At least one limit amount must be greater than 0
- Transaction vs Periodic:
transaction≤ max(daily,monthly,yearly) - Daily vs Transaction:
daily≤transaction - Monthly vs Daily:
monthly≤daily - Yearly vs Monthly:
yearly≤monthly
⚠️ Note: Rules 2 and 3 together mean that when both transaction and daily limits are enabled, they must be equal.
Usage in Endpoints
request: ✅ Required (full validation applied)edit: ⚠️ Partial (only updates limit amounts; enabled flags ignored; no validation checks)list_requests,list_cards: ✅ Returns complete limits with enabled flagsget_details,update_status,activate_card,set_pin: ❌ Not used
⚠️ Important: The validation rules above apply only during card creation (request). The edit endpoint directly overwrites limit amounts without validation checks.
Error Messages
"at least one limit must be provided""single transaction limit cannot be greater than periodic limit""daily limit cannot be greater than single transaction limit""monthly limit cannot be greater than daily limit""yearly limit cannot be greater than monthly limit"
Request Card
Request a new card for a user in your organization.
Endpoint
Request
{
"account_id": "550e8400-e29b-41d4-a716-446655440000", // Required - account ID (UUID)
"user_id": "40ec785e-d915-49de-9053-4630042d8182", // Required - user ID (UUID)
"name": "John Doe", // Required - name to appear on the card
"type": 1, // Required - card type: 1=Virtual, 2=Physical
"use_type": 2, // Required - card use type: 1=Single (rejected), 2=Multi
"features": { // Required - API honours these booleans (except international, currently ignored)
"domestic": true,
"international": false,
"e_commerce": true,
"atm": true,
"pos": true,
"contactless": true
},
"limits": { // Required - include ≥1 non-zero amount; if transaction & daily enabled they must be equal
"transaction_enabled": true,
"transaction": 20000,
"daily_enabled": true,
"daily": 20000,
"monthly_enabled": true,
"monthly": 20000,
"yearly_enabled": false,
"yearly": 0
},
"extras": { // Accepted but currently ignored (auto_lock stored with no enforcement yet)
"auto_lock": "2023-12-31T23:59:59Z"
}
}Response
{
"id": "7aa53a6d-5869-c814-5cee-2af0f5d92aa5" // UUID of the created card request (becomes card_id upon approval)
}Limit Validation
Limit validation is strict: at least one amount must be non-zero, and whenever multiple limits are enabled the hierarchy must hold (transaction ≥ daily ≥ monthly ≥ yearly).
Authorization Required
- The returned
idbecomes thecard_idafter the request is approved. - Virtual cards are auto‑approved. Physical cards may require Owner/Accountant approval.
List Card Requests
List card requests for an account. Members see their own; owners/accountants can see all.
Endpoint
Request
{
"account_id": "550e8400-e29b-41d4-a716-446655440000", // Required - account ID
"user_ids": ["40ec785e-d915-49de-9053-4630042d8182"] // Optional - filter by users
}Response
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"requests": [
{
"id": "7aa53a6d-5869-c814-5cee-2af0f5d92aa5",
"name": "John Doe",
"type": 1,
"use_type": 2,
"created_at": "2023-11-15T10:30:45Z",
"requested_by": "40ec785e-d915-49de-9053-4630042d8182",
"assigned_to": "40ec785e-d915-49de-9053-4630042d8182",
"features": {
"domestic": true,
"international": false,
"e_commerce": true,
"atm": true,
"pos": true,
"contactless": true
},
"limits": {
"transaction_enabled": true,
"transaction": 20000,
"daily_enabled": true,
"daily": 20000,
"monthly_enabled": true,
"monthly": 20000,
"yearly_enabled": false,
"yearly": 0
},
"extras": {},
"status": "PENDING"
}
]
}Visibility enforced by upstream auth/policy; service returns by account and optional user_ids.
Respond to Card Request
Approve or reject a card request. Typically restricted to Owner/Accountant roles; enforce via access control. Note that virtual cards are auto-approved.
Endpoint
Request
{
"entity_id": "7632a09c-9408-4cf0-b8dd-cada3f089240", // Required - entity ID
"account_id": "550e8400-e29b-41d4-a716-446655440000", // Required - account ID
"request_id": "7aa53a6d-5869-c814-5cee-2af0f5d92aa5", // Required - card request ID
"approved": true // Optional - omit or false to record a rejection
}Response
{}Authorization
Ensure appropriate role checks (Owner/Accountant) are enforced in your integration.
List Cards
List cards for an organization/account. Members see their own; owners/accountants can see all.
Endpoint
Request
{
"entity_id": "7632a09c-9408-4cf0-b8dd-cada3f089240", // Required - entity ID
"account_id": "550e8400-e29b-41d4-a716-446655440000", // Required - account ID
"user_ids": ["40ec785e-d915-49de-9053-4630042d8182"] // Optional - filter by users
}Response
{
"id": "7632a09c-9408-4cf0-b8dd-cada3f089240", // Entity ID (echoes request.entity_id)
"cards": [
{
"id": "7aa53a6d-5869-c814-5cee-2af0f5d92aa5",
"name": "John Doe",
"type": 1,
"use_type": 2,
"last_4": "1234",
"date_created": "2023-11-15T10:30:45Z",
"user_id": "40ec785e-d915-49de-9053-4630042d8182",
"org_id": "7632a09c-9408-4cf0-b8dd-cada3f089240",
"features": {
"domestic": true,
"international": false,
"e_commerce": true,
"atm": true,
"pos": true,
"contactless": true
},
"limits": {
"transaction_enabled": true,
"transaction": 20000,
"daily_enabled": true,
"daily": 20000,
"monthly_enabled": true,
"monthly": 20000,
"yearly_enabled": false,
"yearly": 0
},
"extras": {}, // Currently empty in list responses
"status": "ACTIVE"
}
]
}Visibility enforced by upstream auth/policy; service filters by request parameters.
Masking Behavior
When a card is INACTIVE, the last_4 may be masked ("****"). Virtual cards are ACTIVE on creation.
Get Card Details
Retrieve sensitive card details such as the full card number, expiry date, and CVV. This endpoint should be used with caution and only when necessary.
Endpoint
Request
{
"account_id": "550e8400-e29b-41d4-a716-446655440000", // Required - account ID
"id": "7aa53a6d-5869-c814-5cee-2af0f5d92aa5" // Required - card ID
}Response
{
"card_number": "4111111111111234", // Full card number
"expiry_date": "1225", // Card expiry date (MMYY)
"cvv": "123" // Card verification value
}Security Warning
This endpoint returns sensitive card information. Ensure you handle this data securely and in compliance with PCI DSS requirements.
Edit Card
Partial update of card configuration (name, features, limits, extras). Some flags and numeric limits are persisted.
Endpoint
Request
{
"entity_id": "7632a09c-9408-4cf0-b8dd-cada3f089240", // Required - org ID
"card_id": "7aa53a6d-5869-c814-5cee-2af0f5d92aa5", // Required - card ID
"name": "John Doe", // Optional - new name
"features": {
"domestic": true,
"international": true, // Example update
"e_commerce": true,
"atm": true,
"pos": true,
"contactless": true
},
"limits": {
"transaction_enabled": true,
"transaction": 20000,
"daily_enabled": true,
"daily": 20000,
"monthly_enabled": true,
"monthly": 20000,
"yearly_enabled": false,
"yearly": 0
},
"extras": {
"auto_lock": "2024-12-31T23:59:59Z"
}
}Response
{}Update Card Status
Change a card's status to ACTIVE or FROZEN. Freezing a card prevents transactions.
Endpoint
Request
{
"account_id": "550e8400-e29b-41d4-a716-446655440000", // Required - account ID
"id": "7aa53a6d-5869-c814-5cee-2af0f5d92aa5", // Required - card ID
"status": "FROZEN" // New status: ACTIVE or FROZEN
}Response
{}Activate Card
Activate a physical card after it has been received by the user. This is required before the card can be used for transactions.
Endpoint
Request
{
"account_id": "550e8400-e29b-41d4-a716-446655440000", // Required - account ID
"id": "7aa53a6d-5869-c814-5cee-2af0f5d92aa5", // Optional - card ID (can activate by last_4 only)
"last_4": "1234" // Required - last 4 digits for verification
}Response
{}Set PIN
Set or change the PIN for a card. The PIN must be exactly 4 digits. A cooldown applies between PIN set requests.
Endpoint
Request
{
"account_id": "550e8400-e29b-41d4-a716-446655440000", // Required - account ID
"id": "7aa53a6d-5869-c814-5cee-2af0f5d92aa5", // Required - card ID
"pin": "1234" // New 4-digit PIN
}Response
{}Security Warning
PINs are encrypted in transit and storage. Handle this data with care in your application.
Card Transaction Authorization
The Card Transaction Authorization API provides real-time transaction approval capabilities, allowing you to intercept and approve/decline card transactions as they occur. This feature enables enhanced security controls and custom authorization logic for your organization's cards.
Real-time Processing
The authorization system uses NATS JetStream for real-time event streaming, ensuring reliable message delivery and transaction processing within strict time constraints.
Stream Configuration
stream: CARD_TX_AUTH
subjects:
- card.transaction.auth.>
max_age: 24h
max_msgs_per_subject: 10000
storage: file
retention: limits
discard: oldTransaction Authorization Request
When a card transaction occurs, you will receive an authorization request event.
Subject Pattern
Event Structure
{
"transaction_id": "550e8400-e29b-41d4-a716-446655440000",
"card_id": "7aa53a6d-5869-c814-5cee-2af0f5d92aa5",
"user_id": "40ec785e-d915-49de-9053-4630042d8182",
"entity_id": "7632a09c-9408-4cf0-b8dd-cada3f089240",
"timestamp": "2024-01-15T10:30:45.123Z",
"expires_at": "2024-01-15T10:30:50.123Z", // 5 second timeout
"merchant": {
"name": "AMAZON.COM",
"category_code": "5999", // MCC code
"category": "Miscellaneous Retail",
"city": "Seattle",
"country": "USA",
"terminal_id": "12345678"
},
"transaction": {
"type": "PURCHASE", // PURCHASE, ATM_WITHDRAWAL, REFUND, etc.
"amount": 15000, // Amount in cents
"currency": "USD",
"method": "CHIP", // CHIP, CONTACTLESS, SWIPE, ONLINE, etc.
"pos_entry_mode": "CHIP_PIN",
"international": false,
"recurring": false
},
"authorization": {
"available_balance": 50000, // Available balance in cents
"daily_spent": 10000, // Amount spent today
"monthly_spent": 45000, // Amount spent this month
"limits": {
"transaction": 20000,
"daily": 50000,
"monthly": 200000
}
},
"metadata": {
"ip_address": "192.168.1.1",
"device_id": "mobile_app_123",
"location": {
"latitude": 47.6062,
"longitude": -122.3321
}
}
}Transaction Authorization Response
You must respond to the authorization request within 5 seconds with your decision.
Response Structure
{
"transaction_id": "550e8400-e29b-41d4-a716-446655440000",
"decision": "APPROVE", // APPROVE, DECLINE, REVIEW
"reason_code": "00", // ISO 8583 response code
"reason": "Transaction approved",
"processed_at": "2024-01-15T10:30:46.500Z",
"metadata": {
"risk_score": 15,
"flags": ["trusted_merchant", "regular_pattern"]
}
}Implementation Example
Subscribe to transaction authorization requests for your organization:
import { connect, JSONCodec, createInbox } from 'nats.ws';
const jc = JSONCodec();
// Connect to NATS
const nc = await connect({
servers: ["wss://hermes.sava.africa"],
authenticator: jwtAuthenticator(jwt, seed),
});
// Subscribe to transaction authorization requests for your organization
const sub = nc.subscribe('card.transaction.auth.*.*');
(async () => {
for await (const msg of sub) {
try {
const authRequest = jc.decode(msg.data);
// Apply your custom authorization logic
const decision = await evaluateTransaction(authRequest);
// Respond within 5 seconds
const response = {
transaction_id: authRequest.transaction_id,
decision: decision.approved ? "APPROVE" : "DECLINE",
reason_code: decision.approved ? "00" : "51",
reason: decision.reason,
processed_at: new Date().toISOString(),
metadata: decision.metadata
};
msg.respond(jc.encode(response));
} catch (err) {
console.error('Failed to process auth request:', err);
// If you don't respond, the transaction will be processed
// according to default rules
}
}
})();Custom Authorization Logic
Example implementation of custom authorization rules:
async function evaluateTransaction(authRequest) {
// Example: Implement custom rules
const rules = [
checkVelocityLimits,
checkMerchantBlacklist,
checkGeolocation,
checkTimeRestrictions,
checkAmountThresholds
];
for (const rule of rules) {
const result = await rule(authRequest);
if (!result.passed) {
return {
approved: false,
reason: result.reason,
metadata: { failed_rule: rule.name }
};
}
}
return {
approved: true,
reason: "All checks passed",
metadata: { risk_score: calculateRiskScore(authRequest) }
};
}Response Codes
Standard ISO 8583 response codes for transaction authorization:
| Code | Description | Action |
|---|---|---|
| 00 | Approved | Transaction proceeds |
| 05 | Do not honor | Generic decline |
| 14 | Invalid card number | Decline - card issue |
| 51 | Insufficient funds | Decline - balance issue |
| 54 | Expired card | Decline - card expired |
| 61 | Exceeds withdrawal limit | Decline - limit exceeded |
| 65 | Exceeds withdrawal frequency | Decline - velocity limit |
| 85 | No reason to decline | Approve with conditions |
| 91 | Issuer unavailable | Retry or use default rules |
Timeout Handling
Response Timeout: 5 seconds
Default Behavior: If no response is received within the timeout, the transaction is processed according to the card's default rules and limits
Partial Responses: Responses received after timeout are logged but not applied
Rate Limits
- Maximum 100 authorization requests per second per organization
- Burst capacity of 500 requests
- Rate limits are applied at the organization level
Service Level Agreement
- 99.9% uptime for authorization stream
- P95 latency < 100ms for message delivery
- Automatic failover to default rules if system is unavailable
Webhook Alternative (Coming Soon)
For systems that cannot maintain persistent NATS connections, we will offer a webhook-based authorization API:
POST https://your-domain.com/card/authorize
Headers:
X-Nexus-Signature: <hmac-sha256-signature>
X-Nexus-Timestamp: <unix-timestamp>
Body: <authorization-request-json>