Skip to main content

Withdraw Funds from Card

Overview

Use this endpoint to withdraw funds from a Dedicated Funds Card back to your budget account. This operation moves funds from the card's balance back to your budget, reducing the card's available balance. Withdrawals are the reverse operation of top-ups.

Common reasons to withdraw funds:

  • Reclaiming unused card balance
  • Rebalancing funds across cards
  • Deactivating a card and recovering funds
  • End of campaign or project
  • Budget reallocation

NOTE: This API is offered on a client-by-client approval basis. Speak to your account manager to check eligibility.

IMPORTANT: This endpoint only works for Dedicated Funds Cards. Shared Funds Cards do not hold independent balances and therefore cannot have withdrawals.

Resource Access

Production (api.ahrvo.network)

POST https://api.ahrvo.network/card/issuance/api/issuing/card/v2/funding/withdraw

Staging (gateway.ahrvo.network)

POST https://gateway.ahrvo.network/card/issuance/api/issuing/card/v2/funding/withdraw

Request Headers

HeaderValueRequiredDescription
Acceptapplication/jsonYesContent type for the response
Content-Typeapplication/jsonYesContent type of the request body
AuthorizationBearer {access_token}YesBearer token for authentication
x-api-keyAPI KeyYesAPI key for authentication

Request Body

FieldTypeRequiredDescription
unique_order_idstringYesWithdrawal order ID provided by you for idempotency. Character limit: 36
card_idstringYesA unique ID of the card to withdraw from
amountstringYesMonetary amount expressed in USD (e.g., "100.00")

Request Example

{
"card_id": "card2020092710410645531",
"amount": "100.00",
"unique_order_id": "1cc24f5b-5dda-4c2f-8811-df2047469f9f"
}

Response

Success Response (200 OK)

{
"code": "SUCCESS",
"data": {
"record_id": "coi202511211358115253"
}
}

Response Fields

FieldTypeDescription
codestringStatus string indicating the result. "SUCCESS" refers to a successful withdrawal initiation
dataobjectResponse data object
data.record_idstringA unique ID of the withdrawal record generated by PingPong

Error Responses

  • 400 Bad Request: Invalid parameters, insufficient card balance, or duplicate unique_order_id
  • 401 Unauthorized: Invalid or missing authentication token
  • 403 Forbidden: Card issuance feature not enabled for this account
  • 404 Not Found: Card does not exist

Code Examples

cURL

curl -X POST \
'https://gateway.ahrvo.network/card/issuance/api/issuing/card/v2/funding/withdraw' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-H 'x-api-key: YOUR_API_KEY' \
-d '{
"card_id": "card2020092710410645531",
"amount": "100.00",
"unique_order_id": "1cc24f5b-5dda-4c2f-8811-df2047469f9f"
}'

Python

import requests
import uuid

url = "https://gateway.ahrvo.network/card/issuance/api/issuing/card/v2/funding/withdraw"
headers = {
"Accept": "application/json",
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_ACCESS_TOKEN",
"x-api-key": "YOUR_API_KEY"
}
payload = {
"card_id": "card2020092710410645531",
"amount": "100.00",
"unique_order_id": str(uuid.uuid4())
}

response = requests.post(url, headers=headers, json=payload)
result = response.json()

if result['code'] == 'SUCCESS':
data = result['data']

print("Funds Withdrawn Successfully!")
print(f" Record ID: {data['record_id']}")
print(f" Amount: ${payload['amount']}")
print(f" Card ID: {payload['card_id']}")
else:
print(f"Failed to withdraw funds: {result}")

JavaScript (Node.js)

const axios = require('axios');
const { v4: uuidv4 } = require('uuid');

const url = 'https://gateway.ahrvo.network/card/issuance/api/issuing/card/v2/funding/withdraw';
const headers = {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'x-api-key': 'YOUR_API_KEY'
};
const payload = {
card_id: 'card2020092710410645531',
amount: '100.00',
unique_order_id: uuidv4()
};

