Overview
This guide shows you exactly what to change when migrating from OneClickDz Flexy API v2 to v3. Each example shows your current v2 code, lists what to change, and shows the v3 result.
Available in multiple languages : JavaScript/Node.js, PHP, and Python examples included.
API v2 Deprecation : API v2 will be deprecated on October 30, 2026 .
Please migrate before this date to avoid service interruption.
What’s New in v3
Standardized Responses All responses wrapped in {(success, data, meta, requestId)} structure
Better Error Handling Structured errors with codes, messages, and details for easier debugging
Enhanced Debugging Every response includes timestamps and unique request IDs
Cleaner Endpoints Logical grouping: /mobile/*, /internet/*, /gift-cards/*, /account/*
Separate API Keys Generate dedicated sandbox keys for testing while keeping your production key
IP Whitelisting Add IP restrictions directly from the settings page for enhanced security
Quick Migration Checklist
Generate Sandbox API Key (Optional)
Create a sandbox API key from your dashboard settings page for testing v3 before migrating production
Configure IP Whitelist (Optional)
Add IP restrictions for enhanced security directly from settings
Update Authentication Header
Change from authorization to X-Access-Token in all API requests
Update Response Handling
Access data through result.data instead of directly from the response
Update Error Handling
Check result.success boolean and handle structured result.error object
Update Endpoint Paths
Map v2 paths to new v3 paths (see complete reference table below)
Update Pagination Logic
Access pagination info from result.data.pagination instead of root level
Choose Your Migration Strategy
Good News : Both v2 and v3 APIs work simultaneously until v2 deprecation.
Your current production API key works with both v2 and v3 endpoints, giving you flexibility in how you migrate.
Base URL Change : v3 uses a new base URL: - v2:
https://flexy-api.oneclickdz.com/v2 - v3: https://api.oneclickdz.com/v3
New Security Features : v3 introduces separate sandbox API key management for testing, plus IP whitelisting directly from your dashboard settings page. Your production key continues to work with both v2 and v3.
Choose the approach that fits your needs:
🆕 Fresh Start Best for : Complete rebuild or new projects
Generate sandbox key for testing from settings
Configure IP whitelist for production key
Test everything in sandbox with v3
Use production key when ready to go live
✅ Clean slate, no legacy code
✅ Enhanced security with IP restrictions
🔄 Gradual Migration Best for : Existing production systems
Keep using current production key for v2 endpoints
Generate sandbox key to test v3 endpoints safely
Migrate endpoints one by one to v3
Run v2 and v3 side-by-side with same production key
✅ Zero downtime, less risk
✅ Migrate at your own pace
Recommended : Most teams choose gradual migration to minimize risk. You can update critical endpoints first while keeping others on v2.
Smart Migration: Start with Gift Cards
Pro Tip : If you’re interested in gift cards, integrate them on v3 first! You don’t need to migrate your existing mobile and internet top-ups to start using gift cards on v3. This gives you a low-risk way to test v3 while keeping your critical operations on v2. Use your sandbox key to test, then switch to production when ready.
Suggested Migration Order :
Phase 1 : Integrate gift cards on v3 (if applicable)
Generate sandbox key for testing
Use /v3/gift-cards/* endpoints in sandbox
Test thoroughly, then use production key
Keep mobile/internet on v2
Phase 2 : Migrate account & validation endpoints
/v3/validate
/v3/account/balance
/v3/account/transactions
Phase 3 : Migrate mobile top-ups gradually
Test with low-volume operations first
Monitor for issues
Scale up migration
Phase 4 : Migrate internet top-ups
Complete before deprecation deadline
This approach lets you migrate at your own pace until the deprecation date while taking advantage of new v3 features.
The 3 Main Changes
What to change : Rename the header from authorization to X-Access-Token
New Key Management : v3 allows you to generate a separate sandbox API key for testing from your dashboard settings. You can also add IP whitelist restrictions for enhanced security. Your existing production API key works with both v2 and v3 endpoints.
// Your current v2 code:
fetch ( "https://flexy-api.oneclickdz.com/v2/plans/listAll" , {
headers: {
authorization: "YOUR_API_KEY" ,
},
});
// Your current v2 code:
$ch = curl_init ( 'https://flexy-api.oneclickdz.com/v2/plans/listAll' );
curl_setopt ( $ch , CURLOPT_HTTPHEADER , [
'authorization: YOUR_API_KEY'
]);
curl_setopt ( $ch , CURLOPT_RETURNTRANSFER , true );
$response = curl_exec ( $ch );
# Your current v2 code:
import requests
response = requests.get(
'https://flexy-api.oneclickdz.com/v2/plans/listAll' ,
headers = { 'authorization' : 'YOUR_API_KEY' }
)
Changes needed :
❌ Remove: authorization: "YOUR_API_KEY"
✅ Add: X-Access-Token: "YOUR_API_KEY"
✅ Update: Base URL to https://api.oneclickdz.com/v3
The authentication mechanism remains the same - only the header name changes. Your existing API key works with both v2 and v3.
// Updated v3 code:
fetch ( "https://api.oneclickdz.com/v3/mobile/plans" , {
headers: {
"X-Access-Token" : "YOUR_API_KEY" ,
},
});
// Updated v3 code:
$ch = curl_init ( 'https://api.oneclickdz.com/v3/mobile/plans' );
curl_setopt ( $ch , CURLOPT_HTTPHEADER , [
'X-Access-Token: YOUR_API_KEY'
]);
curl_setopt ( $ch , CURLOPT_RETURNTRANSFER , true );
$response = curl_exec ( $ch );
# Updated v3 code:
import requests
response = requests.get(
'https://api.oneclickdz.com/v3/mobile/plans' ,
headers = { 'X-Access-Token' : 'YOUR_API_KEY' }
)
2. Response Structure
What to change : All responses are now wrapped in a standard structure
// Your current v2 code:
const response = await fetch ( "https://flexy-api.oneclickdz.com/v2/account/balance" , {
headers: { authorization: API_KEY },
});
const data = await response . json ();
console . log ( "Balance:" , data . balance ); // Direct access
// Your current v2 code:
$ch = curl_init ( 'https://flexy-api.oneclickdz.com/v2/account/balance' );
curl_setopt ( $ch , CURLOPT_HTTPHEADER , [ 'authorization: ' . $apiKey ]);
curl_setopt ( $ch , CURLOPT_RETURNTRANSFER , true );
$response = curl_exec ( $ch );
$data = json_decode ( $response , true );
echo "Balance: " . $data [ 'balance' ]; // Direct access
# Your current v2 code:
response = requests.get(
'https://flexy-api.oneclickdz.com/v2/account/balance' ,
headers = { 'authorization' : API_KEY }
)
data = response.json()
print ( f "Balance: { data[ 'balance' ] } " ) # Direct access
Changes needed :
❌ Remove: Direct access to data.balance
✅ Add: Check result.success first
✅ Add: Access through result.data.balance
✅ Bonus: Use result.requestId for debugging
✅ Bonus: Use result.meta.timestamp for timing info
Breaking Change : In v3, you must always check the success boolean before
accessing data. Direct field access will cause errors if the request failed.
// Updated v3 code:
const response = await fetch ( "https://api.oneclickdz.com/v3/account/balance" , {
headers: { "X-Access-Token" : API_KEY },
});
const result = await response . json ();
if ( result . success ) {
console . log ( "Balance:" , result . data . balance );
console . log ( "Request ID:" , result . requestId ); // For debugging
console . log ( "Timestamp:" , result . meta . timestamp ); // Response time
} else {
console . error ( "Error:" , result . error . message );
}
// Updated v3 code:
$ch = curl_init ( 'https://api.oneclickdz.com/v3/account/balance' );
curl_setopt ( $ch , CURLOPT_HTTPHEADER , [ 'X-Access-Token: ' . $apiKey ]);
curl_setopt ( $ch , CURLOPT_RETURNTRANSFER , true );
$response = curl_exec ( $ch );
$result = json_decode ( $response , true );
if ( $result [ 'success' ]) {
echo "Balance: " . $result [ 'data' ][ 'balance' ] . " \n " ;
echo "Request ID: " . $result [ 'requestId' ] . " \n " ;
echo "Timestamp: " . $result [ 'meta' ][ 'timestamp' ] . " \n " ;
} else {
error_log ( "Error: " . $result [ 'error' ][ 'message' ]);
}
# Updated v3 code:
response = requests.get(
'https://api.oneclickdz.com/v3/account/balance' ,
headers = { 'X-Access-Token' : API_KEY }
)
result = response.json()
if result[ 'success' ]:
print ( f "Balance: { result[ 'data' ][ 'balance' ] } " )
print ( f "Request ID: { result[ 'requestId' ] } " ) # For debugging
print ( f "Timestamp: { result[ 'meta' ][ 'timestamp' ] } " )
else :
print ( f "Error: { result[ 'error' ][ 'message' ] } " )
3. Error Handling
What to change : Errors are now structured objects with codes
// Your current v2 code:
const response = await fetch ( "https://flexy-api.oneclickdz.com/v2/topup/sendTopup" , {
method: "POST" ,
headers: {
"Content-Type" : "application/json" ,
authorization: API_KEY ,
},
body: JSON . stringify ( data ),
});
const result = await response . json ();
if ( result . error ) {
console . error ( "Error:" , result . message ); // Simple string
}
// Your current v2 code:
$ch = curl_init ( 'https://flexy-api.oneclickdz.com/v2/topup/sendTopup' );
curl_setopt ( $ch , CURLOPT_POST , true );
curl_setopt ( $ch , CURLOPT_POSTFIELDS , json_encode ( $data ));
curl_setopt ( $ch , CURLOPT_HTTPHEADER , [
'Content-Type: application/json' ,
'authorization: ' . $apiKey
]);
curl_setopt ( $ch , CURLOPT_RETURNTRANSFER , true );
$response = curl_exec ( $ch );
$result = json_decode ( $response , true );
if ( isset ( $result [ 'error' ])) {
error_log ( "Error: " . $result [ 'message' ]); // Simple string
}
# Your current v2 code:
response = requests.post(
'https://flexy-api.oneclickdz.com/v2/topup/sendTopup' ,
headers = {
'Content-Type' : 'application/json' ,
'authorization' : API_KEY
},
json = data
)
result = response.json()
if 'error' in result:
print ( f "Error: { result[ 'message' ] } " ) # Simple string
Changes needed :
❌ Remove: Checking result.error string
✅ Add: Check result.success === false
✅ Add: Access result.error.code and result.error.message
✅ Add: Handle different error codes with switch/if statements
✅ Add: Log result.requestId for support tickets
Pro Tip : Always save the requestId when logging errors. Our support team
needs this to trace issues in our system.
// Updated v3 code:
const response = await fetch ( "https://api.oneclickdz.com/v3/mobile/send" , {
method: "POST" ,
headers: {
"Content-Type" : "application/json" ,
"X-Access-Token" : API_KEY ,
},
body: JSON . stringify ( data ),
});
const result = await response . json ();
if ( ! result . success ) {
console . error ( `Error [ ${ result . error . code } ]: ${ result . error . message } ` );
console . log ( "Request ID:" , result . requestId );
// Handle specific errors
switch ( result . error . code ) {
case "INSUFFICIENT_BALANCE" :
alert ( "Please top up your account" );
break ;
case "ERR_PHONE" :
alert ( `Invalid phone number: ${ result . error . message } ` );
if ( result . error . details ?. pattern ) {
console . log ( "Expected format:" , result . error . details . pattern );
}
break ;
case "DUPLICATED_REF" :
alert ( "This order reference was already used" );
break ;
default :
alert ( "An error occurred. Please try again." );
}
}
// Updated v3 code:
$ch = curl_init ( 'https://api.oneclickdz.com/v3/mobile/send' );
curl_setopt ( $ch , CURLOPT_POST , true );
curl_setopt ( $ch , CURLOPT_POSTFIELDS , json_encode ( $data ));
curl_setopt ( $ch , CURLOPT_HTTPHEADER , [
'Content-Type: application/json' ,
'X-Access-Token: ' . $apiKey
]);
curl_setopt ( $ch , CURLOPT_RETURNTRANSFER , true );
$response = curl_exec ( $ch );
$result = json_decode ( $response , true );
if ( ! $result [ 'success' ]) {
$errorCode = $result [ 'error' ][ 'code' ];
$errorMessage = $result [ 'error' ][ 'message' ];
error_log ( "Error [{ $errorCode }]: { $errorMessage }" );
error_log ( "Request ID: " . $result [ 'requestId' ]);
// Handle specific errors
switch ( $errorCode ) {
case 'INSUFFICIENT_BALANCE' :
echo "Please top up your account \n " ;
break ;
case 'ERR_PHONE' :
echo "Invalid phone number: { $errorMessage } \n " ;
if ( isset ( $result [ 'error' ][ 'details' ][ 'pattern' ])) {
echo "Expected format: " . $result [ 'error' ][ 'details' ][ 'pattern' ] . " \n " ;
}
break ;
case 'DUPLICATED_REF' :
echo "This order reference was already used \n " ;
break ;
default :
echo "An error occurred. Please try again \n " ;
}
}
# Updated v3 code:
response = requests.post(
'https://api.oneclickdz.com/v3/mobile/send' ,
headers = {
'Content-Type' : 'application/json' ,
'X-Access-Token' : API_KEY
},
json = data
)
result = response.json()
if not result[ 'success' ]:
error_code = result[ 'error' ][ 'code' ]
error_message = result[ 'error' ][ 'message' ]
print ( f "Error [ { error_code } ]: { error_message } " )
print ( f "Request ID: { result[ 'requestId' ] } " )
# Handle specific errors
if error_code == 'INSUFFICIENT_BALANCE' :
print ( "Please top up your account" )
elif error_code == 'ERR_PHONE' :
print ( f "Invalid phone number: { error_message } " )
if 'pattern' in result[ 'error' ].get( 'details' , {}):
print ( f "Expected format: { result[ 'error' ][ 'details' ][ 'pattern' ] } " )
elif error_code == 'DUPLICATED_REF' :
print ( "This order reference was already used" )
else :
print ( "An error occurred. Please try again." )
Step-by-Step Migration Examples
Example 1: Get Mobile Plans
Step 1 - Your current v2 code:
const response = await fetch ( "https://flexy-api.oneclickdz.com/v2/plans/listAll" , {
headers: {
authorization: API_KEY ,
},
});
const data = await response . json ();
const dynamicPlans = data . dymanicPlans ; // Note: typo in v2
const fixedPlans = data . fixedPlans ;
$ch = curl_init ( 'https://flexy-api.oneclickdz.com/v2/plans/listAll' );
curl_setopt ( $ch , CURLOPT_HTTPHEADER , [ 'authorization: ' . $apiKey ]);
curl_setopt ( $ch , CURLOPT_RETURNTRANSFER , true );
$response = curl_exec ( $ch );
$data = json_decode ( $response , true );
$dynamicPlans = $data [ 'dymanicPlans' ]; // Note: typo in v2
$fixedPlans = $data [ 'fixedPlans' ];
response = requests.get(
'https://flexy-api.oneclickdz.com/v2/plans/listAll' ,
headers = { 'authorization' : API_KEY }
)
data = response.json()
dynamic_plans = data[ 'dymanicPlans' ] # Note: typo in v2
fixed_plans = data[ 'fixedPlans' ]
Step 2 - What to change:
Change base URL: https://flexy-api.oneclickdz.com → https://api.oneclickdz.com
Change endpoint: /v2/plans/listAll → /v3/mobile/plans
Change header: authorization → X-Access-Token
Add success check: if (result.success)
Access through data: data.dymanicPlans → result.data.dynamicPlans (typo fixed!)
MUST Fix : v3 fixes the typo in “dymanicPlans” to “dynamicPlans”. Update
your code to use the correct spelling.
Step 3 - Your new v3 code:
const response = await fetch ( "https://api.oneclickdz.com/v3/mobile/plans" , {
headers: {
"X-Access-Token" : API_KEY ,
},
});
const result = await response . json ();
if ( result . success ) {
const dynamicPlans = result . data . dynamicPlans ; // Typo fixed!
const fixedPlans = result . data . fixedPlans ;
}
$ch = curl_init ( 'https://api.oneclickdz.com/v3/mobile/plans' );
curl_setopt ( $ch , CURLOPT_HTTPHEADER , [ 'X-Access-Token: ' . $apiKey ]);
curl_setopt ( $ch , CURLOPT_RETURNTRANSFER , true );
$response = curl_exec ( $ch );
$result = json_decode ( $response , true );
if ( $result [ 'success' ]) {
$dynamicPlans = $result [ 'data' ][ 'dynamicPlans' ]; // Typo fixed!
$fixedPlans = $result [ 'data' ][ 'fixedPlans' ];
}
response = requests.get(
'https://api.oneclickdz.com/v3/mobile/plans' ,
headers = { 'X-Access-Token' : API_KEY }
)
result = response.json()
if result[ 'success' ]:
dynamic_plans = result[ 'data' ][ 'dynamicPlans' ] # Typo fixed!
fixed_plans = result[ 'data' ][ 'fixedPlans' ]
Example 2: Send Mobile Top-Up
Step 1 - Your current v2 code:
const response = await fetch ( "https://flexy-api.oneclickdz.com/v2/topup/sendTopup" , {
method: "POST" ,
headers: {
"Content-Type" : "application/json" ,
authorization: API_KEY ,
},
body: JSON . stringify ({
plan_code: "PREPAID_DJEZZY" ,
MSSIDN: "0778037340" ,
amount: 500 ,
ref: "order-123" ,
}),
});
const data = await response . json ();
console . log ( "Topup ID:" , data . topupId );
console . log ( "New Balance:" , data . newBalance );
$data = [
'plan_code' => 'PREPAID_DJEZZY' ,
'MSSIDN' => '0778037340' ,
'amount' => 500 ,
'ref' => 'order-123'
];
$ch = curl_init ( 'https://flexy-api.oneclickdz.com/v2/topup/sendTopup' );
curl_setopt ( $ch , CURLOPT_POST , true );
curl_setopt ( $ch , CURLOPT_POSTFIELDS , json_encode ( $data ));
curl_setopt ( $ch , CURLOPT_HTTPHEADER , [
'Content-Type: application/json' ,
'authorization: ' . $apiKey
]);
curl_setopt ( $ch , CURLOPT_RETURNTRANSFER , true );
$response = curl_exec ( $ch );
$result = json_decode ( $response , true );
echo "Topup ID: " . $result [ 'topupId' ] . " \n " ;
echo "New Balance: " . $result [ 'newBalance' ] . " \n " ;
data = {
'plan_code' : 'PREPAID_DJEZZY' ,
'MSSIDN' : '0778037340' ,
'amount' : 500 ,
'ref' : 'order-123'
}
response = requests.post(
'https://flexy-api.oneclickdz.com/v2/topup/sendTopup' ,
headers = {
'Content-Type' : 'application/json' ,
'authorization' : API_KEY
},
json = data
)
result = response.json()
print ( f "Topup ID: { result[ 'topupId' ] } " )
print ( f "New Balance: { result[ 'newBalance' ] } " )
Step 2 - What to change:
Change base URL: https://flexy-api.oneclickdz.com → https://api.oneclickdz.com
Change endpoint: /v2/topup/sendTopup → /v3/mobile/send
Change header: authorization → X-Access-Token
Add success check: if (result.success)
Access through data: data.topupId → result.data.topupId
Add comprehensive error handling
Important : Always handle errors when sending top-ups. Network issues,
insufficient balance, or invalid data can cause failures. Save the requestId
for tracking.
Step 3 - Your new v3 code:
const response = await fetch ( "https://api.oneclickdz.com/v3/mobile/send" , {
method: "POST" ,
headers: {
"Content-Type" : "application/json" ,
"X-Access-Token" : API_KEY ,
},
body: JSON . stringify ({
plan_code: "PREPAID_DJEZZY" ,
MSSIDN: "0778037340" ,
amount: 500 ,
ref: "order-123" ,
}),
});
const result = await response . json ();
if ( result . success ) {
console . log ( "✅ Top-up sent successfully!" );
console . log ( "Topup ID:" , result . data . topupId );
console . log ( "New Balance:" , result . data . newBalance );
console . log ( "Request ID:" , result . requestId );
} else {
console . error ( `❌ Error [ ${ result . error . code } ]: ${ result . error . message } ` );
console . error ( "Request ID:" , result . requestId );
}
// ...existing code...
$ch = curl_init ( 'https://api.oneclickdz.com/v3/mobile/send' );
curl_setopt ( $ch , CURLOPT_POST , true );
curl_setopt ( $ch , CURLOPT_POSTFIELDS , json_encode ( $data ));
curl_setopt ( $ch , CURLOPT_HTTPHEADER , [
'Content-Type: application/json' ,
'X-Access-Token: ' . $apiKey
]);
curl_setopt ( $ch , CURLOPT_RETURNTRANSFER , true );
$response = curl_exec ( $ch );
$result = json_decode ( $response , true );
if ( $result [ 'success' ]) {
echo "✅ Top-up sent successfully! \n " ;
echo "Topup ID: " . $result [ 'data' ][ 'topupId' ] . " \n " ;
echo "New Balance: " . $result [ 'data' ][ 'newBalance' ] . " \n " ;
echo "Request ID: " . $result [ 'requestId' ] . " \n " ;
} else {
error_log ( "❌ Error [{ $result ['error']['code']}]: { $result ['error']['message']}" );
error_log ( "Request ID: " . $result [ 'requestId' ]);
}
# ...existing code...
response = requests.post(
'https://api.oneclickdz.com/v3/mobile/send' ,
headers = {
'Content-Type' : 'application/json' ,
'X-Access-Token' : API_KEY
},
json = data
)
result = response.json()
if result[ 'success' ]:
print ( "✅ Top-up sent successfully!" )
print ( f "Topup ID: { result[ 'data' ][ 'topupId' ] } " )
print ( f "New Balance: { result[ 'data' ][ 'newBalance' ] } " )
print ( f "Request ID: { result[ 'requestId' ] } " )
else :
print ( f "❌ Error [ { result[ 'error' ][ 'code' ] } ]: { result[ 'error' ][ 'message' ] } " )
print ( f "Request ID: { result[ 'requestId' ] } " )
Example 3: Check Top-Up Status
Step 1 - Your current v2 code:
const response = await fetch (
"https://flexy-api.oneclickdz.com/v2/topup/checkStatus/REF/order-123" ,
{
headers: { authorization: API_KEY },
}
);
const data = await response . json ();
console . log ( "Status:" , data . topup . status );
console . log ( "Phone:" , data . topup . MSSIDN );
$ch = curl_init ( 'https://flexy-api.oneclickdz.com/v2/topup/checkStatus/REF/order-123' );
curl_setopt ( $ch , CURLOPT_HTTPHEADER , [ 'authorization: ' . $apiKey ]);
curl_setopt ( $ch , CURLOPT_RETURNTRANSFER , true );
$response = curl_exec ( $ch );
$data = json_decode ( $response , true );
echo "Status: " . $data [ 'topup' ][ 'status' ] . " \n " ;
echo "Phone: " . $data [ 'topup' ][ 'MSSIDN' ] . " \n " ;
response = requests.get(
'https://flexy-api.oneclickdz.com/v2/topup/checkStatus/REF/order-123' ,
headers = { 'authorization' : API_KEY }
)
data = response.json()
print ( f "Status: { data[ 'topup' ][ 'status' ] } " )
print ( f "Phone: { data[ 'topup' ][ 'MSSIDN' ] } " )
Step 2 - What to change:
Change base URL: https://flexy-api.oneclickdz.com → https://api.oneclickdz.com
Change endpoint: /v2/topup/checkStatus/REF/:ref → /v3/mobile/check-ref/:ref
Change header: authorization → X-Access-Token
Add success check: if (result.success)
Access directly: data.topup.status → result.data.status (no more nested topup object)
Handle new refund information fields
New Feature : v3 includes detailed refund information with suggested
alternative offers when a top-up is refunded.
Step 3 - Your new v3 code:
const response = await fetch (
"https://api.oneclickdz.com/v3/mobile/check-ref/order-123" ,
{
headers: { "X-Access-Token" : API_KEY },
}
);
const result = await response . json ();
if ( result . success ) {
console . log ( "Status:" , result . data . status );
console . log ( "Phone:" , result . data . MSSIDN );
// New in v3: Enhanced refund information
if ( result . data . status === "REFUNDED" ) {
console . log ( "❌ Top-up was refunded" );
console . log ( "Reason:" , result . data . refund_message );
if ( result . data . suggested_offers ) {
console . log ( "💡 Try these offers instead:" );
console . log ( result . data . suggested_offers );
}
} else if ( result . data . status === "FULFILLED" ) {
console . log ( "✅ Top-up completed successfully" );
}
}
$ch = curl_init ( 'https://api.oneclickdz.com/v3/mobile/check-ref/order-123' );
curl_setopt ( $ch , CURLOPT_HTTPHEADER , [ 'X-Access-Token: ' . $apiKey ]);
curl_setopt ( $ch , CURLOPT_RETURNTRANSFER , true );
$response = curl_exec ( $ch );
$result = json_decode ( $response , true );
if ( $result [ 'success' ]) {
echo "Status: " . $result [ 'data' ][ 'status' ] . " \n " ;
echo "Phone: " . $result [ 'data' ][ 'MSSIDN' ] . " \n " ;
// New in v3: Enhanced refund information
if ( $result [ 'data' ][ 'status' ] === 'REFUNDED' ) {
echo "❌ Top-up was refunded \n " ;
echo "Reason: " . $result [ 'data' ][ 'refund_message' ] . " \n " ;
if ( isset ( $result [ 'data' ][ 'suggested_offers' ])) {
echo "💡 Try these offers instead: \n " ;
print_r ( $result [ 'data' ][ 'suggested_offers' ]);
}
} elseif ( $result [ 'data' ][ 'status' ] === 'FULFILLED' ) {
echo "✅ Top-up completed successfully \n " ;
}
}
response = requests.get(
'https://api.oneclickdz.com/v3/mobile/check-ref/order-123' ,
headers = { 'X-Access-Token' : API_KEY }
)
result = response.json()
if result[ 'success' ]:
print ( f "Status: { result[ 'data' ][ 'status' ] } " )
print ( f "Phone: { result[ 'data' ][ 'MSSIDN' ] } " )
# New in v3: Enhanced refund information
if result[ 'data' ][ 'status' ] == 'REFUNDED' :
print ( "❌ Top-up was refunded" )
print ( f "Reason: { result[ 'data' ][ 'refund_message' ] } " )
if 'suggested_offers' in result[ 'data' ]:
print ( "💡 Try these offers instead:" )
print (result[ 'data' ][ 'suggested_offers' ])
elif result[ 'data' ][ 'status' ] == 'FULFILLED' :
print ( "✅ Top-up completed successfully" )
Step 1 - Your current v2 code:
const response = await fetch (
"https://flexy-api.oneclickdz.com/v2/account/transactions?page=1&pageSize=20" ,
{
headers: { authorization: API_KEY },
}
);
const data = await response . json ();
console . log ( "Transactions:" , data . transactions );
console . log ( "Total:" , data . totalResults );
console . log ( "Page:" , data . currentPage );
console . log ( "Pages:" , data . totalPages );
$ch = curl_init ( 'https://flexy-api.oneclickdz.com/v2/account/transactions?page=1&pageSize=20' );
curl_setopt ( $ch , CURLOPT_HTTPHEADER , [ 'authorization: ' . $apiKey ]);
curl_setopt ( $ch , CURLOPT_RETURNTRANSFER , true );
$response = curl_exec ( $ch );
$data = json_decode ( $response , true );
echo "Transactions: " . count ( $data [ 'transactions' ]) . " \n " ;
echo "Total: " . $data [ 'totalResults' ] . " \n " ;
echo "Page: " . $data [ 'currentPage' ] . " \n " ;
echo "Pages: " . $data [ 'totalPages' ] . " \n " ;
response = requests.get(
'https://flexy-api.oneclickdz.com/v2/account/transactions?page=1&pageSize=20' ,
headers = { 'authorization' : API_KEY }
)
data = response.json()
print ( f "Transactions: { len (data[ 'transactions' ]) } " )
print ( f "Total: { data[ 'totalResults' ] } " )
print ( f "Page: { data[ 'currentPage' ] } " )
print ( f "Pages: { data[ 'totalPages' ] } " )
Step 2 - What to change:
Change base URL: https://flexy-api.oneclickdz.com → https://api.oneclickdz.com
Change version: /v2/account/transactions → /v3/account/transactions
Change header: authorization → X-Access-Token
Add success check: if (result.success)
Access items: data.transactions → result.data.items
Access pagination: Root level fields → result.data.pagination object
Breaking Change : Pagination structure has moved from root level to
data.pagination. Update all list/pagination logic.
Step 3 - Your new v3 code:
const response = await fetch (
"https://api.oneclickdz.com/v3/account/transactions?page=1&pageSize=20" ,
{
headers: { "X-Access-Token" : API_KEY },
}
);
const result = await response . json ();
if ( result . success ) {
console . log ( "Transactions:" , result . data . items );
console . log ( "Total:" , result . data . pagination . totalResults );
console . log ( "Page:" , result . data . pagination . page );
console . log ( "Pages:" , result . data . pagination . totalPages );
console . log ( "Page Size:" , result . data . pagination . pageSize );
}
$ch = curl_init ( 'https://api.oneclickdz.com/v3/account/transactions?page=1&pageSize=20' );
curl_setopt ( $ch , CURLOPT_HTTPHEADER , [ 'X-Access-Token: ' . $apiKey ]);
curl_setopt ( $ch , CURLOPT_RETURNTRANSFER , true );
$response = curl_exec ( $ch );
$result = json_decode ( $response , true );
if ( $result [ 'success' ]) {
echo "Transactions: " . count ( $result [ 'data' ][ 'items' ]) . " \n " ;
echo "Total: " . $result [ 'data' ][ 'pagination' ][ 'totalResults' ] . " \n " ;
echo "Page: " . $result [ 'data' ][ 'pagination' ][ 'page' ] . " \n " ;
echo "Pages: " . $result [ 'data' ][ 'pagination' ][ 'totalPages' ] . " \n " ;
echo "Page Size: " . $result [ 'data' ][ 'pagination' ][ 'pageSize' ] . " \n " ;
}
response = requests.get(
'https://api.oneclickdz.com/v3/account/transactions?page=1&pageSize=20' ,
headers = { 'X-Access-Token' : API_KEY }
)
result = response.json()
if result[ 'success' ]:
print ( f "Transactions: { len (result[ 'data' ][ 'items' ]) } " )
print ( f "Total: { result[ 'data' ][ 'pagination' ][ 'totalResults' ] } " )
print ( f "Page: { result[ 'data' ][ 'pagination' ][ 'page' ] } " )
print ( f "Pages: { result[ 'data' ][ 'pagination' ][ 'totalPages' ] } " )
print ( f "Page Size: { result[ 'data' ][ 'pagination' ][ 'pageSize' ] } " )
Example 5: Check Internet Products
Step 1 - Your current v2 code:
const response = await fetch (
"https://flexy-api.oneclickdz.com/v2/internet/checkCards/ADSL" ,
{
headers: { authorization: API_KEY },
}
);
const cards = await response . json (); // Direct array
cards . forEach (( card ) => {
console . log ( ` ${ card . value } DA - Available: ${ card . available } ` );
});
$ch = curl_init ( 'https://flexy-api.oneclickdz.com/v2/internet/checkCards/ADSL' );
curl_setopt ( $ch , CURLOPT_HTTPHEADER , [ 'authorization: ' . $apiKey ]);
curl_setopt ( $ch , CURLOPT_RETURNTRANSFER , true );
$response = curl_exec ( $ch );
$cards = json_decode ( $response , true ); // Direct array
foreach ( $cards as $card ) {
echo $card [ 'value' ] . " DA - Available: " . $card [ 'available' ] . " \n " ;
}
response = requests.get(
'https://flexy-api.oneclickdz.com/v2/internet/checkCards/ADSL' ,
headers = { 'authorization' : API_KEY }
)
cards = response.json() # Direct array
for card in cards:
print ( f " { card[ 'value' ] } DA - Available: { card[ 'available' ] } " )
Step 2 - What to change:
Change base URL: https://flexy-api.oneclickdz.com → https://api.oneclickdz.com
Change endpoint: /v2/internet/checkCards/:type → /v3/internet/products?type=:type
Change from path param to query param: /ADSL → ?type=ADSL
Change header: authorization → X-Access-Token
Add success check: if (result.success)
Access array: Direct array → result.data.products
API Design : v3 uses query parameters instead of path parameters for type
selection, making it easier to add filters in the future.
Step 3 - Your new v3 code:
const response = await fetch (
"https://api.oneclickdz.com/v3/internet/products?type=ADSL" ,
{
headers: { "X-Access-Token" : API_KEY },
}
);
const result = await response . json ();
if ( result . success ) {
result . data . products . forEach (( card ) => {
console . log ( ` ${ card . value } DA - Available: ${ card . available } ` );
});
}
$ch = curl_init ( 'https://api.oneclickdz.com/v3/internet/products?type=ADSL' );
curl_setopt ( $ch , CURLOPT_HTTPHEADER , [ 'X-Access-Token: ' . $apiKey ]);
curl_setopt ( $ch , CURLOPT_RETURNTRANSFER , true );
$response = curl_exec ( $ch );
$result = json_decode ( $response , true );
if ( $result [ 'success' ]) {
foreach ( $result [ 'data' ][ 'products' ] as $card ) {
echo $card [ 'value' ] . " DA - Available: " . $card [ 'available' ] . " \n " ;
}
}
response = requests.get(
'https://api.oneclickdz.com/v3/internet/products?type=ADSL' ,
headers = { 'X-Access-Token' : API_KEY }
)
result = response.json()
if result[ 'success' ]:
for card in result[ 'data' ][ 'products' ]:
print ( f " { card[ 'value' ] } DA - Available: { card[ 'available' ] } " )
Migrate Your API Client
Here’s how to update your API client wrapper:
Step 1 - Your current v2 client:
class FlexyAPI {
constructor ( apiKey ) {
this . apiKey = apiKey ;
this . baseUrl = "https://flexy-api.oneclickdz.com" ;
}
async request ( endpoint , options = {}) {
const response = await fetch ( ` ${ this . baseUrl }${ endpoint } ` , {
... options ,
headers: {
... options . headers ,
authorization: this . apiKey ,
},
});
return response . json ();
}
async getBalance () {
const data = await this . request ( "/v2/account/balance" );
return data . balance ;
}
async sendTopup ( params ) {
return await this . request ( "/v2/topup/sendTopup" , {
method: "POST" ,
headers: { "Content-Type" : "application/json" },
body: JSON . stringify ( params ),
});
}
}
class FlexyAPI {
private $apiKey ;
private $baseUrl = 'https://flexy-api.oneclickdz.com' ;
public function __construct ( $apiKey ) {
$this -> apiKey = $apiKey ;
}
private function request ( $endpoint , $options = []) {
$ch = curl_init ( $this -> baseUrl . $endpoint );
curl_setopt ( $ch , CURLOPT_HTTPHEADER , [
'authorization: ' . $this -> apiKey
]);
curl_setopt ( $ch , CURLOPT_RETURNTRANSFER , true );
if ( isset ( $options [ 'method' ]) && $options [ 'method' ] === 'POST' ) {
curl_setopt ( $ch , CURLOPT_POST , true );
curl_setopt ( $ch , CURLOPT_POSTFIELDS , $options [ 'body' ]);
}
$response = curl_exec ( $ch );
return json_decode ( $response , true );
}
public function getBalance () {
$data = $this -> request ( '/v2/account/balance' );
return $data [ 'balance' ];
}
public function sendTopup ( $params ) {
return $this -> request ( '/v2/topup/sendTopup' , [
'method' => 'POST' ,
'body' => json_encode ( $params )
]);
}
}
import requests
class FlexyAPI :
def __init__ ( self , api_key ):
self .api_key = api_key
self .base_url = 'https://flexy-api.oneclickdz.com'
def request ( self , endpoint , method = 'GET' , data = None ):
headers = { 'authorization' : self .api_key}
url = f " { self .base_url }{ endpoint } "
if method == 'GET' :
response = requests.get(url, headers = headers)
else :
headers[ 'Content-Type' ] = 'application/json'
response = requests.post(url, headers = headers, json = data)
return response.json()
def get_balance ( self ):
data = self .request( '/v2/account/balance' )
return data[ 'balance' ]
def send_topup ( self , params ):
return self .request( '/v2/topup/sendTopup' , method = 'POST' , data = params)
Step 2 - What to change:
Change base URL: https://flexy-api.oneclickdz.com → https://api.oneclickdz.com
Change header: authorization → X-Access-Token
Add response wrapper handling in request() method
Check result.success and handle errors
Update all endpoint paths to v3
Throw structured errors with codes and request IDs
Best Practice : Centralize error handling in your API client. This makes it
easier to add logging, monitoring, and user notifications.
Step 3 - Your new v3 client:
class FlexyAPI {
constructor ( apiKey ) {
this . apiKey = apiKey ;
this . baseUrl = "https://api.oneclickdz.com" ;
}
async request ( endpoint , options = {}) {
const response = await fetch ( ` ${ this . baseUrl }${ endpoint } ` , {
... options ,
headers: {
... options . headers ,
"X-Access-Token" : this . apiKey , // Changed header name
},
});
const result = await response . json ();
// Handle v3 response wrapper
if ( ! result . success ) {
const error = new Error ( result . error . message );
error . code = result . error . code ;
error . details = result . error . details ;
error . requestId = result . requestId ;
throw error ;
}
return result . data ; // Return unwrapped data
}
async getBalance () {
const data = await this . request ( "/v3/account/balance" ); // Updated path
return data . balance ;
}
async sendTopup ( params ) {
return await this . request ( "/v3/mobile/send" , { // Updated path
method: "POST" ,
headers: { "Content-Type" : "application/json" },
body: JSON . stringify ( params ),
});
}
}
class FlexyAPI {
private $apiKey ;
private $baseUrl = 'https://api.oneclickdz.com' ;
public function __construct ( $apiKey ) {
$this -> apiKey = $apiKey ;
}
private function request ( $endpoint , $options = []) {
$ch = curl_init ( $this -> baseUrl . $endpoint );
$headers = [ 'X-Access-Token: ' . $this -> apiKey ]; // Changed header name
if ( isset ( $options [ 'method' ]) && $options [ 'method' ] === 'POST' ) {
curl_setopt ( $ch , CURLOPT_POST , true );
curl_setopt ( $ch , CURLOPT_POSTFIELDS , $options [ 'body' ]);
$headers [] = 'Content-Type: application/json' ;
}
curl_setopt ( $ch , CURLOPT_HTTPHEADER , $headers );
curl_setopt ( $ch , CURLOPT_RETURNTRANSFER , true );
$response = curl_exec ( $ch );
$result = json_decode ( $response , true );
// Handle v3 response wrapper
if ( ! $result [ 'success' ]) {
$error = [
'message' => $result [ 'error' ][ 'message' ],
'code' => $result [ 'error' ][ 'code' ],
'details' => $result [ 'error' ][ 'details' ] ?? null ,
'requestId' => $result [ 'requestId' ]
];
throw new Exception ( json_encode ( $error ));
}
return $result [ 'data' ]; // Return unwrapped data
}
public function getBalance () {
$data = $this -> request ( '/v3/account/balance' ); // Updated path
return $data [ 'balance' ];
}
public function sendTopup ( $params ) {
return $this -> request ( '/v3/mobile/send' , [ // Updated path
'method' => 'POST' ,
'body' => json_encode ( $params )
]);
}
}
import requests
class FlexyAPIError ( Exception ):
"""Custom exception for API errors"""
def ** init ** ( self , message, code, details= None , request_id= None ):
super (). ** init ** (message)
self .code = code
self .details = details
self .request_id = request_id
class FlexyAPI :
def ** init ** ( self , api_key):
self .api_key = api_key
self .base_url = 'https://api.oneclickdz.com'
def request ( self , endpoint , method = 'GET' , data = None ):
headers = { 'X-Access-Token' : self .api_key} # Changed header name
url = f " { self .base_url }{ endpoint } "
if method == 'GET' :
response = requests.get(url, headers = headers)
else :
headers[ 'Content-Type' ] = 'application/json'
response = requests.post(url, headers = headers, json = data)
result = response.json()
# Handle v3 response wrapper
if not result[ 'success' ]:
raise FlexyAPIError(
result[ 'error' ][ 'message' ],
result[ 'error' ][ 'code' ],
result[ 'error' ].get( 'details' ),
result[ 'requestId' ]
)
return result[ 'data' ] # Return unwrapped data
def get_balance ( self ):
data = self .request( '/v3/account/balance' ) # Updated path
return data[ 'balance' ]
def send_topup ( self , params ):
return self .request( '/v3/mobile/send' , method = 'POST' , data = params) # Updated path
Complete Endpoint Reference
Service v2 Endpoint v3 Endpoint Notes
Authentication Header authorization: KEYX-Access-Token: KEYHeader name changed Validate GET /v2/validateGET /v3/validateResponse wrapped Mobile List Plans GET /v2/plans/listAllGET /v3/mobile/plansPath changed, typo fixed Send Top-Up POST /v2/topup/sendTopupPOST /v3/mobile/sendPath simplified Check by Ref GET /v2/topup/checkStatus/REF/:refGET /v3/mobile/check-ref/:refPath simplified, refund info added Check by ID GET /v2/topup/checkStatus/ID/:idGET /v3/mobile/check-id/:idPath simplified List Top-Ups GET /v2/topup/listGET /v3/mobile/listPagination moved to data.pagination Internet Products GET /v2/internet/checkCards/:typeGET /v3/internet/products?type=Path param → query param Check Number GET /v2/internet/checkNumber/:t/:nGET /v3/internet/check-number?type=&number=Path params → query params Send POST /v2/internet/sendTopupPOST /v3/internet/sendPath simplified Check by Ref GET /v2/internet/checkStatus/REF/:refGET /v3/internet/check-ref/:refPath simplified Check by ID GET /v2/internet/checkStatus/ID/:idGET /v3/internet/check-id/:idPath simplified List GET /v2/internet/listGET /v3/internet/listPagination moved Gift Cards Catalog GET /v2/gift-cards/catalogueGET /v3/gift-cards/catalogSpelling fixed Check Product GET /v2/gift-cards/checkProduct/:idGET /v3/gift-cards/checkProduct/:idResponse wrapped Place Order POST /v2/gift-cards/placeOrderPOST /v3/gift-cards/placeOrderResponse wrapped Check Order GET /v2/gift-cards/checkOrder/:idGET /v3/gift-cards/checkOrder/:idResponse wrapped List Orders GET /v2/gift-cards/listGET /v3/gift-cards/listPagination moved Account Balance GET /v2/account/balanceGET /v3/account/balanceResponse wrapped Transactions GET /v2/account/transactionsGET /v3/account/transactionsPagination moved
Error Code Reference
Common v2 errors and their v3 equivalents:
v2 Error v3 Error Code What Changed
"NO-BALANCE"INSUFFICIENT_BALANCENow a code with structured message "DUPLICATED-REF"DUPLICATED_REFSame code, now with details object "ERR_VALIDATION"ERR_VALIDATIONIncludes details.field and `details.pattern "ERR_PHONE"ERR_PHONEIncludes validation pattern in details "ERR_STOCK"ERR_STOCKProduct availability information "Forbidden"ERR_AUTHMore descriptive, unified auth errors "Access token missing"ERR_AUTHSame code for all auth errors "NOT_FOUND"NOT_FOUNDResource not found (order, product, etc.)
How to handle v3 errors:
// v2 way:
if ( result . error === "NO-BALANCE" ) {
// handle
}
// v3 way:
if ( ! result . success && result . error . code === "INSUFFICIENT_BALANCE" ) {
console . log ( "Request ID:" , result . requestId ); // Save for support
// handle with more context
if ( result . error . details ?. currentBalance ) {
console . log ( "Current balance:" , result . error . details . currentBalance );
}
}
// v2 way:
if ( $result [ 'error' ] === 'NO-BALANCE' ) {
// handle
}
// v3 way:
if ( ! $result [ 'success' ] && $result [ 'error' ][ 'code' ] === 'INSUFFICIENT_BALANCE' ) {
error_log ( "Request ID: " . $result [ 'requestId' ]); // Save for support
// handle with more context
if ( isset ( $result [ 'error' ][ 'details' ][ 'currentBalance' ])) {
echo "Current balance: " . $result [ 'error' ][ 'details' ][ 'currentBalance' ] . " \n " ;
}
}
# v2 way:
if result.get( 'error' ) == 'NO-BALANCE' :
# handle
# v3 way:
if not result[ 'success' ] and result[ 'error' ][ 'code' ] == 'INSUFFICIENT_BALANCE' :
print ( f "Request ID: { result[ 'requestId' ] } " ) # Save for support
# handle with more context
if 'currentBalance' in result[ 'error' ].get( 'details' , {}):
print ( f "Current balance: { result[ 'error' ][ 'details' ][ 'currentBalance' ] } " )
Critical Deadline : After October 30, 2026, all v2 API requests will fail.
Plan your migration accordingly to avoid service disruption.
Common Migration Issues & Solutions
Issue: Getting 401 Unauthorized Errors
Problem : Changed endpoint but forgot to update header name.Solution : Replace authorization with X-Access-Token in ALL requests.// ❌ Wrong
headers : { authorization : API_KEY }
// ✅ Correct
headers : { "X-Access-Token" : API_KEY }
Issue: Getting 'undefined' When Accessing Data
Problem : Trying to access data directly instead of through result.data.
Solution : Always access response data through the data property.❌ Wrong const balance = result.balance; ✅ Correct const
balance = result . data . balance ; ```
</Accordion>
{" "}
<Accordion title="Issue: Pagination Not Working" icon="circle-exclamation">
**Problem**: Looking for pagination fields at root level. **Solution**: Access
pagination through ` data . pagination `. ``` javascript // ❌ Wrong const total =
result . totalResults ; const page = result . currentPage ; // ✅ Correct const
total = result . data . pagination . totalResults ; const page =
result . data . pagination . page ; ```
</Accordion>
<Accordion title="Issue: Error Handling Not Working" icon="circle-exclamation">
**Problem**: Checking for old error format.
**Solution**: Check ` success ` boolean and access structured error object.
``` javascript
// ❌ Wrong
if ( result . error ) {
console . log ( result . message );
}
// ✅ Correct
if ( ! result . success ) {
console . log ( result . error . code , result . error . message );
console . log ( "Request ID:" , result . requestId );
}
Issue: 404 Not Found Errors
Problem : Using old v2 endpoint paths.Solution : Update all paths according to the reference table above.// ❌ Wrong
POST / v2 / topup / sendTopup
GET / v2 / internet / checkCards / ADSL
// ✅ Correct
POST / v3 / mobile / send
GET / v3 / internet / products ? type = ADSL
Testing Checklist
Before deploying to production, verify:
Need Help?
Migration Support : Our team is here to help! Email us with “API v3
Migration Support” in the subject line, and include: - Your current
implementation details - Specific errors (with request IDs) - Code samples
showing the issue