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.
Base Subject
All Cards API endpoints use the base subject:svc.card.*
Access Control
Only the assigned cardholder can retrieve details. Card must not be INACTIVE.
Access Control
Only the assigned cardholder can change card status.
Virtual Card Flow (Getting Started)
Key Concepts
- ID reuse: the id returned from “request card” is the request_id and becomes the card_id after approval/creation.
- Visibility: list_requests shows request lifecycle; list_cards shows provisioned cards (id, last_4, status).
- Security: only the assigned cardholder can get_details/set_pin/update_status/activate (derived from token).
Step 1 — Request Card
- Call:
svc.card.{partner}.request
- Send: { account_id, user_id, name, type: 1, use_type: 2, features, limits?, extras? }
- Receive: { id } — save this; it will be the card_id once approved.
Step 2 — Track Approval
- Call:
svc.card.{partner}.list_requests
- Send: { account_id } (optionally add user_ids)
- Expect: your item with the same id and status: "APPROVED" once processed.
- Tip: single-use (use_type=1) is not available; the service rejects it.
Step 3 — Get Card Details (PAN/CVV)
- Call:
svc.card.{partner}.get_details
- Send: { account_id, id } where id is the value from Step 1
- Receive: { card_number, expiry_date: "MMYY", cvv }
- Constraints: caller must be the assigned cardholder; card must not be INACTIVE (virtual cards are ACTIVE).
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_4 is masked ("****") when status is INACTIVE; visible when ACTIVE.
Optional — Cardholder Actions
- set_pin: { account_id, id, pin: "1234" } — 4 digits; cooldown applies between requests.
- update_status: { account_id, id, status: "ACTIVE" | "FROZEN" } — cardholder safety control.
Limits & Features Notes
- Limits: values are minor units (e.g., cents). Validations apply: at least one limit required; daily ≤ single; monthly ≤ daily; yearly ≤ monthly; single ≤ max(periodic).
- Features: you can enable domestic/international/e_commerce/atm/pos/contactless on request. Edits currently persist a subset (international, e_commerce, atm) and numeric limits; others may not be applied yet.
Card Features and Limits
Card Features
When requesting or configuring cards, you can specify which features should be enabled:
{
"domestic": true, // Allow domestic transactions
"international": false, // Allow international transactions
"e_commerce": true, // Allow online purchases
"atm": true, // Allow ATM withdrawals
"pos": true, // Allow point-of-sale transactions
"contactless": true // Allow contactless payments
}
Where used:
- Send with: request (required), edit (optional)
- Returned by: list_requests.items[*].features, list_cards.cards[*].features
- Not in: get_details, update_status, activate_card, set_pin
Notes:
- International toggle present; full enablement may be staged.
- Edit currently persists a subset (international, e_commerce, atm).
Card Limits
You can set various spending limits on cards:
{
"transaction_enabled": true, // Enable per-transaction limit
"transaction": 5000, // Maximum amount per transaction (in cents)
"daily_enabled": true, // Enable daily spending limit
"daily": 20000, // Maximum daily spend (in cents)
"monthly_enabled": true, // Enable monthly spending limit
"monthly": 100000, // Maximum monthly spend (in cents)
"yearly_enabled": false, // Enable yearly spending limit
"yearly": 0 // Maximum yearly spend (in cents)
}
Where used:
- Send with: request (optional — at least one amount must be > 0), edit (optional)
- Returned by: list_requests.items[*].limits, list_cards.cards[*].limits
- Not in: get_details, update_status, activate_card, set_pin
Notes:
- Units are minor units (e.g., cents) of the account currency.
- Validation: single ≤ max(daily, monthly, yearly); daily ≤ single; monthly ≤ daily; yearly ≤ monthly.
Card Extras
Additional card features that can be configured:
{
"auto_lock": "2023-12-31T23:59:59Z" // Automatically lock the card at this time
}
Where used:
- Send with: request (optional), edit (optional)
- Returned by: list_requests.items[*].extras, list_cards.cards[*].extras (currently {} in list_cards)
- Not in: get_details, update_status, activate_card, set_pin
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 - card features
"domestic": true,
"international": false,
"e_commerce": true,
"atm": true,
"pos": true,
"contactless": true
},
"limits": { // Optional — at least one amount must be > 0
"transaction_enabled": true,
"transaction": 5000,
"daily_enabled": true,
"daily": 20000,
"monthly_enabled": true,
"monthly": 100000,
"yearly_enabled": false,
"yearly": 0
},
"extras": { // Optional - card extras
"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)
}
Authorization Required
- The returned id becomes the card_id after 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", // Account ID (echoes request.account_id)
"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": 5000,
"daily_enabled": true,
"daily": 20000,
"monthly_enabled": true,
"monthly": 100000,
"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.
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 to default behavior
}
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": 5000,
"daily_enabled": true,
"daily": 20000,
"monthly_enabled": true,
"monthly": 100000,
"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.
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": 10000, // Updated limit
"daily_enabled": true,
"daily": 50000, // Updated limit
"monthly_enabled": true,
"monthly": 200000, // Updated limit
"yearly_enabled": false,
"yearly": 0
},
"extras": {
"auto_lock": "2024-12-31T23:59:59Z"
}
}
Response
{}
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.
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: old
Transaction 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>