axios.post(url, payload, { headers })
.then(response => {
const result = response.data;

if (result.code === 'SUCCESS') {
const data = result.data;

console.log('Funds Withdrawn Successfully!');
console.log(` Record ID: ${data.record_id}`);
console.log(` Amount: $${payload.amount}`);
console.log(` Card ID: ${payload.card_id}`);
}
})
.catch(error => {
console.error('Failed to withdraw funds:', error.response.data);
});

Usage Notes

  • Idempotency: Use unique unique_order_id values to prevent duplicate withdrawals
  • Immediate Processing: Funds are typically transferred within seconds
  • Card Balance: Ensure card has sufficient balance before withdrawal
  • USD Only: All amounts must be in USD with exactly 2 decimal places
  • Record Tracking: Save the returned record_id to track the withdrawal in funding orders
  • Dedicated Funds Only: This endpoint only works for Dedicated Funds Cards

Common Use Cases

Withdraw Unused Card Balance

Reclaim unused funds from a card:

def withdraw_unused_funds(card_id):
"""
Withdraw all or most unused funds from a card

Args:
card_id: Card ID to withdraw from
"""
import uuid

print(f"\n=== Withdraw Unused Funds ===")
print(f"Card ID: {card_id}\n")

# Step 1: Check current balance
print("Step 1: Checking card balance...")
balance_response = query_card_balance(card_id)

if balance_response['code'] != 'SUCCESS':
print(f"✗ Failed to check balance: {balance_response}")
return {'success': False, 'error': balance_response}

card_number = balance_response['data']['card_number']
available_balance = float(balance_response['data']['available_balance'])

print(f" Card: {card_number}")
print(f" Available Balance: ${available_balance:.2f}")

if available_balance <= 0:
print(f"\n⚠️ No funds to withdraw")
return {'success': False, 'reason': 'No balance'}

# Leave a small buffer (e.g., $1) to keep card operational if needed
buffer_amount = 1.00
withdraw_amount = max(0, available_balance - buffer_amount)

if withdraw_amount <= 0:
print(f"\n⚠️ Balance too low to withdraw (keeping ${buffer_amount:.2f} buffer)")
return {'success': False, 'reason': 'Balance too low'}

print(f"\nStep 2: Withdrawing ${withdraw_amount:.2f}...")
print(f" (Keeping ${buffer_amount:.2f} buffer on card)")

# Execute withdrawal
payload = {
"card_id": card_id,
"amount": f"{withdraw_amount:.2f}",
"unique_order_id": str(uuid.uuid4())
}

response = requests.post(url, headers=headers, json=payload)
result = response.json()

if result['code'] == 'SUCCESS':
data = result['data']
expected_new_balance = available_balance - withdraw_amount

print(f"\n✓ Withdrawal Successful!")
print(f" Record ID: {data['record_id']}")
print(f" Amount Withdrawn: ${withdraw_amount:.2f}")
print(f" Previous Balance: ${available_balance:.2f}")
print(f" Expected New Balance: ${expected_new_balance:.2f}")

# Verify new balance
time.sleep(2)
verify_response = query_card_balance(card_id)
if verify_response['code'] == 'SUCCESS':
new_balance = float(verify_response['data']['available_balance'])
print(f" Actual New Balance: ${new_balance:.2f}")

return {
'success': True,
'record_id': data['record_id'],
'amount_withdrawn': withdraw_amount,
'previous_balance': available_balance,
'new_balance': expected_new_balance
}
else:
print(f"\n✗ Withdrawal Failed: {result}")
return {'success': False, 'error': result}

# Example usage
result = withdraw_unused_funds('card2020092710410645531')

if result['success']:
print(f"\n✅ Successfully withdrew ${result['amount_withdrawn']:.2f}")
else:
print(f"\n❌ Failed to withdraw funds")

Withdraw All Funds Before Card Closure

Recover all funds before closing a card:

