Top Up Budget Account
Overview
Use this endpoint to transfer funds and top up the balance of a specific budget account. Budget accounts serve as funding sources for card operations, and maintaining adequate balance is essential for uninterrupted card spending.
NOTE: This API is offered on a client-by-client approval basis. Speak to your account manager to check eligibility.
Resource Access
Production (api.ahrvo.network)
POST https://api.ahrvo.network/card/issuance/api/issuing/budget/v2/top-up
Staging (gateway.ahrvo.network)
POST https://gateway.ahrvo.network/card/issuance/api/issuing/budget/v2/top-up
Request Headers
| Header | Value | Required | Description |
|---|---|---|---|
Accept | application/json | Yes | Content type for the response |
Content-Type | application/json | Yes | Content type of the request body |
Authorization | Bearer {access_token} | Yes | Bearer token for authentication |
x-api-key | API Key | Yes | API key for authentication |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
budget_id | string | Yes | A unique ID of the budget account to top up |
top_up_amount | string | Yes | The amount of funds to be transferred into the budget. Must be a positive number with up to 2 decimal places |
top_up_currency | string | Yes | Currency of the top-up amount. Must follow ISO 4217 standard (e.g., USD, EUR, GBP) |
unique_order_id | string | Yes | Top-up order ID provided by you for idempotency. Use this to safely retry requests and query the status later |
Request Example
{
"budget_id": "ci201910231405089666",
"top_up_amount": "10.45",
"top_up_currency": "USD",
"unique_order_id": "1621924039"
}
Response
Success Response (200 OK)
{
"code": "SUCCESS",
"data": {
"record_id": "example_record_id"
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
code | string | Status string indicating the result of the request. "SUCCESS" refers to a successful initiation |
data | object | Response data object |
data.record_id | string | A unique ID of the top-up record generated by the system |
Error Responses
- 400 Bad Request: Invalid parameters, invalid amount format, 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: Budget account does not exist
Code Examples
cURL
curl -X POST \
https://gateway.ahrvo.network/card/issuance/api/issuing/budget/v2/top-up \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-H 'x-api-key: YOUR_API_KEY' \
-d '{
"budget_id": "ci201910231405089666",
"top_up_amount": "10.45",
"top_up_currency": "USD",
"unique_order_id": "1621924039"
}'
Python
import requests
import time
url = "https://gateway.ahrvo.network/card/issuance/api/issuing/budget/v2/top-up"
headers = {
"Accept": "application/json",
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_ACCESS_TOKEN",
"x-api-key": "YOUR_API_KEY"
}
payload = {
"budget_id": "ci201910231405089666",
"top_up_amount": "10.45",
"top_up_currency": "USD",
"unique_order_id": f"topup_{int(time.time())}" # Generate unique ID
}
response = requests.post(url, headers=headers, json=payload)
result = response.json()
if result['code'] == 'SUCCESS':
record_id = result['data']['record_id']
unique_order_id = payload['unique_order_id']
print(f"✓ Top-up initiated successfully")
print(f" Record ID: {record_id}")
print(f" Order ID: {unique_order_id}")
print(f"\nUse the order ID to check status with the Query Top-Up Order endpoint")
else:
print(f"Failed to initiate top-up: {result}")
JavaScript (Node.js)
const axios = require('axios');
const url = 'https://gateway.ahrvo.network/card/issuance/api/issuing/budget/v2/top-up';
const headers = {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'x-api-key': 'YOUR_API_KEY'
};
const payload = {
budget_id: 'ci201910231405089666',
top_up_amount: '10.45',
top_up_currency: 'USD',
unique_order_id: `topup_${Date.now()}` // Generate unique ID
};
axios.post(url, payload, { headers })
.then(response => {
const result = response.data;
if (result.code === 'SUCCESS') {
console.log('✓ Top-up initiated successfully');
console.log(` Record ID: ${result.data.record_id}`);
console.log(` Order ID: ${payload.unique_order_id}`);
console.log('\nUse the order ID to check status with the Query Top-Up Order endpoint');
}
})
.catch(error => {
console.error('Failed to initiate top-up:', error.response.data);
});
Usage Notes
- Asynchronous Processing: Top-ups are processed asynchronously. The successful response means the request was accepted, not that funds are immediately available
- Status Verification: Use the Query Top-Up Order endpoint with
unique_order_idto check if the top-up succeeded - Unique Order ID: Generate unique IDs (e.g., timestamp + UUID) to prevent duplicates
- Idempotency: Using the same
unique_order_idin retries prevents duplicate top-ups - Amount Format: Provide amounts as strings with up to 2 decimal places (e.g., "10.45", not 10.45)
- Currency: Must match the budget account's currency
Common Use Cases
Basic Top-Up with Status Check
Initiate a top-up and verify it completed successfully:
import time
def top_up_budget(budget_id, amount, currency='USD'):
"""
Top up a budget account and wait for completion
"""
# Generate unique order ID
unique_order_id = f"topup_{int(time.time())}"
# Initiate top-up
print(f"Initiating top-up of {currency} {amount}...")
response = initiate_top_up(
budget_id=budget_id,
amount=amount,
currency=currency,
unique_order_id=unique_order_id
)
if response['code'] == 'SUCCESS':
record_id = response['data']['record_id']
print(f"✓ Top-up initiated (Record ID: {record_id})")
# Poll for completion
print("Checking top-up status...")
max_attempts = 10
for attempt in range(max_attempts):
time.sleep(3) # Wait 3 seconds between checks
status_response = query_top_up_order(unique_order_id)
if status_response['code'] == 'SUCCESS':
status = status_response['data']['status']
if status == 'SUCCESS':
print(f"✓ Top-up completed successfully")
return {
'success': True,
'record_id': record_id,
'unique_order_id': unique_order_id
}
elif status == 'FAILED':
print(f"✗ Top-up failed")
return {
'success': False,
'unique_order_id': unique_order_id
}
elif status == 'PROCESSING':
print(f"⏳ Attempt {attempt + 1}/{max_attempts}: Still processing...")
print("⚠ Top-up status check timed out")
return {
'success': None,
'unique_order_id': unique_order_id,
'error': 'Timeout'
}
else:
print(f"✗ Failed to initiate top-up: {response}")
return {'success': False, 'error': response}
# Example usage
result = top_up_budget('ci201910231405089666', '1000.00')
Automatic Low Balance Top-Up
Monitor budget balance and automatically top up when low:
async function autoTopUpOnLowBalance(budgetId, threshold, topUpAmount) {
try {
// Check current balance
const balanceResponse = await queryBudgetBalance(budgetId);
const currentBalance = parseFloat(balanceResponse.data.balance);
const currency = balanceResponse.data.currency;
console.log(`Current balance: ${currency} ${currentBalance}`);
if (currentBalance < threshold) {
console.log(`⚠ Balance below threshold (${threshold})`);
console.log(`Initiating automatic top-up of ${currency} ${topUpAmount}...`);
const uniqueOrderId = `auto_topup_${Date.now()}`;
const topUpResponse = await topUpBudgetAccount({
budget_id: budgetId,
top_up_amount: topUpAmount.toFixed(2),
top_up_currency: currency,
unique_order_id: uniqueOrderId
});
if (topUpResponse.code === 'SUCCESS') {
console.log('✓ Auto top-up initiated');
console.log(` Record ID: ${topUpResponse.data.record_id}`);
// Wait for completion
let attempts = 0;
const maxAttempts = 10;
while (attempts < maxAttempts) {
await new Promise(resolve => setTimeout(resolve, 3000));
attempts++;
const statusResponse = await queryTopUpOrder(uniqueOrderId);
if (statusResponse.data.status === 'SUCCESS') {
console.log('✓ Auto top-up completed successfully');
// Verify new balance
const newBalanceResponse = await queryBudgetBalance(budgetId);
const newBalance = parseFloat(newBalanceResponse.data.balance);
console.log(`New balance: ${currency} ${newBalance}`);
return {
success: true,
old_balance: currentBalance,
new_balance: newBalance,
topped_up_amount: topUpAmount
};
} else if (statusResponse.data.status === 'FAILED') {
console.log('✗ Auto top-up failed');
return { success: false, error: 'Top-up failed' };
}
console.log(`⏳ Attempt ${attempts}/${maxAttempts}: Still processing...`);
}
console.log('⚠ Auto top-up status check timed out');
return { success: null, error: 'Timeout' };
}
} else {
console.log('✓ Balance is sufficient, no top-up needed');
return {
success: true,
balance_sufficient: true,
current_balance: currentBalance
};
}
} catch (error) {
console.error('Auto top-up failed:', error);
throw error;
}
}
// Example usage: Monitor every hour
setInterval(async () => {
await autoTopUpOnLowBalance(
'ci201910231405089666',
1000.00, // Threshold
5000.00 // Top-up amount
);
}, 3600000); // Check every hour
Scheduled Bulk Top-Ups
Top up multiple budget accounts at scheduled intervals:
def scheduled_bulk_top_up(budget_configs):
"""
Top up multiple budget accounts in bulk
Args:
budget_configs: List of dicts with budget_id, amount, currency
"""
import time
from datetime import datetime
results = {
'initiated': [],
'failed': [],
'timestamp': datetime.now().isoformat()
}
print(f"\n=== Starting Bulk Top-Up at {results['timestamp']} ===")
print(f"Processing {len(budget_configs)} budgets...")
for config in budget_configs:
budget_id = config['budget_id']
amount = config['amount']
currency = config.get('currency', 'USD')
budget_name = config.get('name', budget_id)
try:
# Generate unique order ID
unique_order_id = f"bulk_topup_{int(time.time())}_{budget_id[-6:]}"
print(f"\nTopping up {budget_name}...")
response = initiate_top_up(
budget_id=budget_id,
amount=str(amount),
currency=currency,
unique_order_id=unique_order_id
)
if response['code'] == 'SUCCESS':
record_id = response['data']['record_id']
print(f" ✓ Initiated: {currency} {amount} (Record: {record_id})")
results['initiated'].append({
'budget_id': budget_id,
'budget_name': budget_name,
'amount': amount,
'currency': currency,
'unique_order_id': unique_order_id,
'record_id': record_id
})
else:
print(f" ✗ Failed: {response}")
results['failed'].append({
'budget_id': budget_id,
'budget_name': budget_name,
'error': response
})
# Small delay between requests
time.sleep(1)
except Exception as e:
print(f" ✗ Error: {e}")
results['failed'].append({
'budget_id': budget_id,
'budget_name': budget_name,
'error': str(e)
})
# Summary
print(f"\n=== Bulk Top-Up Summary ===")
print(f"Successfully initiated: {len(results['initiated'])}")
print(f"Failed: {len(results['failed'])}")
# Verify all initiated top-ups
if results['initiated']:
print("\nVerifying top-up statuses...")
time.sleep(5) # Wait for processing
for item in results['initiated']:
status_response = query_top_up_order(item['unique_order_id'])
status = status_response['data']['status']
status_icon = '✓' if status == 'SUCCESS' else '✗' if status == 'FAILED' else '⏳'
print(f" {status_icon} {item['budget_name']}: {status}")
return results
# Example usage
budgets_to_top_up = [
{
'budget_id': 'ci201910231405089666',
'name': 'Marketing Budget',
'amount': 10000.00,
'currency': 'USD'
},
{
'budget_id': 'ci201910231405089667',
'name': 'Operations Budget',
'amount': 15000.00,
'currency': 'USD'
},
{
'budget_id': 'ci201910231405089668',
'name': 'Travel Budget',
'amount': 5000.00,
'currency': 'USD'
}
]
results = scheduled_bulk_top_up(budgets_to_top_up)
Top-Up with Retry on Failure
Implement retry logic for failed top-ups:
async function topUpWithRetry(budgetId, amount, currency, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const uniqueOrderId = `topup_retry_${Date.now()}_${attempt}`;
console.log(`\nAttempt ${attempt}/${maxRetries}: Initiating top-up...`);
// Initiate top-up
const topUpResponse = await topUpBudgetAccount({
budget_id: budgetId,
top_up_amount: amount,
top_up_currency: currency,
unique_order_id: uniqueOrderId
});
if (topUpResponse.code === 'SUCCESS') {
console.log(`✓ Top-up initiated (Record: ${topUpResponse.data.record_id})`);
// Poll for status
let statusAttempts = 0;
const maxStatusAttempts = 10;
while (statusAttempts < maxStatusAttempts) {
await new Promise(resolve => setTimeout(resolve, 3000));
statusAttempts++;
const statusResponse = await queryTopUpOrder(uniqueOrderId);
const status = statusResponse.data.status;
if (status === 'SUCCESS') {
console.log(`✓ Top-up completed successfully on attempt ${attempt}`);
return {
success: true,
unique_order_id: uniqueOrderId,
attempts: attempt
};
} else if (status === 'FAILED') {
console.log(`✗ Top-up failed on attempt ${attempt}`);
if (attempt < maxRetries) {
console.log(`Retrying... (${maxRetries - attempt} attempts remaining)`);
await new Promise(resolve => setTimeout(resolve, 5000)); // Wait before retry
break; // Exit status check loop to retry
} else {
return {
success: false,
unique_order_id: uniqueOrderId,
attempts: attempt,
error: 'Max retries exceeded'
};
}
} else if (status === 'PROCESSING') {
console.log(`⏳ Status check ${statusAttempts}/${maxStatusAttempts}: Still processing...`);
}
}
} else {
console.log(`✗ Failed to initiate top-up: ${topUpResponse}`);
if (attempt < maxRetries) {
console.log(`Retrying... (${maxRetries - attempt} attempts remaining)`);
await new Promise(resolve => setTimeout(resolve, 5000));
}
}
} catch (error) {
console.error(`Error on attempt ${attempt}:`, error.message);
if (attempt < maxRetries) {
console.log(`Retrying... (${maxRetries - attempt} attempts remaining)`);
await new Promise(resolve => setTimeout(resolve, 5000));
} else {
return {
success: false,
attempts: attempt,
error: error.message
};
}
}
}
return {
success: false,
attempts: maxRetries,
error: 'Max retries exceeded'
};
}
// Example usage
const result = await topUpWithRetry(
'ci201910231405089666',
'5000.00',
'USD',
3
);
Top-Up Alert Notifications
Send notifications when top-ups complete or fail:
def top_up_with_notifications(budget_id, amount, currency, notification_email):
"""
Top up budget and send email notifications on completion
"""
import time
from datetime import datetime
unique_order_id = f"topup_{int(time.time())}"
start_time = datetime.now()
try:
# Initiate top-up
print(f"Initiating top-up: {currency} {amount}")
response = initiate_top_up(
budget_id=budget_id,
amount=str(amount),
currency=currency,
unique_order_id=unique_order_id
)
if response['code'] != 'SUCCESS':
send_notification(
to=notification_email,
subject='Top-Up Initiation Failed',
body=f'Failed to initiate top-up for budget {budget_id}\nError: {response}'
)
return {'success': False, 'error': 'Initiation failed'}
record_id = response['data']['record_id']
print(f"Top-up initiated (Record: {record_id})")
# Monitor status
max_attempts = 10
for attempt in range(max_attempts):
time.sleep(3)
status_response = query_top_up_order(unique_order_id)
status = status_response['data']['status']
if status == 'SUCCESS':
end_time = datetime.now()
duration = (end_time - start_time).total_seconds()
# Send success notification
send_notification(
to=notification_email,
subject='✓ Top-Up Completed Successfully',
body=f'''
Budget Top-Up Successful
Budget ID: {budget_id}
Amount: {currency} {amount}
Record ID: {record_id}
Order ID: {unique_order_id}
Completed in: {duration:.1f} seconds
Time: {end_time.strftime('%Y-%m-%d %H:%M:%S')}
'''
)
print(f"✓ Top-up completed successfully (Duration: {duration:.1f}s)")
return {
'success': True,
'record_id': record_id,
'unique_order_id': unique_order_id,
'duration_seconds': duration
}
elif status == 'FAILED':
# Send failure notification
send_notification(
to=notification_email,
subject='✗ Top-Up Failed',
body=f'''
Budget Top-Up Failed
Budget ID: {budget_id}
Amount: {currency} {amount}
Record ID: {record_id}
Order ID: {unique_order_id}
Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
Please check your funding source and retry.
'''
)
print(f"✗ Top-up failed")
return {
'success': False,
'unique_order_id': unique_order_id,
'error': 'Top-up failed'
}
print(f"⏳ Attempt {attempt + 1}/{max_attempts}: Status {status}")
# Timeout notification
send_notification(
to=notification_email,
subject='⚠ Top-Up Status Unknown',
body=f'''
Budget Top-Up Status Check Timed Out
Budget ID: {budget_id}
Amount: {currency} {amount}
Order ID: {unique_order_id}
Please check the status manually.
'''
)
return {
'success': None,
'unique_order_id': unique_order_id,
'error': 'Timeout'
}
except Exception as e:
send_notification(
to=notification_email,
subject='✗ Top-Up Error',
body=f'''
Budget Top-Up Error
Budget ID: {budget_id}
Amount: {currency} {amount}
Error: {str(e)}
Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
'''
)
raise
# Example usage
result = top_up_with_notifications(
budget_id='ci201910231405089666',
amount=10000.00,
currency='USD',
notification_email='finance@company.com'
)
Monthly Budget Replenishment
Automatically replenish budgets at the start of each month:
async function monthlyBudgetReplenishment(budgetConfigs) {
const results = {
timestamp: new Date().toISOString(),
month: new Date().toLocaleDateString('en-US', { month: 'long', year: 'numeric' }),
budgets: []
};
console.log(`\n=== Monthly Budget Replenishment ===`);
console.log(`Date: ${results.timestamp}`);
console.log(`Month: ${results.month}`);
console.log(`Processing ${budgetConfigs.length} budgets...\n`);
for (const config of budgetConfigs) {
const uniqueOrderId = `monthly_${Date.now()}_${config.budget_id.slice(-6)}`;
try {
console.log(`Replenishing: ${config.name}`);
// Top up to monthly allocation
const topUpResponse = await topUpBudgetAccount({
budget_id: config.budget_id,
top_up_amount: config.monthly_allocation.toFixed(2),
top_up_currency: config.currency,
unique_order_id: uniqueOrderId
});
if (topUpResponse.code === 'SUCCESS') {
console.log(` ✓ Initiated: ${config.currency} ${config.monthly_allocation}`);
// Wait for completion
await new Promise(resolve => setTimeout(resolve, 5000));
const statusResponse = await queryTopUpOrder(uniqueOrderId);
const status = statusResponse.data.status;
results.budgets.push({
budget_id: config.budget_id,
name: config.name,
amount: config.monthly_allocation,
currency: config.currency,
status: status,
unique_order_id: uniqueOrderId,
record_id: topUpResponse.data.record_id
});
const statusIcon = status === 'SUCCESS' ? '✓' : status === 'FAILED' ? '✗' : '⏳';
console.log(` ${statusIcon} Status: ${status}\n`);
} else {
console.log(` ✗ Failed to initiate\n`);
results.budgets.push({
budget_id: config.budget_id,
name: config.name,
status: 'FAILED_INITIATION',
error: topUpResponse
});
}
// Small delay between requests
await new Promise(resolve => setTimeout(resolve, 1000));
} catch (error) {
console.error(` ✗ Error: ${error.message}\n`);
results.budgets.push({
budget_id: config.budget_id,
name: config.name,
status: 'ERROR',
error: error.message
});
}
}
// Summary
const successful = results.budgets.filter(b => b.status === 'SUCCESS').length;
const failed = results.budgets.filter(b => b.status === 'FAILED' || b.status === 'FAILED_INITIATION').length;
const processing = results.budgets.filter(b => b.status === 'PROCESSING').length;
console.log(`\n=== Replenishment Summary ===`);
console.log(`Total: ${results.budgets.length}`);
console.log(`✓ Successful: ${successful}`);
console.log(`✗ Failed: ${failed}`);
console.log(`⏳ Processing: ${processing}`);
return results;
}
// Example usage with cron job (runs on 1st of each month at 00:00)
const budgetConfigs = [
{
budget_id: 'ci201910231405089666',
name: 'Marketing Department',
monthly_allocation: 50000.00,
currency: 'USD'
},
{
budget_id: 'ci201910231405089667',
name: 'Operations',
monthly_allocation: 75000.00,
currency: 'USD'
},
{
budget_id: 'ci201910231405089668',
name: 'Travel & Entertainment',
monthly_allocation: 25000.00,
currency: 'USD'
}
];
// Schedule for 1st of every month at midnight
// In production, use a proper scheduler like node-cron
const results = await monthlyBudgetReplenishment(budgetConfigs);
Best Practices
- Generate Unique IDs: Use timestamps + UUIDs to ensure unique
unique_order_idvalues - Always Verify Status: Don't assume top-up succeeded - always check status with Query Top-Up Order endpoint
- Implement Polling: Poll status periodically (every 3-5 seconds) until SUCCESS or FAILED
- Set Timeouts: Define maximum polling attempts to avoid infinite loops
- Amount Validation: Validate amounts are positive and properly formatted before submission
- Currency Matching: Ensure top-up currency matches budget account currency
- Idempotency: Save
unique_order_idto safely retry on network failures - Balance Monitoring: Monitor budget balances and set up low-balance alerts
- Audit Logging: Log all top-up attempts and results for financial reconciliation
Top-Up Workflow
Standard Process
- Check Current Balance: Query budget balance to determine top-up needs
- Generate Unique ID: Create unique
unique_order_idfor this top-up - Initiate Top-Up: Call this endpoint with budget details and amount
- Store Reference: Save
record_idandunique_order_idfor tracking - Poll Status: Periodically query status using Query Top-Up Order endpoint
- Wait for Completion: Continue polling until status is SUCCESS or FAILED
- Verify Balance: Confirm budget balance increased by expected amount
- Update Records: Update your financial systems with completed top-up
Related Endpoints
- Query Top-Up Order - Check the status of a top-up using
unique_order_id - Query Budget Balance - View current budget balance before and after top-ups
- Create Budget Account - Create budget accounts to receive top-ups
- Query Budget Details - Get detailed information about budget accounts
Troubleshooting
Duplicate unique_order_id Error (400)
- Cause: The
unique_order_idhas already been used for a previous top-up - Solution:
- Generate a new unique ID for each top-up request
- If retrying, query the existing order status first
- Use timestamp + random component to ensure uniqueness
Invalid Amount Format (400)
- Cause: Amount is not formatted correctly
- Solution:
- Provide amount as a string (e.g., "10.45" not 10.45)
- Use maximum 2 decimal places
- Ensure amount is positive
Budget Not Found (404)
- Cause: The specified
budget_iddoesn't exist - Solution:
- Verify the budget ID is correct
- Ensure the budget account was created successfully
- Check if budget was deleted
Top-Up Stuck in PROCESSING
- Cause: Payment processing is delayed or failed
- Solution:
- Continue polling for up to 5-10 minutes
- Verify your funding source is valid
- Contact support if status doesn't update
Currency Mismatch
- Cause: Top-up currency doesn't match budget currency
- Solution:
- Query budget details to confirm currency
- Use matching currency for top-up
- Contact support if currency conversion is needed
Security Considerations
- Unique Order IDs: Treat as sensitive - they prevent duplicate charges
- Amount Validation: Always validate amounts on your side before submission
- Audit Trail: Maintain comprehensive logs of all top-up operations
- Access Control: Restrict top-up permissions to authorized personnel only
- Balance Monitoring: Set up alerts for unusual top-up patterns
- Idempotency: Leverage idempotency to safely handle retries and network failures