async function withdrawAllFundsBeforeClosure(cardId) {
console.log('\n=== Withdraw All Funds Before Closure ===\n');

try {
// Step 1: Get current balance
console.log('Step 1: Checking card balance...');
const balanceResponse = await queryCardBalance(cardId);

if (balanceResponse.code !== 'SUCCESS') {
console.log('✗ Failed to check balance');
return { success: false, error: balanceResponse };
}

const cardNumber = balanceResponse.data.card_number;
const availableBalance = parseFloat(balanceResponse.data.available_balance);

console.log(` Card: ${cardNumber}`);
console.log(` Available Balance: $${availableBalance.toFixed(2)}`);

if (availableBalance <= 0) {
console.log('\n✓ No balance to withdraw - card ready for closure');
return {
success: true,
no_balance: true,
ready_for_closure: true
};
}

// Step 2: Withdraw entire balance
console.log(`\nStep 2: Withdrawing entire balance of $${availableBalance.toFixed(2)}...`);

const { v4: uuidv4 } = require('uuid');
const payload = {
card_id: cardId,
amount: availableBalance.toFixed(2),
unique_order_id: uuidv4()
};

const withdrawResponse = await withdrawFunds(payload);

if (withdrawResponse.code !== 'SUCCESS') {
console.log('✗ Withdrawal failed');
return { success: false, error: withdrawResponse };
}

console.log('✓ Withdrawal successful');
console.log(` Record ID: ${withdrawResponse.data.record_id}`);

// Step 3: Verify balance is zero
console.log('\nStep 3: Verifying balance is zero...');

await new Promise(resolve => setTimeout(resolve, 2000)); // Wait 2 seconds

const verifyResponse = await queryCardBalance(cardId);

if (verifyResponse.code === 'SUCCESS') {
const newBalance = parseFloat(verifyResponse.data.available_balance);
console.log(` New Balance: $${newBalance.toFixed(2)}`);

if (newBalance === 0 || newBalance < 0.01) {
console.log('\n✅ Card balance cleared - ready for closure!');

return {
success: true,
card_number: cardNumber,
amount_withdrawn: availableBalance,
record_id: withdrawResponse.data.record_id,
ready_for_closure: true
};
} else {
console.log(`\n⚠️ Balance not fully cleared: $${newBalance.toFixed(2)} remaining`);

return {
success: true,
card_number: cardNumber,
amount_withdrawn: availableBalance,
record_id: withdrawResponse.data.record_id,
ready_for_closure: false,
remaining_balance: newBalance
};
}
} else {
console.log('⚠️ Could not verify new balance');

return {
success: true,
amount_withdrawn: availableBalance,
record_id: withdrawResponse.data.record_id,
verification_failed: true
};
}
} catch (error) {
console.error('Error withdrawing funds:', error.message);
return { success: false, error: error.message };
}
}

// Example usage with card closure workflow
async function closeCardWorkflow(cardId) {
console.log('\n🔒 Starting Card Closure Workflow\n');

// Step 1: Withdraw all funds
const withdrawResult = await withdrawAllFundsBeforeClosure(cardId);

if (!withdrawResult.success) {
console.log('\n❌ Failed to withdraw funds - cannot close card safely');
return;
}

if (!withdrawResult.ready_for_closure) {
console.log('\n⚠️ Card not ready for closure - balance remains');
return;
}

// Step 2: Close the card
console.log('\nStep 4: Closing card...');
const closeResult = await closeCard(cardId);

if (closeResult.code === 'SUCCESS') {
console.log('✓ Card closed successfully');
console.log('\n✅ Card Closure Complete!');
console.log(` Funds Withdrawn: $${withdrawResult.amount_withdrawn?.toFixed(2) || '0.00'}`);
console.log(` Card Status: CLOSED`);
} else {
console.log('✗ Failed to close card');
}
}

// Run the workflow
await closeCardWorkflow('card2020092710410645531');

Batch Withdraw from Multiple Cards

Withdraw funds from multiple cards at once:

def batch_withdraw_funds(withdrawals):
"""
Withdraw funds from multiple cards

Args:
withdrawals: List of dicts with 'card_id' and 'amount'
"""
import uuid
from datetime import datetime

print("\n" + "=" * 70)
print("BATCH FUNDS WITHDRAWAL".center(70))
print("=" * 70)
print(f"Started: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print(f"Total Cards: {len(withdrawals)}\n")

results = {
'successful': [],
'failed': [],
'total_amount': 0
}

for idx, item in enumerate(withdrawals, 1):
card_id = item['card_id']
amount = float(item['amount'])

print(f"[{idx}/{len(withdrawals)}] Processing {card_id}...")
print(f" Amount: ${amount:.2f}")

try:
# Check balance first
balance_response = query_card_balance(card_id)

if balance_response['code'] == 'SUCCESS':
card_number = balance_response['data']['card_number']
available_balance = float(balance_response['data']['available_balance'])

print(f" Card: {card_number}")
print(f" Available Balance: ${available_balance:.2f}")

if amount > available_balance:
print(f" ✗ Insufficient balance (need ${amount:.2f}, have ${available_balance:.2f})\n")

results['failed'].append({
'card_id': card_id,
'card_number': card_number,
'amount': amount,
'error': 'Insufficient balance'
})
continue

# Execute withdrawal
payload = {
"card_id": card_id,
"amount": f"{amount:.2f}",
"unique_order_id": str(uuid.uuid4())
}

response = requests.post(url, headers=headers, json=payload)
result = response.json()

if result['code'] == 'SUCCESS':
data = result['data']

print(f" ✓ Success - Record ID: {data['record_id']}")

results['successful'].append({
'card_id': card_id,
'card_number': card_number,
'amount': amount,
'record_id': data['record_id']
})

results['total_amount'] += amount
else:
print(f" ✗ Failed: {result.get('message', 'Unknown error')}")

results['failed'].append({
'card_id': card_id,
'card_number': card_number,
'amount': amount,
'error': result
})
else:
print(f" ✗ Failed to check balance")

results['failed'].append({
'card_id': card_id,
'amount': amount,
'error': 'Balance check failed'
})

except Exception as e:
print(f" ✗ Exception: {e}")

results['failed'].append({
'card_id': card_id,
'amount': amount,
'error': str(e)
})

print()

# Summary
print("=" * 70)
print("BATCH WITHDRAWAL SUMMARY".center(70))
print("=" * 70)
print(f"Total Cards: {len(withdrawals)}")
print(f"Successful: {len(results['successful'])} ✓")
print(f"Failed: {len(results['failed'])} ✗")
print(f"Total Amount Withdrawn: ${results['total_amount']:,.2f}")

if results['failed']:
print(f"\n⚠️ Failed Withdrawals:")
for item in results['failed']:
card_info = item.get('card_number', item['card_id'])
print(f" • {card_info}: ${item['amount']:.2f} - {item.get('error', 'Unknown')}")

print("\n" + "=" * 70 + "\n")

return results

# Example usage
withdrawals = [
{'card_id': 'card2020092710410645531', 'amount': '500.00'},
{'card_id': 'card2020092710410645532', 'amount': '250.00'},
{'card_id': 'card2020092710410645533', 'amount': '1000.00'},
{'card_id': 'card2020092710410645534', 'amount': '750.00'}
]

results = batch_withdraw_funds(withdrawals)

print(f"Batch complete: {len(results['successful'])}/{len(withdrawals)} successful")
print(f"Total funds recovered: ${results['total_amount']:,.2f}")

Rebalance Funds Across Cards

Move funds from over-funded to under-funded cards:

async function rebalanceFundsAcrossCards(cardIds, targetBalance) {
console.log('\n=== Rebalance Funds Across Cards ===');
console.log(`Target Balance per Card: $${targetBalance.toFixed(2)}\n`);

const operations = {
withdrawals: [],
topups: [],
balanced: []
};

// Step 1: Check all card balances
console.log('Step 1: Checking all card balances...\n');

for (const cardId of cardIds) {
const balanceResponse = await queryCardBalance(cardId);

if (balanceResponse.code === 'SUCCESS') {
const cardNumber = balanceResponse.data.card_number;
const currentBalance = parseFloat(balanceResponse.data.available_balance);
const difference = currentBalance - targetBalance;

console.log(`Card: ${cardNumber}`);
console.log(` Current: $${currentBalance.toFixed(2)}`);
console.log(` Target: $${targetBalance.toFixed(2)}`);
console.log(` Difference: ${difference >= 0 ? '+' : ''}$${difference.toFixed(2)}`);

if (Math.abs(difference) < 0.01) {
console.log(` ✓ Already balanced\n`);
operations.balanced.push({
card_id: cardId,
card_number: cardNumber,
balance: currentBalance
});
} else if (difference > 0) {
console.log(` ➖ Withdraw $${difference.toFixed(2)}\n`);
operations.withdrawals.push({
card_id: cardId,
card_number: cardNumber,
current_balance: currentBalance,
amount: difference
});
} else {
console.log(` ➕ Top-up $${Math.abs(difference).toFixed(2)}\n`);
operations.topups.push({
card_id: cardId,
card_number: cardNumber,
current_balance: currentBalance,
amount: Math.abs(difference)
});
}
}
}

// Step 2: Execute withdrawals
if (operations.withdrawals.length > 0) {
console.log(`\nStep 2: Executing ${operations.withdrawals.length} withdrawal(s)...\n`);

for (const withdrawal of operations.withdrawals) {
console.log(`Withdrawing $${withdrawal.amount.toFixed(2)} from ${withdrawal.card_number}...`);

const { v4: uuidv4 } = require('uuid');
const payload = {
card_id: withdrawal.card_id,
amount: withdrawal.amount.toFixed(2),
unique_order_id: uuidv4()
};

const result = await withdrawFunds(payload);

if (result.code === 'SUCCESS') {
console.log(` ✓ Withdrawn successfully - Record: ${result.data.record_id}\n`);
withdrawal.success = true;
withdrawal.record_id = result.data.record_id;
} else {
console.log(` ✗ Failed: ${result.message || 'Unknown error'}\n`);
withdrawal.success = false;
withdrawal.error = result;
}
}
}

// Step 3: Execute top-ups
if (operations.topups.length > 0) {
console.log(`\nStep 3: Executing ${operations.topups.length} top-up(s)...\n`);

for (const topup of operations.topups) {
console.log(`Topping up $${topup.amount.toFixed(2)} to ${topup.card_number}...`);

const { v4: uuidv4 } = require('uuid');
const payload = {
card_id: topup.card_id,
amount: topup.amount.toFixed(2),
unique_order_id: uuidv4()
};

const result = await topUpCard(payload);

if (result.code === 'SUCCESS') {
console.log(` ✓ Topped up successfully - Record: ${result.data.record_id}\n`);
topup.success = true;
topup.record_id = result.data.record_id;
} else {
console.log(` ✗ Failed: ${result.message || 'Unknown error'}\n`);
topup.success = false;
topup.error = result;
}
}
}

// Summary
console.log('\n=== Rebalancing Summary ===');
console.log(`Total Cards: ${cardIds.length}`);
console.log(`Already Balanced: ${operations.balanced.length}`);
console.log(`Withdrawals: ${operations.withdrawals.length}`);
console.log(`Top-ups: ${operations.topups.length}`);

const successfulWithdrawals = operations.withdrawals.filter(w => w.success).length;
const successfulTopups = operations.topups.filter(t => t.success).length;

console.log(`\nSuccessful Operations:`);
console.log(` Withdrawals: ${successfulWithdrawals}/${operations.withdrawals.length}`);
console.log(` Top-ups: ${successfulTopups}/${operations.topups.length}`);

return operations;
}

// Example usage
const cardList = [
'card2020092710410645531',
'card2020092710410645532',
'card2020092710410645533',
'card2020092710410645534'
];

const result = await rebalanceFundsAcrossCards(cardList, 500.00);

Scheduled Fund Recovery

Automatically withdraw excess funds on a schedule:

def schedule_fund_recovery(card_id, threshold_amount, schedule_time):
"""
Schedule automatic withdrawal when balance exceeds threshold

Args:
card_id: Card ID to monitor
threshold_amount: Balance threshold to trigger withdrawal
schedule_time: Time to check and withdraw (cron format)
"""
import schedule
import time

print(f"\n📅 Scheduled Fund Recovery")
print(f" Card: {card_id}")
print(f" Threshold: ${threshold_amount:.2f}")
print(f" Schedule: {schedule_time}\n")

def check_and_withdraw():
print(f"\n⏰ Running scheduled fund recovery check...")
print(f" Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")

# Check balance
balance_response = query_card_balance(card_id)

if balance_response['code'] == 'SUCCESS':
card_number = balance_response['data']['card_number']
available_balance = float(balance_response['data']['available_balance'])

print(f"Card: {card_number}")
print(f" Current Balance: ${available_balance:.2f}")
print(f" Threshold: ${threshold_amount:.2f}")

if available_balance > threshold_amount:
excess_amount = available_balance - threshold_amount

print(f" ⚠️ Balance exceeds threshold by ${excess_amount:.2f}")
print(f" Withdrawing excess funds...")

payload = {
"card_id": card_id,
"amount": f"{excess_amount:.2f}",
"unique_order_id": str(uuid.uuid4())
}

response = requests.post(url, headers=headers, json=payload)
result = response.json()

if result['code'] == 'SUCCESS':
print(f" ✓ Withdrawal successful!")
print(f" Record ID: {result['data']['record_id']}")
print(f" Amount: ${excess_amount:.2f}")
print(f" New Balance: ${threshold_amount:.2f}")

# Send notification
send_notification(
f"Scheduled fund recovery: ${excess_amount:.2f} withdrawn from card {card_number}"
)
else:
print(f" ✗ Withdrawal failed: {result}")
send_alert(f"Failed to withdraw from card {card_number}", result)
else:
print(f" ✓ Balance below threshold - no withdrawal needed")
else:
print(f"✗ Failed to check balance: {balance_response}")

# Schedule the job (example: daily at 2 AM)
schedule.every().day.at("02:00").do(check_and_withdraw)

print("✓ Scheduled fund recovery configured")
print(" Will check daily at 2:00 AM")

# Keep the scheduler running
while True:
schedule.run_pending()
time.sleep(60) # Check every minute

# Example usage
schedule_fund_recovery(
'card2020092710410645531',
threshold_amount=1000.00,
schedule_time='02:00'
)

Campaign End Cleanup

Withdraw funds at campaign end:

async function campaignEndCleanup(campaignCards) {
console.log('\n=== Campaign End Cleanup ===\n');
console.log(`Processing ${campaignCards.length} campaign card(s)\n`);

const results = {
total_recovered: 0,
cards_processed: []
};

for (const campaign of campaignCards) {
const { card_id, campaign_name, end_date } = campaign;

console.log(`\nCampaign: ${campaign_name}`);
console.log(`End Date: ${end_date}`);
console.log(`Card: ${card_id}\n`);

try {
// Get card balance
const balanceResponse = await queryCardBalance(card_id);

if (balanceResponse.code === 'SUCCESS') {
const cardNumber = balanceResponse.data.card_number;
const balance = parseFloat(balanceResponse.data.available_balance);

console.log(` Balance: $${balance.toFixed(2)}`);

if (balance > 0) {
console.log(' Withdrawing remaining funds...');

const { v4: uuidv4 } = require('uuid');
const withdrawPayload = {
card_id: card_id,
amount: balance.toFixed(2),
unique_order_id: uuidv4()
};

const withdrawResponse = await withdrawFunds(withdrawPayload);

if (withdrawResponse.code === 'SUCCESS') {
console.log(` ✓ Funds withdrawn successfully`);
console.log(` Record ID: ${withdrawResponse.data.record_id}`);

results.total_recovered += balance;
results.cards_processed.push({
campaign_name: campaign_name,
card_id: card_id,
card_number: cardNumber,
amount_recovered: balance,
record_id: withdrawResponse.data.record_id,
success: true
});

// Update card remark
await updateCardRemark({
card_id: card_id,
remark: `Campaign Ended: ${campaign_name} - Funds recovered ${new Date().toLocaleDateString()}`
});

console.log(` ✓ Card remark updated`);
} else {
console.log(` ✗ Withdrawal failed`);

results.cards_processed.push({
campaign_name: campaign_name,
card_id: card_id,
success: false,
error: withdrawResponse
});
}
} else {
console.log(' ✓ No balance to withdraw');

results.cards_processed.push({
campaign_name: campaign_name,
card_id: card_id,
card_number: cardNumber,
amount_recovered: 0,
success: true,
no_balance: true
});
}
} else {
console.log(' ✗ Failed to check balance');

results.cards_processed.push({
campaign_name: campaign_name,
card_id: card_id,
success: false,
error: 'Balance check failed'
});
}
} catch (error) {
console.error(` ✗ Error: ${error.message}`);

results.cards_processed.push({
campaign_name: campaign_name,
card_id: card_id,
success: false,
error: error.message
});
}
}

// Summary
console.log('\n' + '='.repeat(60));
console.log('CAMPAIGN CLEANUP SUMMARY');
console.log('='.repeat(60));
console.log(`Total Campaigns: ${campaignCards.length}`);
console.log(`Successfully Processed: ${results.cards_processed.filter(c => c.success).length}`);
console.log(`Failed: ${results.cards_processed.filter(c => !c.success).length}`);
console.log(`Total Funds Recovered: $${results.total_recovered.toFixed(2)}`);
console.log('='.repeat(60) + '\n');

return results;
}

// Example usage
const campaignCards = [
{
card_id: 'card2020092710410645531',
campaign_name: 'Spring Sale 2026',
end_date: '2026-03-31'
},
{
card_id: 'card2020092710410645532',
campaign_name: 'Product Launch - Widget Pro',
end_date: '2026-04-15'
}
];

const result = await campaignEndCleanup(campaignCards);

console.log(`✅ Campaign cleanup complete`);
console.log(` Total funds recovered: $${result.total_recovered.toFixed(2)}`);

Best Practices

  • Idempotency: Always use unique unique_order_id values (UUID recommended)
  • Balance Check: Verify card balance before attempting withdrawal
  • Amount Validation: Ensure withdrawal amount doesn't exceed card balance
  • Verification: Query balance after withdrawal to confirm transaction
  • Record Tracking: Store record_id values for reconciliation
  • Budget Monitoring: Track budget account balance after receiving funds
  • Error Handling: Implement retry logic for network errors
  • Notifications: Alert administrators of large withdrawals

Understanding Card Funding Operations

Withdrawal Flow

  1. Initiate: Call this endpoint with card ID and amount
  2. Debit Card: Amount is deducted from card's balance
  3. Credit Budget: Amount is added to your budget account
  4. Immediate: Funds typically available within seconds
  5. Track: Use returned record_id to track the funding order

Funding Operations Comparison

OperationDescriptionAmount DirectionUse Case
Top-Up (card_in)Add funds to cardBudget → CardPrepare card for use
Withdrawal (card_out)Remove funds from cardCard → BudgetRecover unused funds
  • Top-Up Dedicated Funds Card - Add funds to a card
  • Query Dedicated Funds Card Balance - Check current card balance
  • Query Card Funding Orders - View withdrawal and top-up history
  • Query Budget Balance - Check budget account balance after withdrawal
  • Close a Card - Permanently close a card (withdraw funds first)

Troubleshooting

Insufficient Card Balance (400)

  • Cause: Card doesn't have enough funds for withdrawal
  • Solution:
    • Query card balance first
    • Reduce withdrawal amount
    • Withdrawal amount cannot exceed available balance

Duplicate Order ID (400)

  • Cause: The unique_order_id was used in a previous request
  • Solution:
    • Generate a new UUID for each withdrawal attempt
    • Don't retry with the same unique_order_id

Card Not Found (404)

  • Cause: Invalid card_id or card doesn't exist
  • Solution:
    • Verify the card ID is correct
    • Use Query All Cards to list available cards
    • Check if card was deleted or closed

Wrong Card Type (400)

  • Cause: Attempting to withdraw from a Shared Funds Card
  • Solution:
    • This endpoint only works for Dedicated Funds Cards
    • Shared Funds Cards don't hold independent balances

Withdrawal Not Reflected

  • Cause: Balance query timing or processing delay
  • Solution:
    • Wait 2-5 seconds and query balance again
    • Use record_id to verify the funding order status
    • Check Query Card Funding Orders for the transaction

Security Considerations

  • Access Control: Restrict withdrawal operations to authorized users only
  • Amount Limits: Implement maximum withdrawal limits in your application
  • Approval Workflow: Require approval for large withdrawals
  • Audit Logging: Log all withdrawal operations for security auditing
  • Fraud Detection: Monitor for unusual withdrawal patterns
  • Rate Limiting: Implement reasonable limits on withdrawal frequency
  • Balance Validation: Always verify sufficient balance before withdrawal

Interactive API Explorer